#define MAX_INBUF_SIZE 8192
#define MAX_OUTBUF_SIZE 1024
+struct auth_master_request {
+ pool_t pool;
+ struct event *event;
+
+ struct auth_master_connection *conn;
+ struct auth_master_request *prev, *next;
+
+ const char *cmd;
+ const unsigned char *args;
+ size_t args_size;
+
+ unsigned int id;
+
+ auth_master_request_callback_t *callback;
+ void *context;
+
+ bool aborted:1;
+ bool removed:1;
+ bool in_callback:1;
+};
+
struct auth_master_connection {
struct connection conn;
struct connection_list *clist;
unsigned int id_counter;
- auth_master_request_callback_t *reply_callback;
- void *reply_context;
+ struct auth_master_request *requests_head, *requests_tail;
+ unsigned int requests_count;
unsigned int timeout_msecs;
bool connected:1;
bool sent_handshake:1;
- bool aborted:1;
+ bool waiting:1;
};
/*
* Request
*/
-void auth_request_lookup_abort(struct auth_master_connection *conn);
-
-int auth_master_run_cmd_pre(struct auth_master_connection *conn,
- const char *cmd, const unsigned char *args,
- size_t args_size);
-int auth_master_run_cmd_post(struct auth_master_connection *conn);
-int auth_master_run_cmd(struct auth_master_connection *conn, const char *cmd,
- const unsigned char *args, size_t args_size);
+int auth_master_request_got_reply(struct auth_master_request **_req,
+ const char *reply, const char *const *args);
+void auth_master_request_fail(struct auth_master_request **_req,
+ const char *reason);
/*
* Connection
/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
+#include "llist.h"
#include "ioloop.h"
+#include "istream.h"
#include "ostream.h"
#include "master-service.h"
#include "auth-master-private.h"
-void auth_request_lookup_abort(struct auth_master_connection *conn)
+static void auth_master_request_update_event(struct auth_master_request *req)
{
- if (conn->ioloop != NULL)
+ event_add_int(req->event, "id", req->id);
+ event_set_append_log_prefix(req->event,
+ t_strdup_printf("request [%u]: ", req->id));
+}
+
+static void auth_master_request_remove(struct auth_master_request *req)
+{
+ struct auth_master_connection *conn = req->conn;
+
+ if (req->removed)
+ return;
+ req->removed = TRUE;
+
+ DLLIST2_REMOVE(&conn->requests_head, &conn->requests_tail, req);
+ conn->requests_count--;
+
+ if (conn->waiting) {
+ i_assert(conn->ioloop != NULL);
io_loop_stop(conn->ioloop);
- conn->aborted = TRUE;
+ } else if (conn->requests_head == NULL) {
+ auth_master_unset_io(conn);
+ }
+}
+
+static void auth_master_request_free(struct auth_master_request **_req)
+{
+ struct auth_master_request *req = *_req;
+
+ *_req = NULL;
+
+ if (req == NULL)
+ return;
+
+ auth_master_request_remove(req);
+ event_unref(&req->event);
+ pool_unref(&req->pool);
+}
+
+void auth_master_request_set_event(struct auth_master_request *req,
+ struct event *event)
+{
+ event_unref(&req->event);
+ req->event = event_create(event);
+ event_set_forced_debug(req->event,
+ HAS_ALL_BITS(req->conn->flags,
+ AUTH_MASTER_FLAG_DEBUG));
+ auth_master_request_update_event(req);
+}
+
+static int
+auth_master_request_callback(struct auth_master_request *req,
+ const struct auth_master_reply *mreply)
+{
+ auth_master_request_callback_t *callback = req->callback;
+ int ret;
+
+ req->callback = NULL;
+
+ /* Disallow running an ioloop for this auth master client from inside
+ one of its own callbacks; i.e. thereby eventually triggering a
+ callback in a callback. This is not supported and can cause nasty
+ bugs.
+ */
+ i_assert(!req->in_callback);
+
+ if (callback == NULL)
+ return 1;
+
+ req->in_callback = TRUE;
+ ret = callback(mreply, req->context);
+ req->in_callback = FALSE;
+
+ if (ret == 0) {
+ /* Application expects more replies for this request. */
+ req->callback = callback;
+ }
+ return ret;
+}
+
+int auth_master_request_got_reply(struct auth_master_request **_req,
+ const char *reply, const char *const *args)
+{
+ struct auth_master_request *req = *_req;
+ struct auth_master_connection *conn = req->conn;
+ int ret;
+
+ *_req = NULL;
+
+ i_assert(!req->in_callback);
+
+ e_debug(req->event, "Got reply: %s %s",
+ reply, t_strarray_join(args, " "));
+
+ const struct auth_master_reply mreply = {
+ .reply = reply,
+ .args = args,
+ };
+
+ ret = auth_master_request_callback(req, &mreply);
+ if (ret == 0) {
+ if (conn->waiting) {
+ i_assert(conn->ioloop != NULL);
+ io_loop_stop(conn->ioloop);
+ }
+ } else {
+ auth_master_request_remove(req);
+ auth_master_request_free(&req);
+ }
+ return ret;
+}
+
+void auth_master_request_abort(struct auth_master_request **_req)
+{
+ struct auth_master_request *req = *_req;
+
+ *_req = NULL;
+
+ if (req == NULL)
+ return;
+ if (req->in_callback)
+ return;
+
+ e_debug(req->event, "Aborted");
+
+ auth_master_request_remove(req);
+ auth_master_request_free(&req);
+}
+
+void auth_master_request_fail(struct auth_master_request **_req,
+ const char *reason)
+{
+ struct auth_master_request *req = *_req;
+
+ if (req->in_callback)
+ return;
+
+ e_debug(req->event, "Failed: %s", reason);
+
+ const struct auth_master_reply mreply = {
+ .reply = "FAIL",
+ .errormsg = reason,
+ };
+
+ i_assert(req->callback != NULL);
+ (void)auth_master_request_callback(req, &mreply);
+
+ auth_master_request_abort(_req);
}
static void
o_stream_nsendv(conn->conn.output, iov, iovc);
}
-int auth_master_run_cmd_pre(struct auth_master_connection *conn,
- const char *cmd, const unsigned char *args,
- size_t args_size)
+#undef auth_master_request
+struct auth_master_request *
+auth_master_request(struct auth_master_connection *conn, const char *cmd,
+ const unsigned char *args, size_t args_size,
+ auth_master_request_callback_t *callback, void *context)
{
- unsigned int id;
+ pool_t pool;
+ struct auth_master_request *req;
+
+ pool = pool_alloconly_create("auth_master_request", 256 + args_size);
+ req = p_new(pool, struct auth_master_request, 1);
+ req->pool = pool;
+ req->conn = conn;
+
+ if (++conn->id_counter == 0) {
+ /* avoid zero */
+ conn->id_counter++;
+ }
+ req->id = conn->id_counter;
+
+ req->event = event_create(conn->conn.event);
+ event_drop_parent_log_prefixes(req->event, 1);
+ auth_master_request_update_event(req);
+
+ req->callback = callback;
+ req->context = context;
+
+ DLLIST2_APPEND(&conn->requests_head, &conn->requests_tail, req);
+ conn->requests_count++;
+
+ req->cmd = p_strdup(req->pool, cmd);
+ if (args_size > 0)
+ req->args = p_memdup(req->pool, args, args_size);
+ req->args_size = args_size;
+
+ return req;
+}
+
+int auth_master_request_submit(struct auth_master_request **_req)
+{
+ struct auth_master_request *req = *_req;
+ struct auth_master_connection *conn = req->conn;
+
+ if (req == NULL)
+ return -1;
auth_master_set_io(conn);
if (!conn->connected) {
if (auth_master_connect(conn) < 0) {
auth_master_unset_io(conn);
+ auth_master_request_free(_req);
return -1;
}
i_assert(conn->connected);
conn->sent_handshake = TRUE;
}
- if (++conn->id_counter == 0) {
- /* avoid zero */
- conn->id_counter++;
- }
- id = conn->id_counter;
-
- auth_master_request_send(conn, cmd, id, args, args_size);
+ auth_master_request_send(req->conn, req->cmd, req->id,
+ req->args, req->args_size);
o_stream_uncork(conn->conn.output);
if (o_stream_flush(conn->conn.output) < 0) {
o_stream_get_error(conn->conn.output));
auth_master_unset_io(conn);
auth_master_disconnect(conn);
+ auth_master_request_free(_req);
return -1;
}
return 0;
}
-int auth_master_run_cmd_post(struct auth_master_connection *conn)
+static void auth_master_request_stop(struct auth_master_request *req)
{
- auth_master_unset_io(conn);
- if (conn->aborted) {
- conn->aborted = FALSE;
- auth_master_disconnect(conn);
- return -1;
- }
- return 0;
-}
+ struct auth_master_connection *conn = req->conn;
-static void auth_master_stop(struct auth_master_connection *conn)
-{
if (master_service_is_killed(master_service)) {
- auth_request_lookup_abort(conn);
+ auth_master_request_abort(&req);
io_loop_stop(conn->ioloop);
}
}
-int auth_master_run_cmd(struct auth_master_connection *conn, const char *cmd,
- const unsigned char *args, size_t args_size)
+bool auth_master_request_wait(struct auth_master_request *req)
{
- if (auth_master_run_cmd_pre(conn, cmd, args, args_size) < 0)
- return -1;
+ struct auth_master_connection *conn = req->conn;
+ struct timeout *to;
+ bool was_corked = FALSE;
+
+ if (conn->conn.input != NULL &&
+ i_stream_get_data_size(conn->conn.input) > 0)
+ i_stream_set_input_pending(conn->conn.input, TRUE);
+ if (conn->conn.output != NULL) {
+ was_corked = o_stream_is_corked(conn->conn.output);
+ o_stream_uncork(conn->conn.output);
+ }
+
/* add stop handler */
- struct timeout *to = timeout_add_short(100, auth_master_stop, conn);
+ to = timeout_add_short(100, auth_master_request_stop, req);
+
+ conn->waiting = TRUE;
io_loop_run(conn->ioloop);
+ conn->waiting = FALSE;
+
timeout_remove(&to);
- return auth_master_run_cmd_post(conn);
+
+ if (conn->conn.output != NULL && was_corked)
+ o_stream_cork(conn->conn.output);
+
+ if (conn->requests_head != NULL)
+ return FALSE;
+
+ auth_master_unset_io(conn);
+ return TRUE;
}
auth_master_connection_failure(struct auth_master_connection *conn,
const char *reason)
{
+ struct auth_master_request *req;
+
if (reason == NULL)
reason = "Disconnected from auth service";
timeout_remove(&conn->to);
- auth_request_lookup_abort(conn);
+ while (conn->requests_head != NULL) {
+ req = conn->requests_head;
+
+ auth_master_request_fail(&req, reason);
+ }
+
+ if (conn->ioloop != NULL && conn->waiting)
+ io_loop_stop(conn->ioloop);
}
void auth_master_disconnect(struct auth_master_connection *conn)
auth_master_connection_failure(conn, NULL);
break;
default:
- if (!conn->aborted)
+ if (conn->requests_head != NULL)
e_error(conn->conn.event, "Disconnected unexpectedly");
auth_master_connection_failure(conn, NULL);
}
}
e_error(conn->conn.event, "Request timed out");
- auth_request_lookup_abort(conn);
+ struct auth_master_request *req = conn->requests_head;
+ auth_master_request_abort(&req);
}
static int
auth_master_handle_input(struct auth_master_connection *conn,
const char *const *args)
{
+ struct auth_master_request *req;
unsigned int id;
if (strcmp(args[0], "CUID") == 0) {
return -1;
}
- if (id != conn->id_counter) {
+ req = conn->requests_head;
+ if (req == NULL || id != req->id) {
e_error(conn->conn.event,
"Auth server sent reply with unknown ID %u", id);
return -1;
e_debug(conn->conn.event, "auth input: %s",
t_strarray_join(args, "\t"));
- io_loop_stop(conn->ioloop);
-
- struct auth_master_reply mreply = {
- .reply = args[0],
- .args = args + 2,
- };
- return conn->reply_callback(&mreply, conn->reply_context);
+ return auth_master_request_got_reply(&req, args[0], args + 2);
}
static int
}
static int
-auth_lookup_reply_callback(const struct auth_master_reply *reply, void *context)
+auth_lookup_reply_callback(const struct auth_master_reply *reply,
+ struct auth_master_lookup *lookup)
{
- struct auth_master_lookup *lookup = context;
const char *value;
const char *const *args = reply->args;
unsigned int i, len;
+ if (reply->errormsg != NULL) {
+ lookup->fields = p_new(lookup->pool, const char *, 2);
+ lookup->fields[0] = p_strdup(lookup->pool, reply->errormsg);
+ e_debug(lookup->event, "auth %s error: %s",
+ lookup->expected_reply, reply->errormsg);
+ lookup->return_value = -1;
+ return 1;
+ }
+ i_assert(reply->reply != NULL);
+ i_assert(args != NULL);
+
lookup->return_value = parse_reply(lookup, reply->reply, args);
len = str_array_length(args);
pool_t pool, const char *const **fields_r)
{
struct auth_master_lookup lookup;
+ struct auth_master_request *req;
string_t *args;
if (!is_valid_string(user) || !is_valid_string(info->protocol)) {
lookup.expected_reply = "PASS";
lookup.user = user;
- conn->reply_callback = auth_lookup_reply_callback;
- conn->reply_context = &lookup;
-
args = t_str_new(128);
str_append(args, user);
auth_user_info_export(args, info);
set_name("auth_client_passdb_lookup_started");
e_debug(e->event(), "Started passdb lookup");
- (void)auth_master_run_cmd(conn, "PASS", str_data(args), str_len(args));
+ req = auth_master_request(conn, "PASS", str_data(args), str_len(args),
+ auth_lookup_reply_callback, &lookup);
+ if (auth_master_request_submit(&req) < 0) {
+ *fields_r = empty_str_array;
+ event_unref(&lookup.event);
+ return -1;
+ }
+
+ auth_master_request_set_event(req, lookup.event);
+ (void)auth_master_request_wait(req);
*fields_r = lookup.fields != NULL ? lookup.fields :
p_new(pool, const char *, 1);
}
event_unref(&lookup.event);
- conn->reply_context = NULL;
return lookup.return_value;
}
const char *const **fields_r)
{
struct auth_master_lookup lookup;
+ struct auth_master_request *req;
string_t *args;
if (!is_valid_string(user) || !is_valid_string(info->protocol)) {
lookup.expected_reply = "USER";
lookup.user = user;
- conn->reply_callback = auth_lookup_reply_callback;
- conn->reply_context = &lookup;
-
args = t_str_new(128);
str_append(args, user);
auth_user_info_export(args, info);
set_name("auth_client_userdb_lookup_started");
e_debug(e->event(), "Started userdb lookup");
- (void)auth_master_run_cmd(conn, "USER", str_data(args), str_len(args));
+ req = auth_master_request(conn, "USER", str_data(args), str_len(args),
+ auth_lookup_reply_callback, &lookup);
+ if (auth_master_request_submit(&req) < 0) {
+ *fields_r = empty_str_array;
+ event_unref(&lookup.event);
+ return -1;
+ }
+
+ auth_master_request_set_event(req, lookup.event);
+ (void)auth_master_request_wait(req);
if (lookup.return_value <= 0 || lookup.fields[0] == NULL) {
*username_r = NULL;
}
event_unref(&lookup.event);
- conn->reply_context = NULL;
return lookup.return_value;
}
struct auth_master_user_list_ctx {
struct auth_master_connection *conn;
struct event *event;
+ struct auth_master_request *req;
string_t *username;
bool finished;
bool failed;
static int
auth_user_list_reply_callback(const struct auth_master_reply *reply,
- void *context)
+ struct auth_master_user_list_ctx *ctx)
{
- struct auth_master_user_list_ctx *ctx = context;
const char *const *args = reply->args;
timeout_reset(ctx->conn->to);
+ if (reply->errormsg != NULL) {
+ e_error(ctx->event, "User listing failed: %s", reply->errormsg);
+ ctx->req = NULL;
+ ctx->failed = TRUE;
+ ctx->finished = TRUE;
+ return 1;
+ }
+ i_assert(reply->reply != NULL);
+ i_assert(args != NULL);
+
if (strcmp(reply->reply, "DONE") == 0) {
+ ctx->req = NULL;
if (args[0] != NULL && strcmp(args[0], "fail") == 0) {
e_error(ctx->event, "User listing returned failure");
ctx->failed = TRUE;
}
if (strcmp(reply->reply, "LIST") != 0 || args[0] == NULL) {
e_error(ctx->event, "User listing returned invalid input");
+ ctx->req = NULL;
ctx->failed = TRUE;
return -1;
}
ctx->conn = conn;
ctx->username = str_new(default_pool, 128);
- conn->reply_callback = auth_user_list_reply_callback;
- conn->reply_context = ctx;
-
args = t_str_new(128);
if (*user_mask != '\0')
str_printfa(args, "\tuser=%s", user_mask);
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, "LIST",
- str_data(args), str_len(args)) < 0)
+ ctx->req = auth_master_request(conn, "LIST",
+ str_data(args), str_len(args),
+ auth_user_list_reply_callback, ctx);
+ if (auth_master_request_submit(&ctx->req) < 0)
ctx->failed = TRUE;
+ else
+ auth_master_request_set_event(ctx->req, ctx->event);
if (conn->prev_ioloop != NULL)
io_loop_set_current(conn->prev_ioloop);
struct auth_master_connection *conn = ctx->conn;
const char *line;
- if (!conn->connected)
+ if (ctx->finished || ctx->failed || ctx->req == NULL)
return NULL;
+ i_assert(!conn->waiting);
str_truncate(ctx->username, 0);
/* try to read already buffered input */
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)
/* wait for more data */
io_loop_set_current(conn->ioloop);
- i_stream_set_input_pending(conn->conn.input, TRUE);
- io_loop_run(conn->ioloop);
+ if (auth_master_request_wait(ctx->req))
+ ctx->req = NULL;
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);
int ret = ctx->failed ? -1 : 0;
*_ctx = NULL;
- auth_master_run_cmd_post(ctx->conn);
if (ret < 0) {
struct event_passthrough *e =
e_debug(e->event(), "Finished listing users");
}
+ auth_master_request_abort(&ctx->req);
str_free(&ctx->username);
event_unref(&ctx->event);
i_free(ctx);
static int
auth_cache_flush_reply_callback(const struct auth_master_reply *reply,
- void *context)
+ struct auth_master_cache_ctx *ctx)
{
- struct auth_master_cache_ctx *ctx = context;
const char *const *args = reply->args;
+ if (reply->errormsg != NULL) {
+ ctx->failed = TRUE;
+ return 1;
+ }
+ i_assert(reply->reply != NULL);
+ i_assert(args != NULL);
+
if (strcmp(reply->reply, "OK") != 0)
ctx->failed = TRUE;
else if (args[0] == NULL || str_to_uint(args[0], &ctx->count) < 0)
const char *const *users, unsigned int *count_r)
{
struct auth_master_cache_ctx ctx;
+ struct auth_master_request *req;
string_t *args;
i_zero(&ctx);
ctx.conn = conn;
- conn->reply_callback = auth_cache_flush_reply_callback;
- conn->reply_context = &ctx;
-
args = t_str_new(128);
if (users != NULL) {
for (; *users != NULL; users++) {
e_debug(ctx.event, "Started cache flush");
- (void)auth_master_run_cmd(conn, "CACHE-FLUSH",
- str_data(args), str_len(args));
+ req = auth_master_request(conn, "CACHE-FLUSH",
+ str_data(args), str_len(args),
+ auth_cache_flush_reply_callback, &ctx);
+ if (auth_master_request_submit(&req) < 0)
+ ctx.failed = TRUE;
+ else {
+ auth_master_request_set_event(req, ctx.event);
+ (void)auth_master_request_wait(req);
+ }
if (ctx.failed)
e_debug(ctx.event, "Cache flush failed");
e_debug(ctx.event, "Finished cache flush");
event_unref(&ctx.event);
- conn->reply_context = NULL;
*count_r = ctx.count;
return ctx.failed ? -1 : 0;
}
#include "net.h"
+struct auth_master_request;
+struct auth_master_reply;
+struct auth_master_connection;
+
enum auth_master_flags {
/* Enable logging debug information */
AUTH_MASTER_FLAG_DEBUG = 0x01,
struct auth_master_reply {
const char *reply;
const char *const *args;
+
+ const char *errormsg;
};
/* Returns 1 upon full completion, 0 upon successful partial completion (will
auth_master_request_callback_t(const struct auth_master_reply *reply,
void *context);
+struct auth_master_request *
+auth_master_request(struct auth_master_connection *conn, const char *cmd,
+ const unsigned char *args, size_t args_size,
+ auth_master_request_callback_t *callback, void *context);
+#define auth_master_request(conn, cmd, args, args_size, callback, context) \
+ auth_master_request(conn, cmd, args, args_size + \
+ CALLBACK_TYPECHECK(callback, int (*)( \
+ const struct auth_master_reply *reply, \
+ typeof(context))), \
+ (auth_master_request_callback_t *)callback, context)
+
+int auth_master_request_submit(struct auth_master_request **_req);
+
+void auth_master_request_set_event(struct auth_master_request *req,
+ struct event *event);
+
+void auth_master_request_abort(struct auth_master_request **_req);
+bool auth_master_request_wait(struct auth_master_request *req);
+
/*
* Connection
*/
ret = test_client_passdb_lookup_simple("hendrik", FALSE, &error);
test_out("run (ret == -1)", ret == -1);
- test_assert(error == NULL);
+ test_assert(error != NULL &&
+ str_begins_with(error, "Disconnected from auth service"));
return FALSE;
}
ret = test_client_passdb_lookup_simple("hendrik", TRUE, &error);
test_out("run (ret == -1)", ret == -1);
- test_assert(error == NULL);
+ test_assert(error != NULL &&
+ str_begins_with(error, "Disconnected from auth service"));
return FALSE;
}
ret = test_client_userdb_lookup_simple("hendrik", FALSE, &error);
test_out("run (ret == -1)", ret == -1);
- test_assert(error == NULL);
+ test_assert(error != NULL &&
+ str_begins_with(error, "Disconnected from auth service"));
return FALSE;
}
ret = test_client_userdb_lookup_simple("hendrik", TRUE, &error);
test_out("run (ret == -1)", ret == -1);
- test_assert(error == NULL);
+ test_assert(error != NULL &&
+ str_begins_with(error, "Disconnected from auth service"));
return FALSE;
}