/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
+#include "array.h"
+#include "hash.h"
#include "llist.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
+#include "time-util.h"
#include "master-service.h"
#include "auth-master-private.h"
t_strdup_printf("request [%u]: ", req->id));
}
+unsigned int
+auth_master_request_get_timeout_msecs(struct auth_master_request *req)
+{
+ struct timeval expires = req->create_stamp;
+ int msecs;
+
+ timeval_add_msecs(&expires, req->conn->timeout_msecs);
+
+ msecs = timeval_diff_msecs(&expires, &ioloop_timeval);
+ return (unsigned int)(msecs < 0 ? 0 : msecs);
+}
+
static void auth_master_request_remove(struct auth_master_request *req)
{
struct auth_master_connection *conn = req->conn;
return;
req->removed = TRUE;
+ if (req->sent)
+ hash_table_remove(conn->requests, POINTER_CAST(req->id));
DLLIST2_REMOVE(&conn->requests_head, &conn->requests_tail, req);
conn->requests_count--;
+ auth_master_connection_update_timeout(conn);
+ auth_master_check_idle(conn);
+
if (conn->waiting) {
i_assert(conn->ioloop != NULL);
io_loop_stop(conn->ioloop);
- } else if (conn->requests_head == NULL) {
- auth_master_unset_io(conn);
}
}
-static void auth_master_request_free(struct auth_master_request **_req)
+static void auth_master_request_ref(struct auth_master_request *req)
+{
+ req->refcount++;
+}
+
+static bool auth_master_request_unref(struct auth_master_request **_req)
{
struct auth_master_request *req = *_req;
*_req = NULL;
if (req == NULL)
- return;
+ return TRUE;
+
+ i_assert(req->refcount > 0);
+ if (--req->refcount > 0)
+ return TRUE;
auth_master_request_remove(req);
event_unref(&req->event);
pool_unref(&req->pool);
+ return FALSE;
}
void auth_master_request_set_event(struct auth_master_request *req,
auth_master_request_callback(struct auth_master_request *req,
const struct auth_master_reply *mreply)
{
+ struct auth_master_connection *conn = req->conn;
auth_master_request_callback_t *callback = req->callback;
+ struct auth_master_request *tmp_req = req;
int ret;
req->callback = NULL;
if (callback == NULL)
return 1;
+ if (conn->prev_ioloop != NULL) {
+ /* Don't let callback see that we've created our
+ internal ioloop in case it wants to add some ios
+ or timeouts. */
+ current_ioloop = conn->prev_ioloop;
+ }
+
+ auth_master_request_ref(tmp_req);
req->in_callback = TRUE;
ret = callback(mreply, req->context);
req->in_callback = FALSE;
+ auth_master_request_unref(&tmp_req);
+
+ if (conn->prev_ioloop != NULL)
+ current_ioloop = conn->ioloop;
if (ret == 0) {
/* Application expects more replies for this request. */
i_assert(!req->in_callback);
+ if (req->state < AUTH_MASTER_REQUEST_STATE_FINISHED)
+ req->state = AUTH_MASTER_REQUEST_STATE_REPLIED;
+
e_debug(req->event, "Got reply: %s %s",
reply, t_strarray_join(args, " "));
io_loop_stop(conn->ioloop);
}
} else {
+ if (req->state < AUTH_MASTER_REQUEST_STATE_FINISHED)
+ req->state = AUTH_MASTER_REQUEST_STATE_FINISHED;
auth_master_request_remove(req);
- auth_master_request_free(&req);
+ auth_master_request_unref(&req);
}
return ret;
}
if (req->in_callback)
return;
+ if (req->state >= AUTH_MASTER_REQUEST_STATE_FINISHED)
+ return;
+ req->state = AUTH_MASTER_REQUEST_STATE_ABORTED;
+
e_debug(req->event, "Aborted");
auth_master_request_remove(req);
- auth_master_request_free(&req);
+ auth_master_request_unref(&req);
}
void auth_master_request_fail(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->refcount = 1;
req->conn = conn;
+ req->create_stamp = ioloop_timeval;
if (++conn->id_counter == 0) {
/* avoid zero */
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);
+ // FIXME: handle
+ /* we couldn't connect to auth now,
+ so we probably can't in future either. */
+ auth_master_request_unref(_req);
return -1;
}
+ // FIXME: allow asynchronous connection
i_assert(conn->connected);
connection_input_resume(&conn->conn);
}
if (o_stream_flush(conn->conn.output) < 0) {
e_error(conn->conn.event, "write(auth socket) failed: %s",
o_stream_get_error(conn->conn.output));
- auth_master_unset_io(conn);
auth_master_disconnect(conn);
- auth_master_request_free(_req);
+ auth_master_request_unref(_req);
return -1;
}
+
+ hash_table_insert(conn->requests, POINTER_CAST(req->id), req);
+ req->sent = TRUE;
+
+ auth_master_connection_start_timeout(conn);
+ auth_master_stop_idle(conn);
+
return 0;
}
bool auth_master_request_wait(struct auth_master_request *req)
{
struct auth_master_connection *conn = req->conn;
+ struct ioloop *ioloop, *prev_ioloop;
+ enum auth_master_request_state last_state;
struct timeout *to;
- bool was_corked = FALSE;
+ bool waiting = conn->waiting, was_corked = FALSE, freed;
+
+ if (req->state >= AUTH_MASTER_REQUEST_STATE_FINISHED)
+ return TRUE;
+
+ i_assert(auth_master_request_count(conn) > 0);
+
+ if ((conn->flags & AUTH_MASTER_FLAG_NO_INNER_IOLOOP) != 0)
+ ioloop = conn->ioloop;
+ else {
+ prev_ioloop = conn->ioloop;
+ if (!waiting)
+ conn->prev_ioloop = prev_ioloop;
+ ioloop = io_loop_create();
+ auth_master_switch_ioloop_to(conn, ioloop);
+ }
if (conn->conn.input != NULL &&
i_stream_get_data_size(conn->conn.input) > 0)
o_stream_uncork(conn->conn.output);
}
+ /* either we're waiting for network I/O or we're getting out of a
+ callback using timeout_add_short(0) */
+ i_assert(io_loop_have_ios(ioloop) ||
+ io_loop_have_immediate_timeouts(ioloop));
+
+ auth_master_request_ref(req);
+ req->state = AUTH_MASTER_REQUEST_STATE_SENT;
+
/* add stop handler */
to = timeout_add_short(100, auth_master_request_stop, req);
conn->waiting = TRUE;
- io_loop_run(conn->ioloop);
- conn->waiting = FALSE;
+ while (req->state < AUTH_MASTER_REQUEST_STATE_REPLIED)
+ io_loop_run(conn->ioloop);
+ conn->waiting = waiting;
timeout_remove(&to);
if (conn->conn.output != NULL && was_corked)
o_stream_cork(conn->conn.output);
- if (conn->requests_head != NULL)
- return FALSE;
+ last_state = req->state;
+ freed = !auth_master_request_unref(&req);
+
+ if ((conn->flags & AUTH_MASTER_FLAG_NO_INNER_IOLOOP) == 0) {
+ auth_master_switch_ioloop_to(conn, prev_ioloop);
+ io_loop_destroy(&ioloop);
+ if (!waiting)
+ conn->prev_ioloop = NULL;
+ }
+
+ return (freed || last_state >= AUTH_MASTER_REQUEST_STATE_FINISHED);
+}
- auth_master_unset_io(conn);
- return TRUE;
+unsigned int auth_master_request_count(struct auth_master_connection *conn)
+{
+ return conn->requests_count;
}
#include "lib.h"
#include "lib-signals.h"
#include "array.h"
+#include "hash.h"
#include "ioloop.h"
#include "eacces-error.h"
#include "net.h"
#include "ostream.h"
#include "str.h"
#include "strescape.h"
+#include "time-util.h"
#include "auth-master-private.h"
conn = p_new(pool, struct auth_master_connection, 1);
conn->pool = pool;
conn->refcount = 1;
+ conn->ioloop = current_ioloop;
conn->auth_socket_path = p_strdup(pool, auth_socket_path);
conn->flags = flags;
conn->timeout_msecs = 1000*MASTER_AUTH_LOOKUP_TIMEOUT_SECS;
if ((flags & AUTH_MASTER_FLAG_NO_INNER_IOLOOP) != 0)
conn->ioloop = current_ioloop;
+
+ hash_table_create_direct(&conn->requests, pool, 0);
+
+ /* Try to use auth request ID numbers from wider range to ease
+ debugging. */
+ conn->id_counter = i_rand_limit(32767) * 131072U;
+
return conn;
}
conn->connected = FALSE;
conn->sent_handshake = FALSE;
- timeout_remove(&conn->to);
+ timeout_remove(&conn->to_connect);
+ timeout_remove(&conn->to_request);
+ timeout_remove(&conn->to_idle);
while (conn->requests_head != NULL) {
req = conn->requests_head;
auth_master_request_fail(&req, reason);
}
+ i_assert(hash_table_count(conn->requests) == 0);
if (conn->ioloop != NULL && conn->waiting)
io_loop_stop(conn->ioloop);
auth_master_disconnect(conn);
connection_deinit(&conn->conn);
connection_list_deinit(&clist);
+ hash_table_destroy(&conn->requests);
pool_unref(&conn->pool);
}
}
}
-static void auth_request_timeout(struct auth_master_connection *conn)
+static void
+auth_master_connection_timeout(struct auth_master_connection *conn)
{
- if (!connection_handshake_received(&conn->conn)) {
- e_error(conn->conn.event, "Connecting timed out");
- auth_master_connection_failure(conn, "Connecting timed out");
+ struct auth_master_request *req;
+ const char *reason;
+
+ timeout_remove(&conn->to_request);
+
+ conn->in_timeout = TRUE;
+ req = conn->requests_head;
+ while (req != NULL && auth_master_request_get_timeout_msecs(req) == 0) {
+ struct auth_master_request *req_next = req->next;
+ int msecs;
+
+ if (req->in_callback) {
+ req = req_next;
+ continue;
+ }
+
+ msecs = timeval_diff_msecs(&ioloop_timeval, &req->create_stamp);
+ reason = t_strdup_printf(
+ "Auth server request timed out after %u.%03u secs",
+ msecs / 1000, msecs % 1000);
+ auth_master_request_fail(&req, reason);
+
+ req = req_next;
+ }
+ conn->in_timeout = FALSE;
+
+ auth_master_connection_update_timeout(conn);
+}
+
+void auth_master_connection_update_timeout(struct auth_master_connection *conn)
+{
+ struct auth_master_request *req;
+
+ if (conn->in_timeout)
+ return;
+ if (!conn->connected) {
+ i_assert(conn->to_request == NULL);
return;
}
- e_error(conn->conn.event, "Request timed out");
- struct auth_master_request *req = conn->requests_head;
- auth_master_request_abort(&req);
+ req = conn->requests_head;
+ while (req != NULL && req->in_callback)
+ req = req->next;
+
+ timeout_remove(&conn->to_request);
+ if (req == NULL)
+ return;
+
+ conn->to_request = timeout_add_to(
+ conn->ioloop, auth_master_request_get_timeout_msecs(req),
+ auth_master_connection_timeout, conn);
+}
+
+void auth_master_connection_start_timeout(struct auth_master_connection *conn)
+{
+ if (conn->to_request != NULL || conn->to_connect != NULL)
+ return;
+
+ auth_master_connection_update_timeout(conn);
}
static int
"Authentication server sent invalid SPID: %s", line);
return -1;
}
+
+ /* Handshake complete */
+ timeout_remove(&conn->to_connect);
+ auth_master_connection_update_timeout(conn);
return 1;
}
return -1;
}
- req = conn->requests_head;
- if (req == NULL || id != req->id) {
- e_error(conn->conn.event,
- "Auth server sent reply with unknown ID %u", id);
+ req = hash_table_lookup(conn->requests, POINTER_CAST(id));
+ if (req == NULL) {
+ e_debug(conn->conn.event,
+ "Auth server sent reply with unknown ID %u "
+ "(this request was probably aborted)", id);
return -1;
}
conn->connected = TRUE;
}
+static void auth_master_connect_timeout(struct auth_master_connection *conn)
+{
+ e_error(conn->conn.event, "Connecting timed out");
+ auth_master_connection_failure(conn, "Connecting timed out");
+}
+
+static void
+auth_master_delayed_connect_failure(struct auth_master_connection *conn)
+{
+ e_debug(conn->conn.event, "Delayed connect failure");
+
+ i_assert(conn->to_connect != NULL);
+ timeout_remove(&conn->to_connect);
+ auth_master_connection_failure(conn, "Connect failed");
+}
+
int auth_master_connect(struct auth_master_connection *conn)
{
if (conn->connected)
return 0;
+ i_assert(conn->to_connect == NULL);
+ i_assert(conn->to_request == NULL);
+
if (conn->ioloop != NULL)
connection_switch_ioloop_to(&conn->conn, conn->ioloop);
if (connection_client_connect(&conn->conn) < 0) {
e_error(conn->conn.event, "connect(%s) failed: %m",
conn->auth_socket_path);
}
+ conn->to_connect = timeout_add_to(
+ conn->ioloop, 0,
+ auth_master_delayed_connect_failure, conn);
return -1;
}
- connection_input_halt(&conn->conn);
+ conn->to_connect = timeout_add_to(conn->ioloop, conn->timeout_msecs,
+ auth_master_connect_timeout, conn);
return 0;
}
+void auth_master_switch_ioloop_to(struct auth_master_connection *conn,
+ struct ioloop *ioloop)
+{
+ conn->ioloop = ioloop;
+
+ if (conn->to_connect != NULL) {
+ conn->to_connect =
+ io_loop_move_timeout_to(ioloop, &conn->to_connect);
+ }
+ if (conn->to_request != NULL) {
+ conn->to_request =
+ io_loop_move_timeout_to(ioloop, &conn->to_request);
+ }
+ if (conn->to_idle != NULL)
+ conn->to_idle = io_loop_move_timeout_to(ioloop, &conn->to_idle);
+ connection_switch_ioloop_to(&conn->conn, conn->ioloop);
+}
+
+void auth_master_switch_ioloop(struct auth_master_connection *conn)
+{
+ auth_master_switch_ioloop_to(conn, current_ioloop);
+}
static void auth_master_idle_timeout(struct auth_master_connection *conn)
{
auth_master_disconnect(conn);
}
-void auth_master_set_io(struct auth_master_connection *conn)
+void auth_master_check_idle(struct auth_master_connection *conn)
{
- if (conn->ioloop != NULL)
+ if ((conn->flags & AUTH_MASTER_FLAG_NO_IDLE_TIMEOUT) != 0)
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);
+ if (current_ioloop == NULL)
+ return;
+ i_assert(conn->to_idle == NULL);
+ if (conn->requests_head != NULL)
+ return;
+ conn->to_idle = timeout_add_to(conn->ioloop,
+ 1000 * AUTH_MASTER_IDLE_SECS,
+ auth_master_idle_timeout, conn);
}
-void auth_master_unset_io(struct auth_master_connection *conn)
+void auth_master_stop_idle(struct auth_master_connection *conn)
{
- if (conn->ioloop == NULL)
- return;
-
- if (conn->prev_ioloop != NULL) {
- io_loop_set_current(conn->prev_ioloop);
- }
- if ((conn->flags & AUTH_MASTER_FLAG_NO_INNER_IOLOOP) == 0) {
- 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_master_disconnect(conn);
- else if (conn->to == NULL) {
- conn->to = timeout_add(1000*AUTH_MASTER_IDLE_SECS,
- auth_master_idle_timeout, conn);
- }
- }
+ timeout_remove(&conn->to_idle);
}
/*
{
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;
struct auth_master_user_list_ctx *ctx;
string_t *args;
+ i_assert(auth_master_request_count(conn) == 0);
+
ctx = i_new(struct auth_master_user_list_ctx, 1);
ctx->conn = conn;
ctx->username = str_new(default_pool, 128);
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);
+
+ connection_input_halt(&conn->conn);
return ctx;
}
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->to_connect == NULL) {
+ line = i_stream_next_line(conn->conn.input);
+ if (line != NULL) {
+ T_BEGIN {
+ conn->conn.v.input_line(&conn->conn, line);
+ } T_END;
+ }
+ if (ctx->finished || ctx->failed)
+ return NULL;
+ if (str_len(ctx->username) > 0)
+ return str_c(ctx->username);
}
- 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);
+ if (!conn->conn.disconnected)
+ connection_input_resume(&conn->conn);
if (auth_master_request_wait(ctx->req))
ctx->req = NULL;
- io_loop_set_current(conn->prev_ioloop);
+ connection_input_halt(&conn->conn);
if (ctx->finished || ctx->failed)
return NULL;
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_request_abort(&ctx->req);
+ if (!conn->conn.disconnected)
+ connection_input_resume(&conn->conn);
+
str_free(&ctx->username);
event_unref(&ctx->event);
i_free(ctx);