diff options
Diffstat (limited to '')
-rw-r--r-- | src/master/test-auth-client.c | 1287 |
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; +} |