diff options
Diffstat (limited to 'src/lib-auth/auth-master.c')
-rw-r--r-- | src/lib-auth/auth-master.c | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/src/lib-auth/auth-master.c b/src/lib-auth/auth-master.c new file mode 100644 index 0000000..57cf8d2 --- /dev/null +++ b/src/lib-auth/auth-master.c @@ -0,0 +1,1030 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "lib-signals.h" +#include "array.h" +#include "ioloop.h" +#include "eacces-error.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "strescape.h" +#include "connection.h" +#include "master-interface.h" +#include "auth-client-private.h" +#include "auth-master.h" + +#include <unistd.h> + +#define AUTH_PROTOCOL_MAJOR 1 +#define AUTH_PROTOCOL_MINOR 0 + +#define AUTH_MASTER_IDLE_SECS 60 + +#define MAX_INBUF_SIZE 8192 +#define MAX_OUTBUF_SIZE 1024 + +struct auth_master_connection { + struct connection conn; + struct connection_list *clist; + struct event *event_parent, *event; + + char *auth_socket_path; + enum auth_master_flags flags; + + struct ioloop *ioloop, *prev_ioloop; + struct timeout *to; + + unsigned int request_counter; + + bool (*reply_callback)(const char *cmd, const char *const *args, + void *context); + void *reply_context; + + unsigned int timeout_msecs; + + bool connected:1; + bool sent_handshake:1; + bool aborted:1; +}; + +struct auth_master_lookup_ctx { + struct auth_master_connection *conn; + const char *user; + const char *expected_reply; + int return_value; + + pool_t pool; + const char **fields; +}; + +struct auth_master_user_list_ctx { + struct auth_master_connection *conn; + string_t *username; + bool finished; + bool failed; +}; + +static void auth_master_connected(struct connection *_conn, bool success); +static int +auth_master_input_args(struct connection *_conn, const char *const *args); +static int +auth_master_handshake_line(struct connection *_conn, const char *line); +static int auth_master_input_line(struct connection *_conn, const char *line); +static void auth_master_destroy(struct connection *_conn); + +static const struct connection_vfuncs auth_master_vfuncs = { + .destroy = auth_master_destroy, + .handshake_line = auth_master_handshake_line, + .input_args = auth_master_input_args, + .input_line = auth_master_input_line, + .client_connected = auth_master_connected, +}; + +static const struct connection_settings auth_master_set = { + .dont_send_version = TRUE, + .service_name_in = "auth-master", + .service_name_out = "auth-master", + .major_version = AUTH_PROTOCOL_MAJOR, + .minor_version = AUTH_PROTOCOL_MINOR, + .unix_client_connect_msecs = 1000, + .input_max_size = MAX_INBUF_SIZE, + .output_max_size = MAX_OUTBUF_SIZE, + .client = TRUE, +}; + +struct auth_master_connection * +auth_master_init(const char *auth_socket_path, enum auth_master_flags flags) +{ + struct auth_master_connection *conn; + + conn = i_new(struct auth_master_connection, 1); + conn->auth_socket_path = i_strdup(auth_socket_path); + conn->flags = flags; + conn->timeout_msecs = 1000*MASTER_AUTH_LOOKUP_TIMEOUT_SECS; + conn->clist = connection_list_init(&auth_master_set, + &auth_master_vfuncs); + + conn->event_parent = conn->event = event_create(NULL); + event_add_category(conn->event_parent, &event_category_auth_client); + event_set_append_log_prefix(conn->event_parent, "auth-master: "); + event_set_forced_debug(conn->event_parent, + HAS_ALL_BITS(flags, AUTH_MASTER_FLAG_DEBUG)); + + conn->conn.event_parent = conn->event_parent; + connection_init_client_unix(conn->clist, &conn->conn, + conn->auth_socket_path); + + return conn; +} + +static void auth_connection_close(struct auth_master_connection *conn) +{ + conn->connected = FALSE; + connection_disconnect(&conn->conn); + + timeout_remove(&conn->to); + + conn->sent_handshake = FALSE; +} + +void auth_master_deinit(struct auth_master_connection **_conn) +{ + struct auth_master_connection *conn = *_conn; + struct connection_list *clist = conn->clist; + + *_conn = NULL; + + auth_connection_close(conn); + connection_deinit(&conn->conn); + connection_list_deinit(&clist); + event_unref(&conn->event_parent); + i_free(conn->auth_socket_path); + i_free(conn); +} + +void auth_master_set_timeout(struct auth_master_connection *conn, + unsigned int msecs) +{ + conn->timeout_msecs = msecs; +} + +const char *auth_master_get_socket_path(struct auth_master_connection *conn) +{ + return conn->auth_socket_path; +} + +static void auth_request_lookup_abort(struct auth_master_connection *conn) +{ + io_loop_stop(conn->ioloop); + conn->aborted = TRUE; +} + +static void auth_master_destroy(struct connection *_conn) +{ + struct auth_master_connection *conn = + container_of(_conn, struct auth_master_connection, conn); + + if (conn->connected) + connection_disconnect(&conn->conn); + conn->connected = FALSE; + conn->sent_handshake = FALSE; + + switch (_conn->disconnect_reason) { + case CONNECTION_DISCONNECT_HANDSHAKE_FAILED: + break; + case CONNECTION_DISCONNECT_BUFFER_FULL: + e_error(conn->event, "BUG: Received more than %d bytes", + MAX_INBUF_SIZE); + break; + default: + if (!conn->aborted) + e_error(conn->event, "Disconnected unexpectedly"); + } + auth_request_lookup_abort(conn); +} + +static int +auth_master_handshake_line(struct connection *_conn, const char *line) +{ + struct auth_master_connection *conn = + container_of(_conn, struct auth_master_connection, conn); + const char *const *tmp; + unsigned int major_version, minor_version; + + tmp = t_strsplit_tabescaped(line); + if (strcmp(tmp[0], "VERSION") == 0 && + tmp[1] != NULL && tmp[2] != NULL) { + if (str_to_uint(tmp[1], &major_version) < 0 || + str_to_uint(tmp[2], &minor_version) < 0) { + e_error(conn->event, + "Auth server sent invalid version line: %s", + line); + auth_request_lookup_abort(conn); + return -1; + } + + if (connection_verify_version(_conn, "auth-master", + major_version, + minor_version) < 0) { + auth_request_lookup_abort(conn); + return -1; + } + } else if (strcmp(tmp[0], "SPID") == 0) { + return 1; + } + return 0; +} + +static int +parse_reply(struct auth_master_lookup_ctx *ctx, const char *cmd, + const char *const *args) +{ + struct auth_master_connection *conn = ctx->conn; + + if (strcmp(cmd, ctx->expected_reply) == 0) + return 1; + if (strcmp(cmd, "NOTFOUND") == 0) + return 0; + if (strcmp(cmd, "FAIL") == 0) { + if (*args == NULL) { + e_error(conn->event, "Auth %s lookup failed", + ctx->expected_reply); + } else { + e_debug(conn->event, + "Auth %s lookup returned temporary failure: %s", + ctx->expected_reply, *args); + } + return -2; + } + e_error(conn->event, "Unknown reply: %s", cmd); + return -1; +} + +static const char *const *args_hide_passwords(const char *const *args) +{ + ARRAY_TYPE(const_string) new_args; + const char *p, *p2; + unsigned int i; + + /* if there are any keys that contain "pass" string */ + for (i = 0; args[i] != NULL; i++) { + p = strstr(args[i], "pass"); + if (p != NULL && p < strchr(args[i], '=')) + break; + } + if (args[i] == NULL) + return args; + + /* there are. replace their values with <hidden> */ + t_array_init(&new_args, i + 16); + array_append(&new_args, args, i); + for (; args[i] != NULL; i++) { + p = strstr(args[i], "pass"); + p2 = strchr(args[i], '='); + if (p != NULL && p < p2) { + p = t_strconcat(t_strdup_until(args[i], p2), + "=<hidden>", NULL); + array_push_back(&new_args, &p); + } else { + array_push_back(&new_args, &args[i]); + } + } + array_append_zero(&new_args); + return array_front(&new_args); +} + +static bool auth_lookup_reply_callback(const char *cmd, const char *const *args, + void *context) +{ + struct auth_master_lookup_ctx *ctx = context; + unsigned int i, len; + + io_loop_stop(ctx->conn->ioloop); + + ctx->return_value = parse_reply(ctx, cmd, args); + + len = str_array_length(args); + i_assert(*args != NULL || len == 0); /* for static analyzer */ + if (ctx->return_value >= 0) { + ctx->fields = p_new(ctx->pool, const char *, len + 1); + for (i = 0; i < len; i++) + ctx->fields[i] = p_strdup(ctx->pool, args[i]); + } else { + /* put the reason string into first field */ + ctx->fields = p_new(ctx->pool, const char *, 2); + for (i = 0; i < len; i++) { + if (str_begins(args[i], "reason=")) { + ctx->fields[0] = + p_strdup(ctx->pool, args[i] + 7); + break; + } + } + } + args = args_hide_passwords(args); + e_debug(ctx->conn->event, "auth %s input: %s", + ctx->expected_reply, t_strarray_join(args, " ")); + return TRUE; +} + +static int +auth_master_input_args(struct connection *_conn, const char *const *args) +{ + struct auth_master_connection *conn = + container_of(_conn, struct auth_master_connection, conn); + const char *const *in_args = args; + const char *cmd, *id, *wanted_id; + + cmd = *args; args++; + if (*args == NULL) + id = ""; + else { + id = *args; + args++; + } + + wanted_id = dec2str(conn->request_counter); + if (strcmp(id, wanted_id) == 0) { + return (conn->reply_callback(cmd, args, conn->reply_context) ? + 0 : 1); + } + + if (strcmp(cmd, "CUID") == 0) { + e_error(conn->event, "%s is an auth client socket. " + "It should be a master socket.", + conn->auth_socket_path); + } else { + e_error(conn->event, "BUG: Unexpected input: %s", + t_strarray_join(in_args, "\t")); + } + auth_request_lookup_abort(conn); + return -1; +} + +static int auth_master_input_line(struct connection *_conn, const char *line) +{ + struct auth_master_connection *conn = + container_of(_conn, struct auth_master_connection, conn); + int ret; + + ret = connection_input_line_default(_conn, line); + return (io_loop_is_running(conn->ioloop) ? ret : 0); +} + +static void auth_master_connected(struct connection *_conn, bool success) +{ + struct auth_master_connection *conn = + container_of(_conn, struct auth_master_connection, conn); + + /* Cannot get here unless connect() was successful */ + i_assert(success); + + conn->connected = TRUE; +} + +static int auth_master_connect(struct auth_master_connection *conn) +{ + i_assert(!conn->connected); + + if (conn->ioloop != NULL) + connection_switch_ioloop_to(&conn->conn, conn->ioloop); + if (connection_client_connect(&conn->conn) < 0) { + if (errno == EACCES) { + e_error(conn->event, + "%s", eacces_error_get("connect", + conn->auth_socket_path)); + } else { + e_error(conn->event, "connect(%s) failed: %m", + conn->auth_socket_path); + } + return -1; + } + + connection_input_halt(&conn->conn); + return 0; +} + +static void auth_request_timeout(struct auth_master_connection *conn) +{ + if (!conn->conn.handshake_received) + e_error(conn->event, "Connecting timed out"); + else + e_error(conn->event, "Request timed out"); + auth_request_lookup_abort(conn); +} + +static void auth_idle_timeout(struct auth_master_connection *conn) +{ + auth_connection_close(conn); +} + +static void auth_master_set_io(struct auth_master_connection *conn) +{ + if (conn->ioloop != NULL) + return; + + timeout_remove(&conn->to); + + conn->prev_ioloop = current_ioloop; + conn->ioloop = io_loop_create(); + connection_switch_ioloop_to(&conn->conn, conn->ioloop); + if (conn->connected) + connection_input_resume(&conn->conn); + + conn->to = timeout_add_to(conn->ioloop, conn->timeout_msecs, + auth_request_timeout, conn); +} + +static void auth_master_unset_io(struct auth_master_connection *conn) +{ + if (conn->prev_ioloop != NULL) { + io_loop_set_current(conn->prev_ioloop); + } + if (conn->ioloop != NULL) { + io_loop_set_current(conn->ioloop); + connection_switch_ioloop_to(&conn->conn, conn->ioloop); + connection_input_halt(&conn->conn); + timeout_remove(&conn->to); + io_loop_destroy(&conn->ioloop); + } + + if ((conn->flags & AUTH_MASTER_FLAG_NO_IDLE_TIMEOUT) == 0) { + if (conn->prev_ioloop == NULL) + auth_connection_close(conn); + else { + i_assert(conn->to == NULL); + conn->to = timeout_add(1000*AUTH_MASTER_IDLE_SECS, + auth_idle_timeout, conn); + } + } +} + +static bool is_valid_string(const char *str) +{ + const char *p; + + /* make sure we're not sending any characters that have a special + meaning. */ + for (p = str; *p != '\0'; p++) { + if (*p == '\t' || *p == '\n' || *p == '\r') + return FALSE; + } + return TRUE; +} + +static int auth_master_run_cmd_pre(struct auth_master_connection *conn, + const char *cmd) +{ + auth_master_set_io(conn); + + if (!conn->connected) { + if (auth_master_connect(conn) < 0) { + auth_master_unset_io(conn); + return -1; + } + i_assert(conn->connected); + connection_input_resume(&conn->conn); + } + + o_stream_cork(conn->conn.output); + if (!conn->sent_handshake) { + const struct connection_settings *set = &conn->conn.list->set; + + o_stream_nsend_str(conn->conn.output, + t_strdup_printf("VERSION\t%u\t%u\n", + set->major_version, + set->minor_version)); + conn->sent_handshake = TRUE; + } + + o_stream_nsend_str(conn->conn.output, cmd); + o_stream_uncork(conn->conn.output); + + if (o_stream_flush(conn->conn.output) < 0) { + e_error(conn->event, "write(auth socket) failed: %s", + o_stream_get_error(conn->conn.output)); + auth_master_unset_io(conn); + auth_connection_close(conn); + return -1; + } + return 0; +} + +static int auth_master_run_cmd_post(struct auth_master_connection *conn) +{ + auth_master_unset_io(conn); + if (conn->aborted) { + conn->aborted = FALSE; + auth_connection_close(conn); + return -1; + } + return 0; +} + +static int auth_master_run_cmd(struct auth_master_connection *conn, + const char *cmd) +{ + if (auth_master_run_cmd_pre(conn, cmd) < 0) + return -1; + io_loop_run(conn->ioloop); + return auth_master_run_cmd_post(conn); +} + +static unsigned int +auth_master_next_request_id(struct auth_master_connection *conn) +{ + if (++conn->request_counter == 0) { + /* avoid zero */ + conn->request_counter++; + } + return conn->request_counter; +} + +void auth_user_info_export(string_t *str, const struct auth_user_info *info) +{ + const char *const *fieldp; + + if (info->service != NULL) { + str_append(str, "\tservice="); + str_append(str, info->service); + } + if (info->session_id != NULL) { + str_append(str, "\tsession="); + str_append_tabescaped(str, info->session_id); + } + if (info->local_name != NULL) { + str_append(str, "\tlocal_name="); + str_append_tabescaped(str, info->local_name); + } + if (info->local_ip.family != 0) + str_printfa(str, "\tlip=%s", net_ip2addr(&info->local_ip)); + if (info->local_port != 0) + str_printfa(str, "\tlport=%d", info->local_port); + if (info->remote_ip.family != 0) + str_printfa(str, "\trip=%s", net_ip2addr(&info->remote_ip)); + if (info->remote_port != 0) + str_printfa(str, "\trport=%d", info->remote_port); + if (info->real_remote_ip.family != 0 && + !net_ip_compare(&info->real_remote_ip, &info->remote_ip)) + str_printfa(str, "\treal_rip=%s", net_ip2addr(&info->real_remote_ip)); + if (info->real_local_ip.family != 0 && + !net_ip_compare(&info->real_local_ip, &info->local_ip)) + str_printfa(str, "\treal_lip=%s", net_ip2addr(&info->real_local_ip)); + if (info->real_local_port != 0 && + info->real_local_port != info->local_port) + str_printfa(str, "\treal_lport=%d", info->real_local_port); + if (info->real_remote_port != 0 && + info->real_remote_port != info->remote_port) + str_printfa(str, "\treal_rport=%d", info->real_remote_port); + if (info->debug) + str_append(str, "\tdebug"); + if (info->forward_fields != NULL && + *info->forward_fields != '\0') { + str_append(str, "\tforward_fields="); + str_append_tabescaped(str, info->forward_fields); + } + if (array_is_created(&info->extra_fields)) { + array_foreach(&info->extra_fields, fieldp) { + str_append_c(str, '\t'); + str_append_tabescaped(str, *fieldp); + } + } +} + +static void +auth_master_event_create(struct auth_master_connection *conn, + const char *prefix) +{ + i_assert(conn->event == conn->event_parent); + conn->event = event_create(conn->event_parent); + event_set_append_log_prefix(conn->event, prefix); +} + +static void +auth_master_user_event_create(struct auth_master_connection *conn, + const char *prefix, + const struct auth_user_info *info) +{ + auth_master_event_create(conn, prefix); + + if (info != NULL) { + if (info->service != NULL) + event_add_str(conn->event, "service", info->service); + if (info->session_id != NULL) + event_add_str(conn->event, "session", info->session_id); + if (info->local_name != NULL) + event_add_str(conn->event, "local_name", info->local_name); + if (info->local_ip.family != 0) { + event_add_str(conn->event, "local_ip", + net_ip2addr(&info->local_ip)); + } + if (info->local_port != 0) { + event_add_int(conn->event, "local_port", + info->local_port); + } + if (info->remote_ip.family != 0) { + event_add_str(conn->event, "remote_ip", + net_ip2addr(&info->remote_ip)); + } + if (info->remote_port != 0) { + event_add_int(conn->event, "remote_port", + info->remote_port); + } + if (info->real_local_ip.family != 0) + event_add_str(conn->event, "real_local_ip", + net_ip2addr(&info->real_local_ip)); + if (info->real_remote_ip.family != 0) + event_add_str(conn->event, "real_remote_ip", + net_ip2addr(&info->real_remote_ip)); + if (info->real_local_port != 0) + event_add_int(conn->event, "real_local_port", + info->real_local_port); + if (info->real_remote_port != 0) + event_add_int(conn->event, "real_remote_port", + info->real_remote_port); + } +} + +static void +auth_master_event_finish(struct auth_master_connection *conn) +{ + i_assert(conn->event != conn->event_parent); + event_unref(&conn->event); + conn->event = conn->event_parent; +} + +int auth_master_user_lookup(struct auth_master_connection *conn, + const char *user, const struct auth_user_info *info, + pool_t pool, const char **username_r, + const char *const **fields_r) +{ + struct auth_master_lookup_ctx ctx; + string_t *str; + + if (!is_valid_string(user) || !is_valid_string(info->service)) { + /* non-allowed characters, the user can't exist */ + *username_r = NULL; + *fields_r = NULL; + return 0; + } + + i_zero(&ctx); + ctx.conn = conn; + ctx.return_value = -1; + ctx.pool = pool; + ctx.expected_reply = "USER"; + ctx.user = user; + + conn->reply_callback = auth_lookup_reply_callback; + conn->reply_context = &ctx; + + str = t_str_new(128); + str_printfa(str, "USER\t%u\t%s", + auth_master_next_request_id(conn), user); + auth_user_info_export(str, info); + str_append_c(str, '\n'); + + auth_master_user_event_create( + conn, t_strdup_printf("userdb lookup(%s): ", user), info); + event_add_str(conn->event, "user", user); + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_lookup_started"); + e_debug(e->event(), "Started userdb lookup"); + + (void)auth_master_run_cmd(conn, str_c(str)); + + if (ctx.return_value <= 0 || ctx.fields[0] == NULL) { + *username_r = NULL; + *fields_r = ctx.fields != NULL ? ctx.fields : + p_new(pool, const char *, 1); + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_lookup_finished"); + + if (ctx.return_value > 0) { + e->add_str("error", "Lookup didn't return username"); + e_error(e->event(), "Userdb lookup failed: " + "Lookup didn't return username"); + ctx.return_value = -2; + } else if ((*fields_r)[0] == NULL) { + e->add_str("error", "Lookup failed"); + e_debug(e->event(), "Userdb lookup failed"); + } else { + e->add_str("error", (*fields_r)[0]); + e_debug(e->event(), "Userdb lookup failed: %s", + (*fields_r)[0]); + } + } else { + *username_r = ctx.fields[0]; + *fields_r = ctx.fields + 1; + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_lookup_finished"); + e_debug(e->event(), "Finished userdb lookup (username=%s %s)", + *username_r, t_strarray_join(*fields_r, " ")); + } + auth_master_event_finish(conn); + + conn->reply_context = NULL; + return ctx.return_value; +} + +void auth_user_fields_parse(const char *const *fields, pool_t pool, + struct auth_user_reply *reply_r) +{ + i_zero(reply_r); + reply_r->uid = (uid_t)-1; + reply_r->gid = (gid_t)-1; + p_array_init(&reply_r->extra_fields, pool, 64); + + for (; *fields != NULL; fields++) { + if (str_begins(*fields, "uid=")) { + if (str_to_uid(*fields + 4, &reply_r->uid) < 0) + i_error("Invalid uid in reply"); + } else if (str_begins(*fields, "gid=")) { + if (str_to_gid(*fields + 4, &reply_r->gid) < 0) + i_error("Invalid gid in reply"); + } else if (str_begins(*fields, "home=")) + reply_r->home = p_strdup(pool, *fields + 5); + else if (str_begins(*fields, "chroot=")) + reply_r->chroot = p_strdup(pool, *fields + 7); + else if (strcmp(*fields, "anonymous") == 0) + reply_r->anonymous = TRUE; + else { + const char *field = p_strdup(pool, *fields); + array_push_back(&reply_r->extra_fields, &field); + } + } +} + +int auth_master_pass_lookup(struct auth_master_connection *conn, + const char *user, const struct auth_user_info *info, + pool_t pool, const char *const **fields_r) +{ + struct auth_master_lookup_ctx ctx; + string_t *str; + + if (!is_valid_string(user) || !is_valid_string(info->service)) { + /* non-allowed characters, the user can't exist */ + *fields_r = NULL; + return 0; + } + + i_zero(&ctx); + ctx.conn = conn; + ctx.return_value = -1; + ctx.pool = pool; + ctx.expected_reply = "PASS"; + ctx.user = user; + + conn->reply_callback = auth_lookup_reply_callback; + conn->reply_context = &ctx; + + str = t_str_new(128); + str_printfa(str, "PASS\t%u\t%s", + auth_master_next_request_id(conn), user); + auth_user_info_export(str, info); + str_append_c(str, '\n'); + + auth_master_user_event_create( + conn, t_strdup_printf("passdb lookup(%s): ", user), info); + event_add_str(conn->event, "user", user); + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_passdb_lookup_started"); + e_debug(e->event(), "Started passdb lookup"); + + (void)auth_master_run_cmd(conn, str_c(str)); + + *fields_r = ctx.fields != NULL ? ctx.fields : + p_new(pool, const char *, 1); + + if (ctx.return_value <= 0) { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_passdb_lookup_finished"); + if ((*fields_r)[0] == NULL) { + e->add_str("error", "Lookup failed"); + e_debug(e->event(), "Passdb lookup failed"); + } else { + e->add_str("error", (*fields_r)[0]); + e_debug(e->event(), "Passdb lookup failed: %s", + (*fields_r)[0]); + } + } else { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_passdb_lookup_finished"); + e_debug(e->event(), "Finished passdb lookup (%s)", + t_strarray_join(*fields_r, " ")); + } + auth_master_event_finish(conn); + + conn->reply_context = NULL; + return ctx.return_value; +} + +struct auth_master_cache_ctx { + struct auth_master_connection *conn; + unsigned int count; + bool failed; +}; + +static bool +auth_cache_flush_reply_callback(const char *cmd, const char *const *args, + void *context) +{ + struct auth_master_cache_ctx *ctx = context; + + if (strcmp(cmd, "OK") != 0) + ctx->failed = TRUE; + else if (args[0] == NULL || str_to_uint(args[0], &ctx->count) < 0) + ctx->failed = TRUE; + + io_loop_stop(ctx->conn->ioloop); + return TRUE; +} + +int auth_master_cache_flush(struct auth_master_connection *conn, + const char *const *users, unsigned int *count_r) +{ + struct auth_master_cache_ctx ctx; + string_t *str; + + i_zero(&ctx); + ctx.conn = conn; + + conn->reply_callback = auth_cache_flush_reply_callback; + conn->reply_context = &ctx; + + str = t_str_new(128); + str_printfa(str, "CACHE-FLUSH\t%u", auth_master_next_request_id(conn)); + if (users != NULL) { + for (; *users != NULL; users++) { + str_append_c(str, '\t'); + str_append_tabescaped(str, *users); + } + } + str_append_c(str, '\n'); + + auth_master_event_create(conn, "auth cache flush: "); + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_cache_flush_started"); + e_debug(e->event(), "Started cache flush"); + + (void)auth_master_run_cmd(conn, str_c(str)); + + if (ctx.failed) { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_cache_flush_finished"); + e->add_str("error", "Cache flush failed"); + e_debug(e->event(), "Cache flush failed"); + } else { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_cache_flush_finished"); + e_debug(e->event(), "Finished cache flush"); + } + auth_master_event_finish(conn); + + conn->reply_context = NULL; + *count_r = ctx.count; + return ctx.failed ? -1 : 0; +} + +static bool +auth_user_list_reply_callback(const char *cmd, const char *const *args, + void *context) +{ + struct auth_master_user_list_ctx *ctx = context; + struct auth_master_connection *conn = ctx->conn; + + timeout_reset(ctx->conn->to); + io_loop_stop(ctx->conn->ioloop); + + if (strcmp(cmd, "DONE") == 0) { + if (args[0] != NULL && strcmp(args[0], "fail") == 0) { + e_error(conn->event, "User listing returned failure"); + ctx->failed = TRUE; + } + ctx->finished = TRUE; + } else if (strcmp(cmd, "LIST") == 0 && args[0] != NULL) { + /* we'll just read all the users into memory. otherwise we'd + have to use a separate connection for listing and there's + a higher chance of a failure since the connection could be + open to dovecot-auth for a long time. */ + str_append(ctx->username, args[0]); + } else { + e_error(conn->event, "User listing returned invalid input"); + ctx->failed = TRUE; + } + return FALSE; +} + +struct auth_master_user_list_ctx * +auth_master_user_list_init(struct auth_master_connection *conn, + const char *user_mask, + const struct auth_user_info *info) +{ + struct auth_master_user_list_ctx *ctx; + string_t *str; + + ctx = i_new(struct auth_master_user_list_ctx, 1); + ctx->conn = conn; + ctx->username = str_new(default_pool, 128); + + conn->reply_callback = auth_user_list_reply_callback; + conn->reply_context = ctx; + + str = t_str_new(128); + str_printfa(str, "LIST\t%u", + auth_master_next_request_id(conn)); + if (*user_mask != '\0') + str_printfa(str, "\tuser=%s", user_mask); + if (info != NULL) + auth_user_info_export(str, info); + str_append_c(str, '\n'); + + auth_master_user_event_create(conn, "userdb list: ", info); + event_add_str(conn->event," user_mask", user_mask); + + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_list_started"); + e_debug(e->event(), "Started listing users (user_mask=%s)", user_mask); + + if (auth_master_run_cmd_pre(conn, str_c(str)) < 0) + ctx->failed = TRUE; + if (conn->prev_ioloop != NULL) + io_loop_set_current(conn->prev_ioloop); + + return ctx; +} + +static const char * +auth_master_user_do_list_next(struct auth_master_user_list_ctx *ctx) +{ + struct auth_master_connection *conn = ctx->conn; + const char *line; + + if (!conn->connected) + return NULL; + + str_truncate(ctx->username, 0); + + /* try to read already buffered input */ + line = i_stream_next_line(conn->conn.input); + if (line != NULL) { + T_BEGIN { + conn->conn.v.input_line(&conn->conn, line); + } T_END; + } + if (conn->aborted) + ctx->failed = TRUE; + if (ctx->finished || ctx->failed) + return NULL; + if (str_len(ctx->username) > 0) + return str_c(ctx->username); + + /* wait for more data */ + io_loop_set_current(conn->ioloop); + i_stream_set_input_pending(conn->conn.input, TRUE); + io_loop_run(conn->ioloop); + io_loop_set_current(conn->prev_ioloop); + + if (conn->aborted) + ctx->failed = TRUE; + if (ctx->finished || ctx->failed) + return NULL; + return str_c(ctx->username); +} + +const char *auth_master_user_list_next(struct auth_master_user_list_ctx *ctx) +{ + struct auth_master_connection *conn = ctx->conn; + const char *username; + + username = auth_master_user_do_list_next(ctx); + if (username == NULL) + return NULL; + + e_debug(conn->event, "Returned username: %s", username); + return username; +} + +int auth_master_user_list_deinit(struct auth_master_user_list_ctx **_ctx) +{ + struct auth_master_user_list_ctx *ctx = *_ctx; + struct auth_master_connection *conn = ctx->conn; + int ret = ctx->failed ? -1 : 0; + + *_ctx = NULL; + auth_master_run_cmd_post(ctx->conn); + + if (ret < 0) { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_list_finished"); + e->add_str("error", "Listing users failed"); + e_debug(e->event(), "Listing users failed"); + } else { + struct event_passthrough *e = + event_create_passthrough(conn->event)-> + set_name("auth_client_userdb_list_finished"); + e_debug(e->event(), "Finished listing users"); + } + auth_master_event_finish(conn); + + str_free(&ctx->username); + i_free(ctx); + return ret; +} |