summaryrefslogtreecommitdiffstats
path: root/src/auth/auth-worker-client.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/auth/auth-worker-client.c
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/auth/auth-worker-client.c')
-rw-r--r--src/auth/auth-worker-client.c972
1 files changed, 972 insertions, 0 deletions
diff --git a/src/auth/auth-worker-client.c b/src/auth/auth-worker-client.c
new file mode 100644
index 0000000..5405144
--- /dev/null
+++ b/src/auth/auth-worker-client.c
@@ -0,0 +1,972 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "base64.h"
+#include "connection.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"
+
+static unsigned int auth_worker_max_service_count = 0;
+static unsigned int auth_worker_service_count = 0;
+
+struct auth_worker_client {
+ struct connection conn;
+ int refcount;
+
+ struct auth *auth;
+ struct event *event;
+ time_t cmd_start;
+
+ bool error_sent:1;
+ bool destroyed:1;
+};
+
+struct auth_worker_command {
+ struct auth_worker_client *client;
+ struct event *event;
+};
+
+struct auth_worker_list_context {
+ struct auth_worker_command *cmd;
+ struct auth_worker_client *client;
+ struct auth_request *auth_request;
+ struct userdb_iterate_context *iter;
+ bool sending, sent, done;
+};
+
+static struct connection_list *clients = NULL;
+static bool auth_worker_client_error = FALSE;
+
+static int auth_worker_output(struct auth_worker_client *client);
+static void auth_worker_client_destroy(struct connection *conn);
+static void auth_worker_client_unref(struct auth_worker_client **_client);
+
+void auth_worker_set_max_service_count(unsigned int count)
+{
+ auth_worker_max_service_count = count;
+}
+
+static struct auth_worker_client *auth_worker_get_client(void)
+{
+ if (!auth_worker_has_client())
+ return NULL;
+ struct auth_worker_client *client =
+ container_of(clients->connections, struct auth_worker_client, conn);
+ return 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_has_client())
+ 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->conn.output) >=
+ OUTBUF_THROTTLE_SIZE) {
+ /* stop reading new requests until client has read the pending
+ replies. */
+ connection_input_halt(&client->conn);
+ }
+}
+
+static void
+auth_worker_request_finished_full(struct auth_worker_command *cmd,
+ const char *error, bool log_as_error)
+{
+ event_set_name(cmd->event, "auth_worker_request_finished");
+ if (error != NULL) {
+ event_add_str(cmd->event, "error", error);
+ if (log_as_error)
+ e_error(cmd->event, "Finished: %s", error);
+ else
+ e_debug(cmd->event, "Finished: %s", error);
+ } else {
+ e_debug(cmd->event, "Finished");
+ }
+ auth_worker_client_check_throttle(cmd->client);
+ auth_worker_client_unref(&cmd->client);
+ event_unref(&cmd->event);
+ i_free(cmd);
+
+ auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
+}
+
+static void auth_worker_request_finished(struct auth_worker_command *cmd,
+ const char *error)
+{
+ auth_worker_request_finished_full(cmd, error, FALSE);
+}
+
+static void auth_worker_request_finished_bug(struct auth_worker_command *cmd,
+ const char *error)
+{
+ auth_worker_request_finished_full(cmd, error, TRUE);
+}
+
+bool auth_worker_auth_request_new(struct auth_worker_command *cmd, unsigned int id,
+ const char *const *args, struct auth_request **request_r)
+{
+ struct auth_request *auth_request;
+ const char *key, *value;
+
+ auth_request = auth_request_new_dummy(cmd->event);
+
+ cmd->client->refcount++;
+ auth_request->context = cmd;
+ auth_request->id = id;
+
+ for (; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value == NULL)
+ (void)auth_request_import(auth_request, *args, "");
+ else {
+ key = t_strdup_until(*args, value++);
+ (void)auth_request_import(auth_request, key, value);
+ }
+ }
+ if (auth_request->fields.user == NULL ||
+ auth_request->fields.service == NULL) {
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ /* reset changed-fields, so we'll export only the ones that were
+ changed by this lookup. */
+ auth_fields_snapshot(auth_request->fields.extra_fields);
+ if (auth_request->fields.userdb_reply != NULL)
+ auth_fields_snapshot(auth_request->fields.userdb_reply);
+
+ auth_request_init(auth_request);
+ *request_r = auth_request;
+
+ return TRUE;
+}
+
+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->conn.output, "RESTART\n");
+ o_stream_nsend(client->conn.output, str_data(str), str_len(str));
+ if (o_stream_flush(client->conn.output) < 0 && request != NULL &&
+ cmd_duration > AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS) {
+ p = i_strchr_to_next(str_c(str), '\t');
+ p = p == NULL ? "BUG" : t_strcut(p, '\t');
+
+ e_warning(client->conn.event, "Auth master disconnected us while handling "
+ "request for %s for %ld secs (result=%s)",
+ request->fields.user, (long)cmd_duration, p);
+ }
+}
+
+static void
+reply_append_extra_fields(string_t *str, struct auth_request *request)
+{
+ if (!auth_fields_is_empty(request->fields.extra_fields)) {
+ str_append_c(str, '\t');
+ /* export only the fields changed by this lookup, so the
+ changed-flag gets preserved correctly on the master side as
+ well. */
+ auth_fields_append(request->fields.extra_fields, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ }
+ if (request->fields.userdb_reply != NULL &&
+ auth_fields_is_empty(request->fields.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_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+ const char *error = NULL;
+ 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->fields.extra_fields, "noauthenticate"))
+ str_append(str, "NEXT");
+ else
+ str_append(str, "OK");
+ else {
+ str_printfa(str, "FAIL\t%d", result);
+ error = passdb_result_to_string(result);
+ }
+ if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
+ str_append_c(str, '\t');
+ if (request->user_changed_by_lookup)
+ str_append_tabescaped(str, request->fields.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_passdb_lookup_end(request, result);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&request);
+}
+
+static bool
+auth_worker_handle_passv(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* 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) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSV";
+ return FALSE;
+ }
+ password = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSV";
+ return FALSE;
+ }
+ auth_request->mech_password =
+ p_strdup(auth_request->pool, password);
+
+ 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) {
+ *error_r = "BUG: PASSV had invalid passdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+ }
+
+ auth_request->passdb = passdb;
+ auth_request_passdb_lookup_begin(auth_request);
+ passdb->passdb->iface.
+ verify_plain(auth_request, password, verify_plain_callback);
+ return TRUE;
+}
+
+static bool
+auth_worker_handle_passw(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ struct auth_worker_client *client = cmd->client;
+ struct auth_request *request;
+ string_t *str;
+ const char *password;
+ const char *crypted, *scheme, *error;
+ unsigned int passdb_id;
+ enum passdb_result ret;
+
+ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL ||
+ args[2] == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSW";
+ return FALSE;
+ }
+ password = args[1];
+ crypted = args[2];
+ scheme = password_get_scheme(&crypted);
+ if (scheme == NULL) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSW (scheme is NULL)";
+ return FALSE;
+ }
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 3, &request)) {
+ *error_r = "BUG: PASSW had missing parameters";
+ return FALSE;
+ }
+ request->mech_password =
+ p_strdup(request->pool, password);
+
+ ret = auth_request_password_verify(request, password,
+ crypted, scheme, "cache");
+ str = t_str_new(128);
+ str_printfa(str, "%u\t", request->id);
+
+ if (ret == PASSDB_RESULT_OK) {
+ str_printfa(str, "OK\t\t");
+ error = NULL;
+ } else {
+ str_printfa(str, "FAIL\t%d", ret);
+ error = passdb_result_to_string(ret);
+ }
+
+ str_append_c(str, '\n');
+ auth_worker_send_reply(client, request, str);
+
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&request);
+ 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_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+ 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");
+ if (request->user_changed_by_lookup)
+ str_append_tabescaped(str, request->fields.user);
+ str_append_c(str, '\t');
+ if (request->wanted_credentials_scheme[0] != '\0') {
+ str_printfa(str, "{%s.b64}", request->wanted_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_passdb_lookup_end(request, result);
+ auth_request_unref(&request);
+ auth_worker_request_finished(cmd, NULL);
+}
+
+static bool
+auth_worker_handle_passl(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* 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) {
+ *error_r = "BUG: Auth worker server sent us invalid PASSL";
+ return FALSE;
+ }
+ scheme = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: PASSL had missing parameters";
+ return FALSE;
+ }
+ auth_request->wanted_credentials_scheme =
+ p_strdup(auth_request->pool, scheme);
+
+ while (auth_request->passdb->passdb->id != passdb_id) {
+ auth_request->passdb = auth_request->passdb->next;
+ if (auth_request->passdb == NULL) {
+ *error_r = "BUG: PASSL had invalid passdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+ }
+
+ if (auth_request->passdb->passdb->iface.lookup_credentials == NULL) {
+ *error_r = "BUG: PASSL lookup not supported by given passdb";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ auth_request->prefer_plain_credentials = TRUE;
+ auth_request_passdb_lookup_begin(auth_request);
+ 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_command *cmd = request->context;
+ struct auth_worker_client *client = cmd->client;
+
+ 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_worker_request_finished(cmd, success ? NULL :
+ "Failed to set credentials");
+ auth_request_unref(&request);
+}
+
+static bool
+auth_worker_handle_setcred(struct auth_worker_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ 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) {
+ *error_r = "BUG: Auth worker server sent us invalid SETCRED";
+ return FALSE;
+ }
+ creds = args[1];
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) {
+ *error_r = "BUG: SETCRED had missing parameters";
+ return FALSE;
+ }
+
+ while (auth_request->passdb->passdb->id != passdb_id) {
+ auth_request->passdb = auth_request->passdb->next;
+ if (auth_request->passdb == NULL) {
+ *error_r = "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_command *cmd = auth_request->context;
+ struct auth_worker_client *client = cmd->client;
+ const char *error;
+ 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");
+ if (auth_request->user_changed_by_lookup)
+ str_append_tabescaped(str, auth_request->fields.user);
+ str_append_c(str, '\t');
+ /* export only the fields changed by this lookup */
+ auth_fields_append(auth_request->fields.userdb_reply, str,
+ AUTH_FIELD_FLAG_CHANGED,
+ AUTH_FIELD_FLAG_CHANGED);
+ 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_userdb_lookup_end(auth_request, result);
+ error = result == USERDB_RESULT_OK ? NULL :
+ userdb_result_to_string(result);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&auth_request);
+}
+
+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_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ /* lookup user */
+ struct auth_request *auth_request;
+ unsigned int userdb_id;
+
+ /* <userdb id> [<args>] */
+ if (str_to_uint(args[0], &userdb_id) < 0) {
+ *error_r = "BUG: Auth worker server sent us invalid USER";
+ return FALSE;
+ }
+
+ if (!auth_worker_auth_request_new(cmd, id, args + 1, &auth_request)) {
+ *error_r = "BUG: USER had missing parameters";
+ 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) {
+ *error_r = "BUG: USER had invalid userdb ID";
+ auth_request_unref(&auth_request);
+ return FALSE;
+ }
+
+ if (auth_request->fields.userdb_reply == NULL)
+ auth_request_init_userdb_reply(auth_request, TRUE);
+ auth_request_userdb_lookup_begin(auth_request);
+ auth_request->userdb->userdb->iface->
+ lookup(auth_request, lookup_user_callback);
+ return TRUE;
+}
+
+static void
+auth_worker_client_idle_kill(struct connection *conn ATTR_UNUSED)
+{
+ auth_worker_client_send_shutdown();
+}
+
+static void list_iter_deinit(struct auth_worker_list_context *ctx)
+{
+ struct auth_worker_command *cmd = ctx->cmd;
+ struct auth_worker_client *client = ctx->client;
+ const char *error = NULL;
+ string_t *str;
+
+ i_assert(client->conn.io == NULL);
+
+ str = t_str_new(32);
+ if (ctx->auth_request->userdb->userdb->iface->
+ iterate_deinit(ctx->iter) < 0) {
+ error = "Iteration failed";
+ 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);
+
+ connection_input_resume(&client->conn);
+ o_stream_set_flush_callback(client->conn.output, auth_worker_output,
+ client);
+ auth_request_userdb_lookup_end(ctx->auth_request, USERDB_RESULT_OK);
+ auth_worker_request_finished(cmd, error);
+ auth_request_unref(&ctx->auth_request);
+ i_free(ctx);
+}
+
+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->conn.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->conn.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->conn.output) > OUTBUF_THROTTLE_SIZE) {
+ if (o_stream_flush(ctx->client->conn.output) < 0) {
+ ctx->done = TRUE;
+ break;
+ }
+ }
+ } while (ctx->sent &&
+ o_stream_get_buffer_used_size(ctx->client->conn.output) <= OUTBUF_THROTTLE_SIZE);
+ o_stream_uncork(ctx->client->conn.output);
+ ctx->sending = FALSE;
+ if (ctx->done)
+ list_iter_deinit(ctx);
+ else
+ o_stream_set_flush_pending(ctx->client->conn.output, TRUE);
+}
+
+static int auth_worker_list_output(struct auth_worker_list_context *ctx)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(ctx->client->conn.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_command *cmd,
+ unsigned int id, const char *const *args,
+ const char **error_r)
+{
+ struct auth_worker_client *client = cmd->client;
+ struct auth_worker_list_context *ctx;
+ struct auth_userdb *userdb;
+ unsigned int userdb_id;
+
+ if (str_to_uint(args[0], &userdb_id) < 0) {
+ *error_r = "BUG: Auth worker server sent us invalid LIST";
+ return FALSE;
+ }
+
+ userdb = auth_userdb_find_by_id(client->auth->userdbs, userdb_id);
+ if (userdb == NULL) {
+ *error_r = "BUG: LIST had invalid userdb ID";
+ return FALSE;
+ }
+
+ ctx = i_new(struct auth_worker_list_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ if (!auth_worker_auth_request_new(cmd, id, args + 1, &ctx->auth_request)) {
+ *error_r = "BUG: LIST had missing parameters";
+ i_free(ctx);
+ return FALSE;
+ }
+ ctx->auth_request->userdb = userdb;
+
+ connection_input_halt(&ctx->client->conn);
+
+ o_stream_set_flush_callback(ctx->client->conn.output,
+ auth_worker_list_output, ctx);
+ ctx->auth_request->userdb_lookup = TRUE;
+ auth_request_userdb_lookup_begin(ctx->auth_request);
+ 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_verify_db_hash(const char *passdb_hash, const char *userdb_hash)
+{
+ string_t *str = t_str_new(MD5_RESULTLEN*2);
+ unsigned char passdb_md5[MD5_RESULTLEN];
+ unsigned char userdb_md5[MD5_RESULTLEN];
+
+ passdbs_generate_md5(passdb_md5);
+ userdbs_generate_md5(userdb_md5);
+
+ binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5));
+ if (strcmp(str_c(str), passdb_hash) != 0)
+ return FALSE;
+ str_truncate(str, 0);
+ binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5));
+ return strcmp(str_c(str), userdb_hash) == 0;
+};
+
+static int auth_worker_client_handshake_args(struct connection *conn, const char *const *args)
+{
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(conn, args) < 0)
+ return -1;
+ return 0;
+ }
+
+ if (str_array_length(args) < 3 ||
+ strcmp(args[0], "DBHASH") != 0) {
+ e_error(conn->event, "BUG: Invalid input: %s",
+ t_strarray_join(args, "\t"));
+ return -1;
+ }
+
+ if (!auth_worker_verify_db_hash(args[1], args[2])) {
+ e_error(conn->event,
+ "Auth worker sees different passdbs/userdbs "
+ "than auth server. Maybe config just changed "
+ "and this goes away automatically?");
+ return -1;
+ }
+ return 1;
+}
+
+static int
+auth_worker_client_input_args(struct connection *conn, const char *const *args)
+{
+ unsigned int id;
+ bool ret = FALSE;
+ const char *error = NULL;
+ struct auth_worker_command *cmd;
+ struct auth_worker_client *client =
+ container_of(conn, struct auth_worker_client, conn);
+
+ if (str_array_length(args) < 3 ||
+ str_to_uint(args[0], &id) < 0) {
+ e_error(conn->event, "BUG: Invalid input: %s",
+ t_strarray_join(args, "\t"));
+ return -1;
+ }
+
+ io_loop_time_refresh();
+
+ cmd = i_new(struct auth_worker_command, 1);
+ cmd->client = client;
+ cmd->event = event_create(client->conn.event);
+ event_add_category(cmd->event, &event_category_auth);
+ event_add_str(cmd->event, "command", args[1]);
+ event_add_int(cmd->event, "command_id", id);
+ event_set_append_log_prefix(cmd->event, t_strdup_printf("auth-worker<%u>: ", id));
+ client->cmd_start = ioloop_time;
+ client->refcount++;
+ e_debug(cmd->event, "Handling %s request", args[1]);
+
+ /* Check if we have reached service_count */
+ if (auth_worker_max_service_count > 0) {
+ auth_worker_service_count++;
+ if (auth_worker_service_count >= auth_worker_max_service_count)
+ worker_restart_request = TRUE;
+ }
+
+ auth_worker_refresh_proctitle(args[1]);
+ if (strcmp(args[1], "PASSV") == 0)
+ ret = auth_worker_handle_passv(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "PASSL") == 0)
+ ret = auth_worker_handle_passl(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "PASSW") == 0)
+ ret = auth_worker_handle_passw(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "SETCRED") == 0)
+ ret = auth_worker_handle_setcred(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "USER") == 0)
+ ret = auth_worker_handle_user(cmd, id, args + 2, &error);
+ else if (strcmp(args[1], "LIST") == 0)
+ ret = auth_worker_handle_list(cmd, id, args + 2, &error);
+ else {
+ error = t_strdup_printf("BUG: Auth-worker received unknown command: %s",
+ args[1]);
+ }
+
+ i_assert(ret || error != NULL);
+
+ if (!ret) {
+ auth_worker_request_finished_bug(cmd, error);
+ return -1;
+ }
+ auth_worker_client_unref(&client);
+ return 1;
+}
+
+static int auth_worker_output(struct auth_worker_client *client)
+{
+ if (o_stream_flush(client->conn.output) < 0) {
+ auth_worker_client_destroy(&client->conn);
+ return 1;
+ }
+
+ if (o_stream_get_buffer_used_size(client->conn.output) <=
+ OUTBUF_THROTTLE_SIZE/3 && client->conn.io == NULL) {
+ /* allow input again */
+ connection_input_resume(&client->conn);
+ }
+ return 1;
+}
+
+static void auth_worker_client_unref(struct auth_worker_client **_client)
+{
+ struct auth_worker_client *client = *_client;
+ if (client == NULL)
+ return;
+ if (--client->refcount > 0)
+ return;
+
+ /* the connection should've been destroyed before getting here */
+ i_assert(client->destroyed);
+ connection_deinit(&client->conn);
+ i_free(client);
+}
+
+static void auth_worker_client_destroy(struct connection *conn)
+{
+ struct auth_worker_client *client =
+ container_of(conn, struct auth_worker_client, conn);
+
+ i_assert(!client->destroyed);
+ client->destroyed = TRUE;
+ connection_input_halt(conn);
+ i_stream_close(conn->input);
+ o_stream_close(conn->output);
+ net_disconnect(conn->fd_in);
+ conn->fd_out = conn->fd_in = -1;
+ auth_worker_client_unref(&client);
+ master_service_client_connection_destroyed(master_service);
+}
+
+static const struct connection_vfuncs auth_worker_client_v =
+{
+ .input_args = auth_worker_client_input_args,
+ .handshake_args = auth_worker_client_handshake_args,
+ .destroy = auth_worker_client_destroy,
+ .idle_timeout = auth_worker_client_idle_kill,
+};
+
+static const struct connection_settings auth_worker_client_set =
+{
+ .service_name_in = "auth-worker",
+ .service_name_out = "auth-worker",
+ .major_version = AUTH_WORKER_PROTOCOL_MAJOR_VERSION,
+ .minor_version = AUTH_WORKER_PROTOCOL_MINOR_VERSION,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX, /* we use throttling */
+};
+
+struct auth_worker_client *
+auth_worker_client_create(struct auth *auth,
+ const struct master_service_connection *master_conn)
+{
+ struct auth_worker_client *client;
+
+ if (clients == NULL)
+ clients = connection_list_init(&auth_worker_client_set, &auth_worker_client_v);
+
+ client = i_new(struct auth_worker_client, 1);
+ client->refcount = 1;
+ client->auth = auth;
+ client->conn.event_parent = auth_event;
+ client->conn.input_idle_timeout_secs = master_service_get_idle_kill_secs(master_service);
+ connection_init_server(clients, &client->conn, master_conn->name,
+ master_conn->fd, master_conn->fd);
+
+ auth_worker_refresh_proctitle(CLIENT_STATE_HANDSHAKE);
+
+ if (auth_worker_client_error)
+ auth_worker_client_send_error();
+ return client;
+}
+
+void auth_worker_client_send_error(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ auth_worker_client_error = TRUE;
+ if (auth_worker_client != NULL &&
+ !auth_worker_client->error_sent) {
+ o_stream_nsend_str(auth_worker_client->conn.output, "ERROR\n");
+ auth_worker_client->error_sent = TRUE;
+ }
+ auth_worker_refresh_proctitle("");
+}
+
+void auth_worker_client_send_success(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ auth_worker_client_error = FALSE;
+ if (auth_worker_client == NULL)
+ return;
+ if (auth_worker_client->error_sent) {
+ o_stream_nsend_str(auth_worker_client->conn.output,
+ "SUCCESS\n");
+ auth_worker_client->error_sent = FALSE;
+ }
+ if (auth_worker_client->conn.io != NULL)
+ auth_worker_refresh_proctitle(CLIENT_STATE_IDLE);
+}
+
+void auth_worker_client_send_shutdown(void)
+{
+ struct auth_worker_client *auth_worker_client =
+ auth_worker_get_client();
+ if (auth_worker_client != NULL)
+ o_stream_nsend_str(auth_worker_client->conn.output,
+ "SHUTDOWN\n");
+ auth_worker_refresh_proctitle(CLIENT_STATE_STOP);
+}
+
+void auth_worker_connections_destroy_all(void)
+{
+ if (clients == NULL)
+ return;
+ while (clients->connections != NULL)
+ connection_deinit(clients->connections);
+ connection_list_deinit(&clients);
+}
+
+bool auth_worker_has_client(void)
+{
+ return clients != NULL && clients->connections_count > 0;
+}