Mercurial > dovecot > original-hg > dovecot-1.2
view src/auth/auth-request-handler.c @ 7086:7ed926ed7aa4 HEAD
Updated copyright notices to include year 2008.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 01 Jan 2008 22:05:21 +0200 |
parents | 414c9d631a81 |
children | a281705a2360 |
line wrap: on
line source
/* Copyright (c) 2005-2008 Dovecot authors, see the included COPYING file */ #include "common.h" #include "ioloop.h" #include "buffer.h" #include "base64.h" #include "hash.h" #include "str.h" #include "str-sanitize.h" #include "auth-request.h" #include "auth-master-connection.h" #include "auth-request-handler.h" #include <stdlib.h> struct auth_request_handler { int refcount; pool_t pool; struct hash_table *requests; struct auth *auth; unsigned int connect_uid, client_pid; auth_request_callback_t *callback; void *context; auth_request_callback_t *master_callback; }; static buffer_t *auth_failures_buf; static struct timeout *to_auth_failures; #undef auth_request_handler_create struct auth_request_handler * auth_request_handler_create(struct auth *auth, auth_request_callback_t *callback, void *context, auth_request_callback_t *master_callback) { struct auth_request_handler *handler; pool_t pool; pool = pool_alloconly_create("auth request handler", 4096); handler = p_new(pool, struct auth_request_handler, 1); handler->refcount = 1; handler->pool = pool; handler->requests = hash_create(default_pool, pool, 0, NULL, NULL); handler->auth = auth; handler->callback = callback; handler->context = context; handler->master_callback = master_callback; return handler; } void auth_request_handler_unref(struct auth_request_handler **_handler) { struct auth_request_handler *handler = *_handler; struct hash_iterate_context *iter; void *key, *value; *_handler = NULL; i_assert(handler->refcount > 0); if (--handler->refcount > 0) return; iter = hash_iterate_init(handler->requests); while (hash_iterate(iter, &key, &value)) { struct auth_request *auth_request = value; auth_request_unref(&auth_request); } hash_iterate_deinit(&iter); /* notify parent that we're done with all requests */ handler->callback(NULL, handler->context); hash_destroy(&handler->requests); pool_unref(&handler->pool); } void auth_request_handler_set(struct auth_request_handler *handler, unsigned int connect_uid, unsigned int client_pid) { handler->connect_uid = connect_uid; handler->client_pid = client_pid; } static void auth_request_handler_remove(struct auth_request_handler *handler, struct auth_request *request) { hash_remove(handler->requests, POINTER_CAST(request->id)); auth_request_unref(&request); } void auth_request_handler_check_timeouts(struct auth_request_handler *handler) { struct hash_iterate_context *iter; void *key, *value; iter = hash_iterate_init(handler->requests); while (hash_iterate(iter, &key, &value)) { struct auth_request *request = value; if (request->last_access + AUTH_REQUEST_TIMEOUT < ioloop_time) auth_request_handler_remove(handler, request); } hash_iterate_deinit(&iter); } static const char *get_client_extra_fields(struct auth_request *request) { string_t *str; const char **fields, *extra_fields; unsigned int src, dest; bool seen_pass = FALSE; if (auth_stream_is_empty(request->extra_fields)) return NULL; extra_fields = auth_stream_reply_export(request->extra_fields); if (!request->proxy) { /* we only wish to remove all fields prefixed with "userdb_" */ if (strstr(extra_fields, "userdb_") == NULL) return extra_fields; } str = t_str_new(128); fields = t_strsplit(extra_fields, "\t"); for (src = dest = 0; fields[src] != NULL; src++) { if (strncmp(fields[src], "userdb_", 7) != 0) { if (str_len(str) > 0) str_append_c(str, '\t'); if (!seen_pass && strncmp(fields[src], "pass=", 5) == 0) seen_pass = TRUE; str_append(str, fields[src]); } } if (request->proxy && !seen_pass && request->mech_password != NULL) { /* we're proxying - send back the password that was sent by user (not the password in passdb). */ str_printfa(str, "\tpass=%s", request->mech_password); } return str_len(str) == 0 ? NULL : str_c(str); } static void auth_callback(struct auth_request *request, enum auth_client_result result, const void *reply, size_t reply_size) { struct auth_request_handler *handler = request->context; string_t *str; const char *fields; str = t_str_new(128 + MAX_BASE64_ENCODED_SIZE(reply_size)); switch (result) { case AUTH_CLIENT_RESULT_CONTINUE: str_printfa(str, "CONT\t%u\t", request->id); base64_encode(reply, reply_size, str); request->accept_input = TRUE; handler->callback(str_c(str), handler->context); break; case AUTH_CLIENT_RESULT_SUCCESS: str_printfa(str, "OK\t%u\tuser=%s", request->id, request->user); if (reply_size > 0) { str_append(str, "\tresp="); base64_encode(reply, reply_size, str); } fields = get_client_extra_fields(request); if (fields != NULL) { str_append_c(str, '\t'); str_append(str, fields); } if (request->no_login || handler->master_callback == NULL) { /* this request doesn't have to wait for master process to pick it up. delete it */ auth_request_handler_remove(handler, request); } handler->callback(str_c(str), handler->context); break; case AUTH_CLIENT_RESULT_FAILURE: str_printfa(str, "FAIL\t%u", request->id); if (request->user != NULL) str_printfa(str, "\tuser=%s", request->user); if (request->internal_failure) str_append(str, "\ttemp"); fields = get_client_extra_fields(request); if (fields != NULL) { str_append_c(str, '\t'); str_append(str, fields); } if (request->delayed_failure) { /* we came here from flush_failures() */ handler->callback(str_c(str), handler->context); break; } /* remove the request from requests-list */ auth_request_ref(request); auth_request_handler_remove(handler, request); if (request->no_failure_delay) { /* passdb specifically requested not to delay the reply. */ handler->callback(str_c(str), handler->context); auth_request_unref(&request); } else { /* failure. don't announce it immediately to avoid a) timing attacks, b) flooding */ request->delayed_failure = TRUE; handler->refcount++; buffer_append(auth_failures_buf, &request, sizeof(request)); } break; } /* NOTE: request may be destroyed now */ auth_request_handler_unref(&handler); } static void auth_request_handler_auth_fail(struct auth_request_handler *handler, struct auth_request *request, const char *reason) { string_t *reply = t_str_new(64); auth_request_log_info(request, request->mech->mech_name, "%s", reason); str_printfa(reply, "FAIL\t%u\treason=%s", request->id, reason); handler->callback(str_c(reply), handler->context); auth_request_handler_remove(handler, request); } bool auth_request_handler_auth_begin(struct auth_request_handler *handler, const char *args) { const struct mech_module *mech; struct auth_request *request; const char *const *list, *name, *arg, *initial_resp; const void *initial_resp_data; size_t initial_resp_len; unsigned int id; buffer_t *buf; bool valid_client_cert; /* <id> <mechanism> [...] */ list = t_strsplit(args, "\t"); if (list[0] == NULL || list[1] == NULL) { i_error("BUG: Authentication client %u " "sent broken AUTH request", handler->client_pid); return FALSE; } id = (unsigned int)strtoul(list[0], NULL, 10); mech = mech_module_find(list[1]); if (mech == NULL) { /* unsupported mechanism */ i_error("BUG: Authentication client %u requested unsupported " "authentication mechanism %s", handler->client_pid, str_sanitize(list[1], MAX_MECH_NAME_LEN)); return FALSE; } request = auth_request_new(handler->auth, mech, auth_callback, handler); request->connect_uid = handler->connect_uid; request->client_pid = handler->client_pid; request->id = id; /* parse optional parameters */ initial_resp = NULL; valid_client_cert = FALSE; for (list += 2; *list != NULL; list++) { arg = strchr(*list, '='); if (arg == NULL) { name = *list; arg = ""; } else { name = t_strdup_until(*list, arg); arg++; } if (auth_request_import(request, name, arg)) ; else if (strcmp(name, "valid-client-cert") == 0) valid_client_cert = TRUE; else if (strcmp(name, "resp") == 0) { initial_resp = arg; /* this must be the last parameter */ list++; break; } } if (*list != NULL) { i_error("BUG: Authentication client %u " "sent AUTH parameters after 'resp'", handler->client_pid); return FALSE; } if (request->service == NULL) { i_error("BUG: Authentication client %u " "didn't specify service in request", handler->client_pid); return FALSE; } hash_insert(handler->requests, POINTER_CAST(id), request); if (request->auth->ssl_require_client_cert && !valid_client_cert) { /* we fail without valid certificate */ auth_request_handler_auth_fail(handler, request, "Client didn't present valid SSL certificate"); return TRUE; } if (initial_resp == NULL) { initial_resp_data = NULL; initial_resp_len = 0; } else { size_t len = strlen(initial_resp); buf = buffer_create_dynamic(pool_datastack_create(), MAX_BASE64_DECODED_SIZE(len)); if (base64_decode(initial_resp, len, NULL, buf) < 0) { auth_request_handler_auth_fail(handler, request, "Invalid base64 data in initial response"); return TRUE; } initial_resp_data = buf->data; initial_resp_len = buf->used; } /* handler is referenced until auth_callback is called. */ handler->refcount++; auth_request_initial(request, initial_resp_data, initial_resp_len); return TRUE; } bool auth_request_handler_auth_continue(struct auth_request_handler *handler, const char *args) { struct auth_request *request; const char *data; size_t data_len; buffer_t *buf; unsigned int id; data = strchr(args, '\t'); if (data == NULL) { i_error("BUG: Authentication client sent broken CONT request"); return FALSE; } data++; id = (unsigned int)strtoul(args, NULL, 10); request = hash_lookup(handler->requests, POINTER_CAST(id)); if (request == NULL) { string_t *reply = t_str_new(64); str_printfa(reply, "FAIL\t%u\treason=Timeouted", id); handler->callback(str_c(reply), handler->context); return TRUE; } /* accept input only once after mechanism has sent a CONT reply */ if (!request->accept_input) { auth_request_handler_auth_fail(handler, request, "Unexpected continuation"); return TRUE; } request->accept_input = FALSE; data_len = strlen(data); buf = buffer_create_dynamic(pool_datastack_create(), MAX_BASE64_DECODED_SIZE(data_len)); if (base64_decode(data, data_len, NULL, buf) < 0) { auth_request_handler_auth_fail(handler, request, "Invalid base64 data in continued response"); return TRUE; } /* handler is referenced until auth_callback is called. */ handler->refcount++; auth_request_continue(request, buf->data, buf->used); return TRUE; } static void userdb_callback(enum userdb_result result, struct auth_request *request) { struct auth_request_handler *handler = request->context; struct auth_stream_reply *reply = request->userdb_reply; string_t *str; i_assert(request->state == AUTH_REQUEST_STATE_USERDB); request->state = AUTH_REQUEST_STATE_FINISHED; if (request->userdb_lookup_failed) result = USERDB_RESULT_INTERNAL_FAILURE; str = t_str_new(256); switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: str_printfa(str, "FAIL\t%u", request->id); break; case USERDB_RESULT_USER_UNKNOWN: str_printfa(str, "NOTFOUND\t%u", request->id); break; case USERDB_RESULT_OK: if (request->master_user != NULL) { auth_stream_reply_add(reply, "master_user", request->master_user); } str_printfa(str, "USER\t%u\t", request->id); str_append(str, auth_stream_reply_export(reply)); break; } handler->master_callback(str_c(str), request->master); auth_master_connection_unref(&request->master); auth_request_unref(&request); auth_request_handler_unref(&handler); } void auth_request_handler_master_request(struct auth_request_handler *handler, struct auth_master_connection *master, unsigned int id, unsigned int client_id) { struct auth_request *request; string_t *reply; reply = t_str_new(64); request = hash_lookup(handler->requests, POINTER_CAST(client_id)); if (request == NULL) { i_error("Master request %u.%u not found", handler->client_pid, client_id); str_printfa(reply, "NOTFOUND\t%u", id); handler->master_callback(str_c(reply), master); return; } auth_request_ref(request); auth_request_handler_remove(handler, request); if (request->state != AUTH_REQUEST_STATE_FINISHED || !request->successful) { i_error("Master requested unfinished authentication request " "%u.%u", handler->client_pid, client_id); str_printfa(reply, "NOTFOUND\t%u", id); handler->master_callback(str_c(reply), master); auth_request_unref(&request); } else { /* the request isn't being referenced anywhere anymore, so we can do a bit of kludging.. replace the request's old client_id with master's id. */ request->state = AUTH_REQUEST_STATE_USERDB; request->id = id; request->context = handler; request->master = master; /* master and handler are referenced until userdb_callback i s called. */ auth_master_connection_ref(master); handler->refcount++; auth_request_lookup_user(request, userdb_callback); } } void auth_request_handler_flush_failures(void) { struct auth_request **auth_request; size_t i, size; auth_request = buffer_get_modifiable_data(auth_failures_buf, &size); size /= sizeof(*auth_request); for (i = 0; i < size; i++) { i_assert(auth_request[i]->state == AUTH_REQUEST_STATE_FINISHED); auth_request[i]->callback(auth_request[i], AUTH_CLIENT_RESULT_FAILURE, NULL, 0); auth_request_unref(&auth_request[i]); } buffer_set_used_size(auth_failures_buf, 0); } static void auth_failure_timeout(void *context ATTR_UNUSED) { auth_request_handler_flush_failures(); } void auth_request_handler_init(void) { auth_failures_buf = buffer_create_dynamic(default_pool, 1024); to_auth_failures = timeout_add(2000, auth_failure_timeout, NULL); } void auth_request_handler_deinit(void) { auth_request_handler_flush_failures(); buffer_free(&auth_failures_buf); timeout_remove(&to_auth_failures); }