Mercurial > dovecot > core-2.2
view src/auth/auth-worker-client.c @ 21390:2e2563132d5f
Updated copyright notices to include the year 2017.
author | Stephan Bosch <stephan.bosch@dovecot.fi> |
---|---|
date | Wed, 11 Jan 2017 02:51:13 +0100 |
parents | 0b12b2cb67dd |
children | 6f75b9de84e1 |
line wrap: on
line source
/* Copyright (c) 2005-2017 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "base64.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "hex-binary.h" #include "str.h" #include "strescape.h" #include "process-title.h" #include "master-service.h" #include "auth-request.h" #include "auth-worker-client.h" #define AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS 30 #define OUTBUF_THROTTLE_SIZE (1024*10) #define CLIENT_STATE_HANDSHAKE "handshaking" #define CLIENT_STATE_ITER "iterating users" #define CLIENT_STATE_IDLE "idling" #define CLIENT_STATE_STOP "waiting for shutdown" struct auth_worker_client { int refcount; struct auth *auth; int fd; struct io *io; struct istream *input; struct ostream *output; struct timeout *to_idle; time_t cmd_start; unsigned int version_received:1; unsigned int dbhash_received:1; unsigned int error_sent:1; }; struct auth_worker_list_context { struct auth_worker_client *client; struct auth_request *auth_request; struct userdb_iterate_context *iter; bool sending, sent, done; }; struct auth_worker_client *auth_worker_client; static bool auth_worker_client_error = FALSE; static void auth_worker_input(struct auth_worker_client *client); static int auth_worker_output(struct auth_worker_client *client); void auth_worker_refresh_proctitle(const char *state) { if (!global_auth_settings->verbose_proctitle || !worker) return; if (auth_worker_client_error) state = "error"; else if (auth_worker_client == NULL) state = "waiting for connection"; process_title_set(t_strdup_printf("worker: %s", state)); } static void auth_worker_client_check_throttle(struct auth_worker_client *client) { if (o_stream_get_buffer_used_size(client->output) >= OUTBUF_THROTTLE_SIZE) { /* stop reading new requests until client has read the pending replies. */ if (client->io != NULL) io_remove(&client->io); } } static struct auth_request * worker_auth_request_new(struct auth_worker_client *client, unsigned int id, const char *const *args) { struct auth_request *auth_request; const char *key, *value; auth_request = auth_request_new_dummy(); client->refcount++; auth_request->context = client; auth_request->id = id; for (; *args != NULL; args++) { value = strchr(*args, '='); if (value == NULL) (void)auth_request_import(auth_request, *args, NULL); else { key = t_strdup_until(*args, value++); (void)auth_request_import(auth_request, key, value); } } auth_request_init(auth_request); return auth_request; } static void auth_worker_send_reply(struct auth_worker_client *client, struct auth_request *request, string_t *str) { time_t cmd_duration = time(NULL) - client->cmd_start; const char *p; if (worker_restart_request) o_stream_nsend_str(client->output, "RESTART\n"); o_stream_nsend(client->output, str_data(str), str_len(str)); if (o_stream_nfinish(client->output) < 0 && request != NULL && cmd_duration > AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS) { p = strchr(str_c(str), '\t'); p = p == NULL ? "BUG" : t_strcut(p+1, '\t'); i_warning("Auth master disconnected us while handling " "request for %s for %ld secs (result=%s)", request->user, (long)cmd_duration, p); } } static void reply_append_extra_fields(string_t *str, struct auth_request *request) { if (!auth_fields_is_empty(request->extra_fields)) { str_append_c(str, '\t'); auth_fields_append(request->extra_fields, str, 0, 0); } if (request->userdb_reply != NULL && auth_fields_is_empty(request->userdb_reply)) { /* all userdb_* fields had NULL values. we'll still need to tell this to the master */ str_append(str, "\tuserdb_"AUTH_REQUEST_USER_KEY_IGNORE); } } static void verify_plain_callback(enum passdb_result result, struct auth_request *request) { struct auth_worker_client *client = request->context; string_t *str; if (request->failed && result == PASSDB_RESULT_OK) result = PASSDB_RESULT_PASSWORD_MISMATCH; str = t_str_new(128); str_printfa(str, "%u\t", request->id); if (result == PASSDB_RESULT_OK) if (auth_fields_exists(request->extra_fields, "noauthenticate")) str_append(str, "NEXT"); else str_append(str, "OK"); else str_printfa(str, "FAIL\t%d", result); if (result != PASSDB_RESULT_INTERNAL_FAILURE) { str_append_c(str, '\t'); str_append_tabescaped(str, request->user); str_append_c(str, '\t'); if (request->passdb_password != NULL) str_append_tabescaped(str, request->passdb_password); reply_append_extra_fields(str, request); } str_append_c(str, '\n'); auth_worker_send_reply(client, request, str); auth_request_unref(&request); auth_worker_client_check_throttle(client); auth_worker_client_unref(&client); } static bool auth_worker_handle_passv(struct auth_worker_client *client, unsigned int id, const char *const *args) { /* verify plaintext password */ struct auth_request *auth_request; struct auth_passdb *passdb; const char *password; unsigned int passdb_id; /* <passdb id> <password> [<args>] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { i_error("BUG: Auth worker server sent us invalid PASSV"); return FALSE; } password = args[1]; auth_request = worker_auth_request_new(client, id, args + 2); auth_request->mech_password = p_strdup(auth_request->pool, password); if (auth_request->user == NULL || auth_request->service == NULL) { i_error("BUG: PASSV had missing parameters"); auth_request_unref(&auth_request); return FALSE; } passdb = auth_request->passdb; while (passdb != NULL && passdb->passdb->id != passdb_id) passdb = passdb->next; if (passdb == NULL) { /* could be a masterdb */ passdb = auth_request_get_auth(auth_request)->masterdbs; while (passdb != NULL && passdb->passdb->id != passdb_id) passdb = passdb->next; if (passdb == NULL) { i_error("BUG: PASSV had invalid passdb ID"); auth_request_unref(&auth_request); return FALSE; } } auth_request->passdb = passdb; passdb->passdb->iface. verify_plain(auth_request, password, verify_plain_callback); return TRUE; } static void lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { struct auth_worker_client *client = request->context; string_t *str; if (request->failed && result == PASSDB_RESULT_OK) result = PASSDB_RESULT_PASSWORD_MISMATCH; str = t_str_new(128); str_printfa(str, "%u\t", request->id); if (result != PASSDB_RESULT_OK && result != PASSDB_RESULT_NEXT) str_printfa(str, "FAIL\t%d", result); else { if (result == PASSDB_RESULT_NEXT) str_append(str, "NEXT\t"); else str_append(str, "OK\t"); str_append_tabescaped(str, request->user); str_append_c(str, '\t'); if (request->credentials_scheme[0] != '\0') { str_printfa(str, "{%s.b64}", request->credentials_scheme); base64_encode(credentials, size, str); } else { i_assert(size == 0); } reply_append_extra_fields(str, request); } str_append_c(str, '\n'); auth_worker_send_reply(client, request, str); auth_request_unref(&request); auth_worker_client_check_throttle(client); auth_worker_client_unref(&client); } static bool auth_worker_handle_passl(struct auth_worker_client *client, unsigned int id, const char *const *args) { /* lookup credentials */ struct auth_request *auth_request; const char *scheme; unsigned int passdb_id; /* <passdb id> <scheme> [<args>] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { i_error("BUG: Auth worker server sent us invalid PASSL"); return FALSE; } scheme = args[1]; auth_request = worker_auth_request_new(client, id, args + 2); auth_request->credentials_scheme = p_strdup(auth_request->pool, scheme); if (auth_request->user == NULL || auth_request->service == NULL) { i_error("BUG: PASSL had missing parameters"); auth_request_unref(&auth_request); return FALSE; } while (auth_request->passdb->passdb->id != passdb_id) { auth_request->passdb = auth_request->passdb->next; if (auth_request->passdb == NULL) { i_error("BUG: PASSL had invalid passdb ID"); auth_request_unref(&auth_request); return FALSE; } } if (auth_request->passdb->passdb->iface.lookup_credentials == NULL) { i_error("BUG: PASSL lookup not supported by given passdb"); auth_request_unref(&auth_request); return FALSE; } auth_request->prefer_plain_credentials = TRUE; auth_request->passdb->passdb->iface. lookup_credentials(auth_request, lookup_credentials_callback); return TRUE; } static void set_credentials_callback(bool success, struct auth_request *request) { struct auth_worker_client *client = request->context; string_t *str; str = t_str_new(64); str_printfa(str, "%u\t%s\n", request->id, success ? "OK" : "FAIL"); auth_worker_send_reply(client, request, str); auth_request_unref(&request); auth_worker_client_check_throttle(client); auth_worker_client_unref(&client); } static bool auth_worker_handle_setcred(struct auth_worker_client *client, unsigned int id, const char *const *args) { struct auth_request *auth_request; unsigned int passdb_id; const char *creds; /* <passdb id> <credentials> [<args>] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { i_error("BUG: Auth worker server sent us invalid SETCRED"); return FALSE; } creds = args[1]; auth_request = worker_auth_request_new(client, id, args + 2); if (auth_request->user == NULL || auth_request->service == NULL) { i_error("BUG: SETCRED had missing parameters"); auth_request_unref(&auth_request); return FALSE; } while (auth_request->passdb->passdb->id != passdb_id) { auth_request->passdb = auth_request->passdb->next; if (auth_request->passdb == NULL) { i_error("BUG: SETCRED had invalid passdb ID"); auth_request_unref(&auth_request); return FALSE; } } auth_request->passdb->passdb->iface. set_credentials(auth_request, creds, set_credentials_callback); return TRUE; } static void lookup_user_callback(enum userdb_result result, struct auth_request *auth_request) { struct auth_worker_client *client = auth_request->context; string_t *str; str = t_str_new(128); str_printfa(str, "%u\t", auth_request->id); switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: str_append(str, "FAIL\t"); break; case USERDB_RESULT_USER_UNKNOWN: str_append(str, "NOTFOUND\t"); break; case USERDB_RESULT_OK: str_append(str, "OK\t"); str_append_tabescaped(str, auth_request->user); str_append_c(str, '\t'); auth_fields_append(auth_request->userdb_reply, str, 0, 0); if (auth_request->userdb_lookup_tempfailed) str_append(str, "\ttempfail"); break; } str_append_c(str, '\n'); auth_worker_send_reply(client, auth_request, str); auth_request_unref(&auth_request); auth_worker_client_check_throttle(client); auth_worker_client_unref(&client); } static struct auth_userdb * auth_userdb_find_by_id(struct auth_userdb *userdbs, unsigned int id) { struct auth_userdb *db; for (db = userdbs; db != NULL; db = db->next) { if (db->userdb->id == id) return db; } return NULL; } static bool auth_worker_handle_user(struct auth_worker_client *client, unsigned int id, const char *const *args) { /* lookup user */ struct auth_request *auth_request; unsigned int userdb_id; /* <userdb id> [<args>] */ if (str_to_uint(args[0], &userdb_id) < 0) { i_error("BUG: Auth worker server sent us invalid USER"); return FALSE; } auth_request = worker_auth_request_new(client, id, args + 1); if (auth_request->user == NULL || auth_request->service == NULL) { i_error("BUG: USER had missing parameters"); auth_request_unref(&auth_request); return FALSE; } auth_request->userdb_lookup = TRUE; auth_request->userdb = auth_userdb_find_by_id(auth_request->userdb, userdb_id); if (auth_request->userdb == NULL) { i_error("BUG: USER had invalid userdb ID"); auth_request_unref(&auth_request); return FALSE; } if (auth_request->userdb_reply == NULL) auth_request_init_userdb_reply(auth_request); auth_request->userdb->userdb->iface-> lookup(auth_request, lookup_user_callback); return TRUE; } static void auth_worker_client_idle_kill(struct auth_worker_client *client ATTR_UNUSED) { auth_worker_client_send_shutdown(); } static void auth_worker_client_set_idle_timeout(struct auth_worker_client *client) { unsigned int idle_kill_secs; i_assert(client->to_idle == NULL); idle_kill_secs = master_service_get_idle_kill_secs(master_service); if (idle_kill_secs > 0) { client->to_idle = timeout_add(idle_kill_secs * 1000, auth_worker_client_idle_kill, client); } } static void list_iter_deinit(struct auth_worker_list_context *ctx) { struct auth_worker_client *client = ctx->client; string_t *str; i_assert(client->io == NULL); str = t_str_new(32); if (ctx->auth_request->userdb->userdb->iface-> iterate_deinit(ctx->iter) < 0) str_printfa(str, "%u\tFAIL\n", ctx->auth_request->id); else str_printfa(str, "%u\tOK\n", ctx->auth_request->id); auth_worker_send_reply(client, NULL, str); client->io = io_add(client->fd, IO_READ, auth_worker_input, client); auth_worker_client_set_idle_timeout(client); o_stream_set_flush_callback(client->output, auth_worker_output, client); auth_request_unref(&ctx->auth_request); auth_worker_client_unref(&client); i_free(ctx); auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); } static void list_iter_callback(const char *user, void *context) { struct auth_worker_list_context *ctx = context; string_t *str; if (user == NULL) { if (ctx->sending) ctx->done = TRUE; else list_iter_deinit(ctx); return; } if (!ctx->sending) o_stream_cork(ctx->client->output); T_BEGIN { str = t_str_new(128); str_printfa(str, "%u\t*\t%s\n", ctx->auth_request->id, user); o_stream_nsend(ctx->client->output, str_data(str), str_len(str)); } T_END; if (ctx->sending) { /* avoid recursively looping to this same function */ ctx->sent = TRUE; return; } do { ctx->sending = TRUE; ctx->sent = FALSE; T_BEGIN { ctx->auth_request->userdb->userdb->iface-> iterate_next(ctx->iter); } T_END; if (o_stream_get_buffer_used_size(ctx->client->output) > OUTBUF_THROTTLE_SIZE) { if (o_stream_flush(ctx->client->output) < 0) { ctx->done = TRUE; break; } } } while (ctx->sent && o_stream_get_buffer_used_size(ctx->client->output) <= OUTBUF_THROTTLE_SIZE); o_stream_uncork(ctx->client->output); ctx->sending = FALSE; if (ctx->done) list_iter_deinit(ctx); else o_stream_set_flush_pending(ctx->client->output, TRUE); } static int auth_worker_list_output(struct auth_worker_list_context *ctx) { int ret; if ((ret = o_stream_flush(ctx->client->output)) < 0) { list_iter_deinit(ctx); return 1; } if (ret > 0) T_BEGIN { ctx->auth_request->userdb->userdb->iface-> iterate_next(ctx->iter); } T_END; return 1; } static bool auth_worker_handle_list(struct auth_worker_client *client, unsigned int id, const char *const *args) { struct auth_worker_list_context *ctx; struct auth_userdb *userdb; unsigned int userdb_id; if (str_to_uint(args[0], &userdb_id) < 0) { i_error("BUG: Auth worker server sent us invalid LIST"); return FALSE; } userdb = auth_userdb_find_by_id(client->auth->userdbs, userdb_id); if (userdb == NULL) { i_error("BUG: LIST had invalid userdb ID"); return FALSE; } ctx = i_new(struct auth_worker_list_context, 1); ctx->client = client; ctx->auth_request = worker_auth_request_new(client, id, args + 1); ctx->auth_request->userdb = userdb; if (ctx->auth_request->user == NULL || ctx->auth_request->service == NULL) { i_error("BUG: LIST had missing parameters"); auth_request_unref(&ctx->auth_request); i_free(ctx); return FALSE; } io_remove(&ctx->client->io); if (ctx->client->to_idle != NULL) timeout_remove(&ctx->client->to_idle); o_stream_set_flush_callback(ctx->client->output, auth_worker_list_output, ctx); ctx->iter = ctx->auth_request->userdb->userdb->iface-> iterate_init(ctx->auth_request, list_iter_callback, ctx); ctx->auth_request->userdb->userdb->iface->iterate_next(ctx->iter); return TRUE; } static bool auth_worker_handle_line(struct auth_worker_client *client, const char *line) { const char *const *args; unsigned int id; bool ret = FALSE; args = t_strsplit_tabescaped(line); if (args[0] == NULL || args[1] == NULL || args[2] == NULL || str_to_uint(args[0], &id) < 0) { i_error("BUG: Invalid input: %s", line); return FALSE; } io_loop_time_refresh(); client->cmd_start = ioloop_time; auth_worker_refresh_proctitle(args[1]); if (strcmp(args[1], "PASSV") == 0) ret = auth_worker_handle_passv(client, id, args + 2); else if (strcmp(args[1], "PASSL") == 0) ret = auth_worker_handle_passl(client, id, args + 2); else if (strcmp(args[1], "SETCRED") == 0) ret = auth_worker_handle_setcred(client, id, args + 2); else if (strcmp(args[1], "USER") == 0) ret = auth_worker_handle_user(client, id, args + 2); else if (strcmp(args[1], "LIST") == 0) ret = auth_worker_handle_list(client, id, args + 2); else { i_error("BUG: Auth-worker received unknown command: %s", args[1]); } if (client->io != NULL) auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); return ret; } static bool auth_worker_verify_db_hash(const char *line) { string_t *str; unsigned char passdb_md5[MD5_RESULTLEN]; unsigned char userdb_md5[MD5_RESULTLEN]; passdbs_generate_md5(passdb_md5); userdbs_generate_md5(userdb_md5); str = t_str_new(128); str_append(str, "DBHASH\t"); binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5)); str_append_c(str, '\t'); binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5)); return strcmp(line, str_c(str)) == 0; } static void auth_worker_input(struct auth_worker_client *client) { char *line; bool ret; switch (i_stream_read(client->input)) { case 0: return; case -1: /* disconnected */ auth_worker_client_destroy(&client); return; case -2: /* buffer full */ i_error("BUG: Auth worker server sent us more than %d bytes", (int)AUTH_WORKER_MAX_LINE_LENGTH); auth_worker_client_destroy(&client); return; } if (!client->version_received) { line = i_stream_next_line(client->input); if (line == NULL) return; if (!version_string_verify(line, "auth-worker", AUTH_WORKER_PROTOCOL_MAJOR_VERSION)) { i_error("Auth worker not compatible with this server " "(mixed old and new binaries?)"); auth_worker_client_destroy(&client); return; } client->version_received = TRUE; } if (!client->dbhash_received) { line = i_stream_next_line(client->input); if (line == NULL) return; if (!auth_worker_verify_db_hash(line)) { i_error("Auth worker sees different passdbs/userdbs " "than auth server. Maybe config just changed " "and this goes away automatically?"); auth_worker_client_destroy(&client); return; } client->dbhash_received = TRUE; auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); } client->refcount++; while ((line = i_stream_next_line(client->input)) != NULL) { T_BEGIN { ret = auth_worker_handle_line(client, line); } T_END; if (!ret) { struct auth_worker_client *client2 = client; auth_worker_client_destroy(&client2); break; } } if (client->to_idle != NULL) timeout_reset(client->to_idle); auth_worker_client_unref(&client); } static int auth_worker_output(struct auth_worker_client *client) { if (o_stream_flush(client->output) < 0) { auth_worker_client_destroy(&client); return 1; } if (o_stream_get_buffer_used_size(client->output) <= OUTBUF_THROTTLE_SIZE/3 && client->io == NULL) { /* allow input again */ client->io = io_add(client->fd, IO_READ, auth_worker_input, client); } return 1; } struct auth_worker_client * auth_worker_client_create(struct auth *auth, int fd) { struct auth_worker_client *client; client = i_new(struct auth_worker_client, 1); client->refcount = 1; client->auth = auth; client->fd = fd; client->input = i_stream_create_fd(fd, AUTH_WORKER_MAX_LINE_LENGTH, FALSE); client->output = o_stream_create_fd(fd, (size_t)-1, FALSE); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, auth_worker_output, client); client->io = io_add(fd, IO_READ, auth_worker_input, client); auth_worker_client_set_idle_timeout(client); auth_worker_refresh_proctitle(CLIENT_STATE_HANDSHAKE); auth_worker_client = client; if (auth_worker_client_error) auth_worker_client_send_error(); return client; } void auth_worker_client_destroy(struct auth_worker_client **_client) { struct auth_worker_client *client = *_client; *_client = NULL; if (client->fd == -1) return; i_stream_close(client->input); o_stream_close(client->output); if (client->to_idle != NULL) timeout_remove(&client->to_idle); if (client->io != NULL) io_remove(&client->io); net_disconnect(client->fd); client->fd = -1; auth_worker_client_unref(&client); auth_worker_client = NULL; auth_worker_refresh_proctitle(""); master_service_client_connection_destroyed(master_service); } void auth_worker_client_unref(struct auth_worker_client **_client) { struct auth_worker_client *client = *_client; *_client = NULL; if (--client->refcount > 0) return; i_stream_unref(&client->input); o_stream_unref(&client->output); i_free(client); } void auth_worker_client_send_error(void) { auth_worker_client_error = TRUE; if (auth_worker_client != NULL && !auth_worker_client->error_sent) { o_stream_nsend_str(auth_worker_client->output, "ERROR\n"); auth_worker_client->error_sent = TRUE; } auth_worker_refresh_proctitle(""); } void auth_worker_client_send_success(void) { auth_worker_client_error = FALSE; if (auth_worker_client == NULL) return; if (auth_worker_client->error_sent) { o_stream_nsend_str(auth_worker_client->output, "SUCCESS\n"); auth_worker_client->error_sent = FALSE; } if (auth_worker_client->io != NULL) auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); } void auth_worker_client_send_shutdown(void) { if (auth_worker_client != NULL) o_stream_nsend_str(auth_worker_client->output, "SHUTDOWN\n"); auth_worker_refresh_proctitle(CLIENT_STATE_STOP); }