summaryrefslogtreecommitdiffstats
path: root/src/master/test-auth-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/master/test-auth-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/master/test-auth-client.c')
-rw-r--r--src/master/test-auth-client.c1287
1 files changed, 1287 insertions, 0 deletions
diff --git a/src/master/test-auth-client.c b/src/master/test-auth-client.c
new file mode 100644
index 0000000..d161a5b
--- /dev/null
+++ b/src/master/test-auth-client.c
@@ -0,0 +1,1287 @@
+/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "llist.h"
+#include "base64.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-chain.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "sleep.h"
+#include "unlink-directory.h"
+#include "write-full.h"
+#include "connection.h"
+#include "master-service.h"
+#include "master-interface.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+
+#include "auth-client.h"
+
+#define TEST_SOCKET "./auth-client-test"
+#define CLIENT_PROGRESS_TIMEOUT 30
+#define SERVER_KILL_TIMEOUT_SECS 20
+
+static void main_deinit(void);
+
+/*
+ * Types
+ */
+
+struct server_connection {
+ struct connection conn;
+
+ void *context;
+
+ pool_t pool;
+};
+
+typedef void test_server_init_t(void);
+typedef bool test_client_init_t(void);
+
+/*
+ * State
+ */
+
+/* common */
+static struct ioloop *ioloop;
+static bool debug = FALSE;
+
+/* server */
+static struct io *io_listen;
+static int fd_listen = -1;
+static struct connection_list *server_conn_list;
+static void (*test_server_input)(struct server_connection *conn);
+static void (*test_server_init)(struct server_connection *conn);
+static void (*test_server_deinit)(struct server_connection *conn);
+
+/* client */
+
+/*
+ * Forward declarations
+ */
+
+/* server */
+static void test_server_run(void);
+static void server_connection_deinit(struct server_connection **_conn);
+
+/* client */
+static void test_client_deinit(void);
+
+static int
+test_client_auth_parallel(const char *mech, const char *username,
+ const char *password, unsigned int concurrency,
+ bool retry, const char **error_r);
+static int
+test_client_auth_simple(const char *mech, const char *username,
+ const char *password, bool retry, const char **error_r);
+
+/* test*/
+static void
+test_run_client_server(test_client_init_t *client_test,
+ test_server_init_t *server_test) ATTR_NULL(2);
+
+/*
+ * Connection refused
+ */
+
+/* server */
+
+static void test_server_connection_refused(void)
+{
+ i_close_fd(&fd_listen);
+ i_sleep_intr_secs(500);
+}
+
+/* client */
+
+static bool test_client_connection_refused(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "harrie", "frop", FALSE, &error);
+ test_out_reason("run (ret == -1)", ret == -1, error);
+
+ return FALSE;
+}
+
+/* test */
+
+static void test_connection_refused(void)
+{
+ test_begin("connection refused");
+ test_expect_error_string("Connection refused");
+ test_run_client_server(test_client_connection_refused,
+ test_server_connection_refused);
+ test_end();
+}
+
+/*
+ * Connection timed out
+ */
+
+/* server */
+
+static void test_connection_timed_out_input(struct server_connection *conn)
+{
+ i_sleep_intr_secs(5);
+ server_connection_deinit(&conn);
+}
+
+static void test_server_connection_timed_out(void)
+{
+ test_server_input = test_connection_timed_out_input;
+ test_server_run();
+}
+
+/* client */
+
+static bool test_client_connection_timed_out(void)
+{
+ time_t time;
+ const char *error;
+ int ret;
+
+ io_loop_time_refresh();
+ time = ioloop_time;
+
+ ret = test_client_auth_simple("PLAIN", "harrie", "frop", FALSE, &error);
+ test_out_reason("run (ret == -1)", ret == -1, error);
+
+ io_loop_time_refresh();
+ test_out("timeout", (ioloop_time - time) < 5);
+ return FALSE;
+}
+
+/* test */
+
+static void test_connection_timed_out(void)
+{
+ test_begin("connection timed out");
+ test_expect_error_string("Timeout waiting for handshake");
+ test_run_client_server(test_client_connection_timed_out,
+ test_server_connection_timed_out);
+ test_end();
+}
+
+/*
+ * Bad VERSION
+ */
+
+/* server */
+
+static void test_bad_version_input(struct server_connection *conn)
+{
+ server_connection_deinit(&conn);
+}
+
+static void test_bad_version_init(struct server_connection *conn)
+{
+ o_stream_nsend_str(
+ conn->conn.output,
+ "VERSION\t666\t666\n"
+ "MECH\tPLAIN\tplaintext\n"
+ "MECH\tLOGIN\tplaintext\n"
+ "SPID\t12296\n"
+ "CUID\t2\n"
+ "COOKIE\t46cc85ccd2833ca39a49c059fa3d3ccf\n"
+ "DONE\n");
+}
+
+static void test_server_bad_version(void)
+{
+ test_server_init = test_bad_version_init;
+ test_server_input = test_bad_version_input;
+ test_server_run();
+}
+
+/* client */
+
+static bool test_client_bad_version(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "harrie", "frop", FALSE, &error);
+ test_out_reason("run (ret == -1)", ret == -1, error);
+ return FALSE;
+}
+
+/* test */
+
+static void test_bad_version(void)
+{
+ test_begin("bad version");
+ test_expect_error_string("Socket supports major version 666");
+ test_run_client_server(test_client_bad_version,
+ test_server_bad_version);
+ test_end();
+}
+
+/*
+ * Disconnect VERSION
+ */
+
+/* server */
+
+static void test_disconnect_version_input(struct server_connection *conn)
+{
+ const char *line;
+
+ line = i_stream_read_next_line(conn->conn.input);
+ if (line == NULL) {
+ if (conn->conn.input->eof)
+ server_connection_deinit(&conn);
+ return;
+ }
+ server_connection_deinit(&conn);
+}
+
+static void test_disconnect_version_init(struct server_connection *conn)
+{
+ o_stream_nsend_str(
+ conn->conn.output,
+ "VERSION\t1\t2\n"
+ "MECH\tPLAIN\tplaintext\n"
+ "MECH\tLOGIN\tplaintext\n"
+ "SPID\t12296\n"
+ "CUID\t2\n"
+ "COOKIE\t46cc85ccd2833ca39a49c059fa3d3ccf\n"
+ "DONE\n");
+}
+
+static void test_server_disconnect_version(void)
+{
+ test_server_init = test_disconnect_version_init;
+ test_server_input = test_disconnect_version_input;
+ test_server_run();
+}
+
+/* client */
+
+static bool test_client_disconnect_version(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "harrie", "frop", FALSE, &error);
+ test_out_reason("run (ret == -1)", ret == -1, error);
+ return FALSE;
+}
+
+/* test */
+
+static void test_disconnect_version(void)
+{
+ test_begin("disconnect version");
+ test_expect_errors(2);
+ test_run_client_server(test_client_disconnect_version,
+ test_server_disconnect_version);
+ test_end();
+}
+
+/*
+ * Auth handshake
+ */
+
+/* server */
+
+enum _auth_handshake_state {
+ AUTH_HANDSHAKE_STATE_VERSION = 0,
+ AUTH_HANDSHAKE_STATE_CMD
+};
+
+struct _auth_handshake_request {
+ struct _auth_handshake_request *prev, *next;
+
+ unsigned int id;
+ const char *username;
+
+ unsigned int login_state;
+};
+
+struct _auth_handshake_server {
+ enum _auth_handshake_state state;
+
+ struct _auth_handshake_request *requests;
+};
+
+static bool
+test_auth_handshake_auth_plain(struct server_connection *conn, unsigned int id,
+ const unsigned char *data, size_t data_size)
+{
+ const char *authid, *authenid;
+ const char *pass;
+ size_t i, len;
+ int count;
+
+ /* authorization ID \0 authentication ID \0 pass. */
+ authid = (const char *) data;
+ authenid = NULL; pass = NULL;
+
+ count = 0;
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == '\0') {
+ if (++count == 1)
+ authenid = (const char *)data + (i + 1);
+ else {
+ i++;
+ len = data_size - i;
+ pass = t_strndup(data+i, len);
+ break;
+ }
+ }
+ }
+
+ if (count != 2) {
+ i_error("Bad AUTH PLAIN request: Bad data");
+ return FALSE;
+ }
+
+ i_assert(authenid != NULL);
+ if (strcmp(authid, "supremelordoftheuniverse") != 0) {
+ /* unexpected authorization ID */
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("FAIL\t%u\tuser=%s\n", id, authenid));
+ return TRUE;
+ }
+ if (strcmp(authenid, "harrie") == 0 && strcmp(pass, "frop") == 0) {
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("OK\t%u\tuser=harrie\n", id));
+ return TRUE;
+ }
+ if (strcmp(authenid, "hendrik") == 0)
+ return FALSE;
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("FAIL\t%u\tuser=%s\n", id, authenid));
+
+ return TRUE;
+}
+
+static bool
+test_auth_handshake_auth_login(struct server_connection *conn, unsigned int id,
+ const unsigned char *data ATTR_UNUSED,
+ size_t data_size)
+{
+ static const char *prompt1 = "Username:";
+ struct _auth_handshake_server *ctx =
+ (struct _auth_handshake_server *)conn->context;
+ struct _auth_handshake_request *req;
+ string_t *chal_b64;
+
+ if (data_size != 0) {
+ i_error("Bad AUTH PLAIN request: "
+ "Not expecting initial response");
+ return FALSE;
+ }
+
+ req = p_new(conn->pool, struct _auth_handshake_request, 1);
+ req->id = id;
+ DLLIST_PREPEND(&ctx->requests, req);
+
+ chal_b64 = t_str_new(64);
+ base64_encode(prompt1, strlen(prompt1), chal_b64);
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("CONT\t%u\t%s\n", id, str_c(chal_b64)));
+ return TRUE;
+}
+
+static bool
+test_auth_handshake_cont_login(struct server_connection *conn,
+ struct _auth_handshake_request *req,
+ const unsigned char *data, size_t data_size)
+{
+ static const char *prompt2 = "Password:";
+ struct _auth_handshake_server *ctx =
+ (struct _auth_handshake_server *)conn->context;
+ const char *resp = t_strndup(data, data_size);
+ string_t *chal_b64;
+
+ if (++req->login_state == 1) {
+ req->username = p_strdup(conn->pool, resp);
+ if (strcmp(resp, "harrie") != 0) {
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("FAIL\t%u\tuser=%s\n",
+ req->id, req->username));
+ return TRUE;
+ }
+ } else {
+ i_assert(req->login_state == 2);
+ DLLIST_REMOVE(&ctx->requests, req);
+ if (strcmp(resp, "frop") != 0) {
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("FAIL\t%u\tuser=%s\n",
+ req->id, req->username));
+ return TRUE;
+ }
+ o_stream_nsend_str(
+ conn->conn.output,
+ t_strdup_printf("OK\t%u\tuser=harrie\n", req->id));
+ return TRUE;
+ }
+
+ chal_b64 = t_str_new(64);
+ base64_encode(prompt2, strlen(prompt2), chal_b64);
+ o_stream_nsend_str(conn->conn.output,
+ t_strdup_printf("CONT\t%u\t%s\n",
+ req->id, str_c(chal_b64)));
+ return TRUE;
+}
+
+
+static bool
+test_auth_handshake_auth(struct server_connection *conn, unsigned int id,
+ const char *const *args)
+{
+ const char *mech, *resp;
+ unsigned int i;
+ buffer_t *data;
+
+ if (args[0] == NULL) {
+ i_error("Bad AUTH request");
+ return FALSE;
+ }
+ mech = args[0];
+ resp = NULL;
+ for (i = 1; args[i] != NULL; i++) {
+ if (str_begins(args[i], "resp=")) {
+ resp = t_strdup(args[i] + 5);
+ break;
+ }
+ }
+ data = t_buffer_create(256);
+ if (resp != NULL) {
+ if (base64_decode(resp, strlen(resp), NULL, data) < 0) {
+ i_error("Bad AUTH request: Bad base64");
+ return FALSE;
+ }
+ }
+
+ if (strcasecmp(mech, "PLAIN") == 0) {
+ return test_auth_handshake_auth_plain(conn, id,
+ data->data, data->used);
+ } else if (strcasecmp(mech, "LOGIN") == 0) {
+ return test_auth_handshake_auth_login(conn, id,
+ data->data, data->used);
+ }
+ i_error("Bad AUTH request: Unknown mechanism");
+ return FALSE;
+}
+
+static bool
+test_auth_handshake_cont(struct server_connection *conn, unsigned int id,
+ const char *const *args)
+{
+ struct _auth_handshake_server *ctx =
+ (struct _auth_handshake_server *)conn->context;
+ struct _auth_handshake_request *req;
+ const char *resp;
+ buffer_t *data;
+
+ if (args[0] == NULL) {
+ i_error("Bad CONT request");
+ return FALSE;
+ }
+ resp = args[0];
+ data = t_buffer_create(256);
+ if (resp != NULL) {
+ if (base64_decode(resp, strlen(resp), NULL, data) < 0) {
+ i_error("Bad CONT request: Bad base64");
+ return FALSE;
+ }
+ }
+
+ req = ctx->requests;
+ while (req != NULL) {
+ if (req->id == id)
+ break;
+ req = req->next;
+ }
+
+ if (req == NULL) {
+ i_error("Bad CONT request: Bad request ID");
+ return FALSE;
+ }
+
+ return test_auth_handshake_cont_login(conn, req,
+ data->data, data->used);
+}
+
+static void test_auth_handshake_input(struct server_connection *conn)
+{
+ struct _auth_handshake_server *ctx =
+ (struct _auth_handshake_server *)conn->context;
+ const char *const *args;
+ unsigned int id;
+ const char *line;
+
+ for (;;) {
+ line = i_stream_read_next_line(conn->conn.input);
+ if (line == NULL) {
+ if (conn->conn.input->eof)
+ server_connection_deinit(&conn);
+ return;
+ }
+
+ switch (ctx->state) {
+ case AUTH_HANDSHAKE_STATE_VERSION:
+ if (!str_begins(line, "VERSION\t")) {
+ i_error("Bad VERSION");
+ server_connection_deinit(&conn);
+ return;
+ }
+ ctx->state = AUTH_HANDSHAKE_STATE_CMD;
+ continue;
+ case AUTH_HANDSHAKE_STATE_CMD:
+ args = t_strsplit_tabescaped(line);
+ if (args[0] == NULL || args[1] == NULL) {
+ i_error("Bad request");
+ server_connection_deinit(&conn);
+ return;
+ }
+ if (str_to_uint(args[1], &id) < 0) {
+ i_error("Bad %s request", args[0]);
+ server_connection_deinit(&conn);
+ return;
+ }
+
+ if (strcmp(args[0], "CPID") == 0) {
+ continue;
+ } else if (strcmp(args[0], "AUTH") == 0) {
+ if (test_auth_handshake_auth(conn, id,
+ args + 2))
+ continue;
+ } else if (strcmp(args[0], "CONT") == 0) {
+ if (test_auth_handshake_cont(conn, id,
+ args + 2))
+ continue;
+ } else {
+ i_error("Bad request: %s", args[0]);
+ server_connection_deinit(&conn);
+ return;
+ }
+ server_connection_deinit(&conn);
+ return;
+ }
+ i_unreached();
+ }
+}
+
+static void test_auth_handshake_init(struct server_connection *conn)
+{
+ struct _auth_handshake_server *ctx;
+
+ ctx = p_new(conn->pool, struct _auth_handshake_server, 1);
+ conn->context = (void*)ctx;
+
+ o_stream_nsend_str(
+ conn->conn.output,
+ "VERSION\t1\t2\n"
+ "MECH\tPLAIN\tplaintext\n"
+ "MECH\tLOGIN\tplaintext\n"
+ "SPID\t12296\n"
+ "CUID\t2\n"
+ "COOKIE\t46cc85ccd2833ca39a49c059fa3d3ccf\n"
+ "DONE\n");
+}
+
+static void test_server_auth_handshake(void)
+{
+ test_server_init = test_auth_handshake_init;
+ test_server_input = test_auth_handshake_input;
+ test_server_run();
+}
+
+/* client */
+
+static bool test_client_auth_plain_disconnect(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "hendrik", "frop", FALSE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Internal failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_plain_reconnect(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "hendrik", "frop", TRUE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Internal failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_plain_failure(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "henk", "frop", FALSE, &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_plain_success(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("PLAIN", "harrie", "frop", FALSE, &error);
+ test_out("run (ret == 0)", ret == 0);
+ test_assert(error == NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_failure1(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("LOGIN", "henk", "frop", FALSE, &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_failure2(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("LOGIN", "harrie", "friep", FALSE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_success(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_simple("LOGIN", "harrie", "frop", FALSE, &error);
+ test_out("run (ret == 0)", ret == 0);
+ test_assert(error == NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_plain_parallel_failure(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_parallel("PLAIN", "henk", "frop", 4, FALSE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_plain_parallel_success(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_parallel("PLAIN", "harrie", "frop", 4, FALSE,
+ &error);
+ test_out("run (ret == 0)", ret == 0);
+ test_assert(error == NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_parallel_failure1(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_parallel("LOGIN", "henk", "frop", 4, FALSE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_parallel_failure2(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_parallel("LOGIN", "harrie", "friep", 4, FALSE,
+ &error);
+ test_out("run (ret < 0)", ret < 0);
+ test_assert(error != NULL && strstr(error, "Login failure") != NULL);
+
+ return FALSE;
+}
+
+static bool test_client_auth_login_parallel_success(void)
+{
+ const char *error;
+ int ret;
+
+ ret = test_client_auth_parallel("LOGIN", "harrie", "frop", 4, FALSE,
+ &error);
+ test_out("run (ret == 0)", ret == 0);
+ test_assert(error == NULL);
+
+ return FALSE;
+}
+
+/* test */
+
+static void test_auth_handshake(void)
+{
+ test_begin("auth PLAIN disconnect");
+ test_expect_errors(1);
+ test_run_client_server(test_client_auth_plain_disconnect,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth PLAIN reconnect");
+ test_expect_errors(2);
+ test_run_client_server(test_client_auth_plain_reconnect,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth PLAIN failure");
+ test_run_client_server(test_client_auth_plain_failure,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth PLAIN success");
+ test_run_client_server(test_client_auth_plain_success,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN failure 1");
+ test_run_client_server(test_client_auth_login_failure1,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN failure 2");
+ test_run_client_server(test_client_auth_login_failure2,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN success");
+ test_run_client_server(test_client_auth_login_success,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth PLAIN parallel failure");
+ test_run_client_server(test_client_auth_plain_parallel_failure,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth PLAIN parallel success");
+ test_run_client_server(test_client_auth_plain_parallel_success,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN parallel failure 1");
+ test_run_client_server(test_client_auth_login_parallel_failure1,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN parallel failure 2");
+ test_run_client_server(test_client_auth_login_parallel_failure2,
+ test_server_auth_handshake);
+ test_end();
+
+ test_begin("auth LOGIN parallel success");
+ test_run_client_server(test_client_auth_login_parallel_success,
+ test_server_auth_handshake);
+ test_end();
+}
+
+/*
+ * All tests
+ */
+
+static void (*const test_functions[])(void) = {
+ test_connection_refused,
+ test_connection_timed_out,
+ test_bad_version,
+ test_disconnect_version,
+ test_auth_handshake,
+ NULL
+};
+
+/*
+ * Test client
+ */
+
+struct timeout *to_client_progress = NULL;
+
+static void test_client_deinit(void)
+{
+}
+
+struct login_request {
+ struct login_test *test;
+
+ unsigned int state;
+};
+
+struct login_test {
+ char *error;
+ int status;
+
+ const char *username;
+ const char *password;
+
+ unsigned int requests_pending;
+
+ struct ioloop *ioloop;
+};
+
+static void
+test_client_auth_callback(struct auth_client_request *request,
+ enum auth_request_status status,
+ const char *data_base64 ATTR_UNUSED,
+ const char *const *args ATTR_UNUSED, void *context)
+{
+ struct login_request *login_req = context;
+ struct login_test *login_test = login_req->test;
+ string_t *resp_b64;
+ const char *errormsg = NULL;
+
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ switch (status) {
+ case AUTH_REQUEST_STATUS_ABORT:
+ errormsg = "Abort";
+ break;
+ case AUTH_REQUEST_STATUS_INTERNAL_FAIL:
+ errormsg = "Internal failure";
+ break;
+ case AUTH_REQUEST_STATUS_FAIL:
+ errormsg = "Login failure";
+ break;
+ case AUTH_REQUEST_STATUS_CONTINUE:
+ resp_b64 = t_str_new(64);
+ if (++login_req->state == 1) {
+ base64_encode(login_test->username,
+ strlen(login_test->username), resp_b64);
+ } else {
+ test_assert(login_req->state == 2);
+ base64_encode(login_test->password,
+ strlen(login_test->password), resp_b64);
+ }
+ auth_client_request_continue(request, str_c(resp_b64));
+ return;
+ case AUTH_REQUEST_STATUS_OK:
+ break;
+ }
+
+ if (login_test->status == 0 && errormsg != NULL) {
+ i_assert(login_test->error == NULL);
+ login_test->error = i_strdup(errormsg);
+ login_test->status = -1;
+ }
+
+ if (--login_test->requests_pending == 0)
+ io_loop_stop(login_test->ioloop);
+}
+
+static void
+test_client_auth_connected(struct auth_client *client ATTR_UNUSED,
+ bool connected, void *context)
+{
+ struct login_test *login_test = context;
+
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ if (login_test->status == 0 && !connected) {
+ i_assert(login_test->error == NULL);
+ login_test->error = i_strdup("Connection failed");
+ login_test->status = -1;
+ }
+ io_loop_stop(login_test->ioloop);
+}
+
+static void test_client_progress_timeout(void *context ATTR_UNUSED)
+{
+ /* Terminate test due to lack of progress */
+ test_assert(FALSE);
+ timeout_remove(&to_client_progress);
+ io_loop_stop(current_ioloop);
+}
+
+static int
+test_client_auth_run(struct auth_client *auth_client, struct ioloop *ioloop,
+ struct auth_request_info *info,
+ const char *username, const char *password,
+ unsigned int concurrency, const char **error_r)
+{
+ struct login_test login_test;
+ struct login_request *login_reqs;
+ unsigned int i;
+ int ret;
+
+ i_zero(&login_test);
+ login_test.ioloop = ioloop;
+ login_test.username = username;
+ login_test.password = password;
+
+ auth_client_set_connect_timeout(auth_client, 1000);
+ auth_client_connect(auth_client);
+ if (auth_client_is_disconnected(auth_client)) {
+ login_test.error = i_strdup("Connection failed");
+ login_test.status = -1;
+ } else {
+ auth_client_set_connect_notify(
+ auth_client, test_client_auth_connected, &login_test);
+ io_loop_run(ioloop);
+ }
+
+ if (login_test.status >= 0) {
+ io_loop_set_running(ioloop);
+ login_test.requests_pending = concurrency;
+ login_reqs = t_new(struct login_request, concurrency);
+ for (i = 0; i < concurrency; i++) {
+ login_reqs[i].test = &login_test;
+ (void)auth_client_request_new(auth_client, info,
+ test_client_auth_callback,
+ &login_reqs[i]);
+ }
+ if (io_loop_is_running(ioloop))
+ io_loop_run(ioloop);
+ }
+
+ ret = login_test.status;
+ *error_r = t_strdup(login_test.error);
+
+ i_free(login_test.error);
+ auth_client_set_connect_notify(auth_client, NULL, NULL);
+
+ return ret;
+}
+
+static int
+test_client_auth_parallel(const char *mech, const char *username,
+ const char *password, unsigned int concurrency,
+ bool retry, const char **error_r)
+{
+ struct auth_client *auth_client;
+ struct auth_request_info info;
+ struct ioloop *ioloop;
+ int ret;
+
+ i_zero(&info);
+ info.mech = mech;
+ info.service = "test";
+ info.session_id = "23423dfd243daaa223";
+ info.flags = AUTH_REQUEST_FLAG_SECURED;
+
+ (void)net_addr2ip("10.0.0.15", &info.local_ip);
+ info.local_port = 143;
+ (void)net_addr2ip("10.0.0.211", &info.remote_ip);
+ info.remote_port = 45546;
+ (void)net_addr2ip("10.1.0.54", &info.real_local_ip);
+ info.real_local_port = 143;
+ (void)net_addr2ip("10.1.0.221", &info.real_remote_ip);
+ info.real_remote_port = 23246;
+
+ if (strcasecmp(mech, "PLAIN") == 0) {
+ string_t *resp_b64, *resp;
+
+ resp = t_str_new(64);
+ str_append(resp, "supremelordoftheuniverse");
+ str_append_c(resp, '\0');
+ str_append(resp, username);
+ str_append_c(resp, '\0');
+ str_append(resp, password);
+
+ resp_b64 = t_str_new(64);
+ base64_encode(str_data(resp), str_len(resp), resp_b64);
+ info.initial_resp_base64 = str_c(resp_b64);
+ } else if (strcasecmp(mech, "LOGIN") == 0) {
+ /* no intial response */
+ } else {
+ i_unreached();
+ }
+
+ ioloop = io_loop_create();
+ to_client_progress = timeout_add(CLIENT_PROGRESS_TIMEOUT*1000,
+ test_client_progress_timeout, NULL);
+
+ auth_client = auth_client_init(TEST_SOCKET, 2234, debug);
+ ret = test_client_auth_run(auth_client, ioloop, &info,
+ username, password, concurrency,
+ error_r);
+ if (ret < 0 && retry) {
+ ret = test_client_auth_run(auth_client, ioloop, &info,
+ username, password, concurrency,
+ error_r);
+ }
+ auth_client_deinit(&auth_client);
+
+ timeout_remove(&to_client_progress);
+ io_loop_destroy(&ioloop);
+
+ return ret;
+}
+
+static int
+test_client_auth_simple(const char *mech, const char *username,
+ const char *password, bool retry, const char **error_r)
+{
+ return test_client_auth_parallel(mech, username, password, 1, retry,
+ error_r);
+}
+
+/*
+ * Test server
+ */
+
+/* client connection */
+
+static void server_connection_input(struct connection *_conn)
+{
+ struct server_connection *conn = (struct server_connection *)_conn;
+
+ test_server_input(conn);
+}
+
+static void server_connection_init(int fd)
+{
+ struct server_connection *conn;
+ pool_t pool;
+
+ net_set_nonblock(fd, TRUE);
+
+ pool = pool_alloconly_create("server connection", 256);
+ conn = p_new(pool, struct server_connection, 1);
+ conn->pool = pool;
+
+ connection_init_server(server_conn_list, &conn->conn,
+ "server connection", fd, fd);
+
+ if (test_server_init != NULL)
+ test_server_init(conn);
+}
+
+static void server_connection_deinit(struct server_connection **_conn)
+{
+ struct server_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (test_server_deinit != NULL)
+ test_server_deinit(conn);
+
+ connection_deinit(&conn->conn);
+ pool_unref(&conn->pool);
+}
+
+static void server_connection_destroy(struct connection *_conn)
+{
+ struct server_connection *conn = (struct server_connection *)_conn;
+
+ server_connection_deinit(&conn);
+}
+
+static void server_connection_accept(void *context ATTR_UNUSED)
+{
+ int fd;
+
+ /* accept new client */
+ fd = net_accept(fd_listen, NULL, NULL);
+ if (fd == -1)
+ return;
+ if (fd == -2)
+ i_fatal("test server: accept() failed: %m");
+
+ server_connection_init(fd);
+}
+
+/* */
+
+static struct connection_settings server_connection_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs server_connection_vfuncs = {
+ .destroy = server_connection_destroy,
+ .input = server_connection_input
+};
+
+static void test_server_run(void)
+{
+ /* open server socket */
+ io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL);
+
+ server_conn_list = connection_list_init(&server_connection_set,
+ &server_connection_vfuncs);
+
+ io_loop_run(ioloop);
+
+ /* close server socket */
+ io_remove(&io_listen);
+
+ connection_list_deinit(&server_conn_list);
+}
+
+/*
+ * Tests
+ */
+
+static int test_open_server_fd(void)
+{
+ int fd;
+ i_unlink_if_exists(TEST_SOCKET);
+ fd = net_listen_unix(TEST_SOCKET, 128);
+ if (debug)
+ i_debug("server listening on "TEST_SOCKET);
+ if (fd == -1)
+ i_fatal("listen("TEST_SOCKET") failed: %m");
+ return fd;
+}
+
+static int test_run_server(test_server_init_t *server_test)
+{
+ main_deinit();
+ master_service_deinit_forked(&master_service);
+
+ i_set_failure_prefix("SERVER: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ ioloop = io_loop_create();
+ server_test();
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+
+ i_close_fd(&fd_listen);
+ return 0;
+}
+
+static void test_run_client(test_client_init_t *client_test)
+{
+ i_set_failure_prefix("CLIENT: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ i_sleep_intr_msecs(100); /* wait a little for server setup */
+
+ ioloop = io_loop_create();
+ if (client_test())
+ io_loop_run(ioloop);
+ test_client_deinit();
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+}
+
+static void
+test_run_client_server(test_client_init_t *client_test,
+ test_server_init_t *server_test)
+{
+ if (server_test != NULL) {
+ /* Fork server */
+ fd_listen = test_open_server_fd();
+ test_subprocess_fork(test_run_server, server_test, FALSE);
+ i_close_fd(&fd_listen);
+ }
+
+ /* Run client */
+ test_run_client(client_test);
+
+ i_unset_failure_prefix();
+ test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS);
+}
+
+/*
+ * Main
+ */
+
+static void main_cleanup(void)
+{
+ i_unlink_if_exists(TEST_SOCKET);
+}
+
+static void main_init(void)
+{
+ /* nothing yet */
+}
+
+static void main_deinit(void)
+{
+ /* nothing yet; also called from sub-processes */
+}
+
+int main(int argc, char *argv[])
+{
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+ MASTER_SERVICE_FLAG_NO_SSL_INIT;
+ int c;
+ int ret;
+
+ master_service = master_service_init("test-auth-master", service_flags,
+ &argc, &argv, "D");
+ main_init();
+
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D]", argv[0]);
+ }
+ }
+
+ master_service_init_finish(master_service);
+ test_subprocesses_init(debug);
+ test_subprocess_set_cleanup_callback(main_cleanup);
+
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ main_deinit();
+ master_service_deinit(&master_service);
+
+ return ret;
+}