diff options
Diffstat (limited to 'src/master/test-auth-master.c')
-rw-r--r-- | src/master/test-auth-master.c | 1390 |
1 files changed, 1390 insertions, 0 deletions
diff --git a/src/master/test-auth-master.c b/src/master/test-auth-master.c new file mode 100644 index 0000000..08112d4 --- /dev/null +++ b/src/master/test-auth-master.c @@ -0,0 +1,1390 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "strescape.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-master.h" + +#define TEST_SOCKET "./auth-master-test" +#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_passdb_lookup_simple(const char *user, bool retry, + const char **error_r); +static int +test_client_userdb_lookup_simple(const char *user, bool retry, + const char **error_r); +static int test_client_user_list_simple(void); + +/* 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_passdb_lookup_simple("harrie", 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_passdb_lookup_simple("harrie", 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("Connecting timed out"); + 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"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\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_passdb_lookup_simple("harrie", 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\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\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_passdb_lookup_simple("harrie", 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_error_string("Disconnected unexpectedly"); + test_run_client_server(test_client_disconnect_version, + test_server_disconnect_version); + test_end(); +} + +/* + * Passdb FAIL + */ + +/* server */ + +enum _passdb_fail_state { + PASSDB_FAIL_STATE_VERSION = 0, + PASSDB_FAIL_STATE_PASS +}; + +struct _passdb_fail_server { + enum _passdb_fail_state state; + + bool not_found:1; +}; + +static void test_passdb_fail_input(struct server_connection *conn) +{ + struct _passdb_fail_server *ctx = + (struct _passdb_fail_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 PASSDB_FAIL_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = PASSDB_FAIL_STATE_PASS; + continue; + case PASSDB_FAIL_STATE_PASS: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "PASS") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0 || args[2] == NULL) { + i_error("Bad PASS request"); + server_connection_deinit(&conn); + return; + } + if (strcmp(args[2], "henk") == 0) { + line = t_strdup_printf("NOTFOUND\t%u\n", id); + } else if (strcmp(args[2], "holger") == 0) { + i_sleep_intr_secs(5); + server_connection_deinit(&conn); + return; + } else if (strcmp(args[2], "hendrik") == 0) { + server_connection_deinit(&conn); + return; + } else { + line = t_strdup_printf( + "FAIL\t%u\t" + "reason=You shall not pass!!\n", id); + } + o_stream_nsend_str(conn->conn.output, line); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_passdb_fail_init(struct server_connection *conn) +{ + struct _passdb_fail_server *ctx; + + ctx = p_new(conn->pool, struct _passdb_fail_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_passdb_fail(void) +{ + test_server_init = test_passdb_fail_init; + test_server_input = test_passdb_fail_input; + test_server_run(); +} + +/* client */ + +static bool test_client_passdb_fail(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("harrie", FALSE, &error); + test_out("run (ret == -2)", ret == -2); + test_assert(error != NULL && + strcmp(error, "You shall not pass!!") == 0); + + return FALSE; +} + +static bool test_client_passdb_notfound(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("henk", FALSE, &error); + test_out("run (ret == 0)", ret == 0); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_passdb_timeout(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("holger", FALSE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_passdb_disconnect(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("hendrik", FALSE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_passdb_reconnect(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("hendrik", TRUE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +/* test */ + +static void test_passdb_fail(void) +{ + test_begin("passdb fail"); + test_run_client_server(test_client_passdb_fail, + test_server_passdb_fail); + test_end(); + + test_begin("passdb notfound"); + test_run_client_server(test_client_passdb_notfound, + test_server_passdb_fail); + test_end(); + + test_begin("passdb timeout"); + test_expect_error_string("Request timed out"); + test_run_client_server(test_client_passdb_timeout, + test_server_passdb_fail); + test_end(); + + test_begin("passdb disconnect"); + test_expect_error_string("Disconnected unexpectedly"); + test_run_client_server(test_client_passdb_disconnect, + test_server_passdb_fail); + test_end(); + + test_begin("passdb reconnect"); + test_expect_errors(2); + test_run_client_server(test_client_passdb_reconnect, + test_server_passdb_fail); + test_end(); +} + +/* + * Userdb FAIL + */ + +/* server */ + +enum _userdb_fail_state { + USERDB_FAIL_STATE_VERSION = 0, + USERDB_FAIL_STATE_USER +}; + +struct _userdb_fail_server { + enum _userdb_fail_state state; + + bool not_found:1; +}; + +static void test_userdb_fail_input(struct server_connection *conn) +{ + struct _userdb_fail_server *ctx = + (struct _userdb_fail_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 USERDB_FAIL_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = USERDB_FAIL_STATE_USER; + continue; + case USERDB_FAIL_STATE_USER: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "USER") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + i_error("Bad USER request"); + server_connection_deinit(&conn); + return; + } + if (strcmp(args[2], "henk") == 0) { + line = t_strdup_printf("NOTFOUND\t%u\n", id); + } else if (strcmp(args[2], "holger") == 0) { + i_sleep_intr_secs(5); + server_connection_deinit(&conn); + return; + } else if (strcmp(args[2], "hendrik") == 0) { + server_connection_deinit(&conn); + return; + } else { + line = t_strdup_printf("FAIL\t%u\t" + "reason=It is no use!\n", id); + } + o_stream_nsend_str(conn->conn.output, line); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_userdb_fail_init(struct server_connection *conn) +{ + struct _userdb_fail_server *ctx; + + ctx = p_new(conn->pool, struct _userdb_fail_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_userdb_fail(void) +{ + test_server_init = test_userdb_fail_init; + test_server_input = test_userdb_fail_input; + test_server_run(); +} + +/* client */ + +static bool test_client_userdb_fail(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("harrie", FALSE, &error); + test_out("run (ret == -2)", ret == -2); + test_assert(error != NULL && + strcmp(error, "It is no use!") == 0); + + return FALSE; +} + +static bool test_client_userdb_notfound(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("henk", FALSE, &error); + test_out("run (ret == 0)", ret == 0); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_userdb_timeout(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("holger", FALSE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_userdb_disconnect(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("hendrik", FALSE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +static bool test_client_userdb_reconnect(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("hendrik", TRUE, &error); + test_out("run (ret == -1)", ret == -1); + test_assert(error == NULL); + + return FALSE; +} + +/* test */ + +static void test_userdb_fail(void) +{ + test_begin("userdb fail"); + test_run_client_server(test_client_userdb_fail, + test_server_userdb_fail); + test_end(); + + test_begin("userdb notfound"); + test_run_client_server(test_client_userdb_notfound, + test_server_userdb_fail); + test_end(); + + test_begin("userdb timeout"); + test_expect_error_string("Request timed out"); + test_run_client_server(test_client_userdb_timeout, + test_server_userdb_fail); + test_end(); + + test_begin("userdb disconnect"); + test_expect_error_string("Disconnected unexpectedly"); + test_run_client_server(test_client_userdb_disconnect, + test_server_userdb_fail); + test_end(); + + test_begin("userdb reconnect"); + test_expect_errors(2); + test_run_client_server(test_client_userdb_reconnect, + test_server_userdb_fail); + test_end(); +} + +/* + * User list FAIL + */ + +/* server */ + +enum _user_list_fail_state { + USER_LIST_FAIL_STATE_VERSION = 0, + USER_LIST_FAIL_STATE_USER +}; + +struct _user_list_fail_server { + enum _user_list_fail_state state; +}; + +static void test_user_list_fail_input(struct server_connection *conn) +{ + struct _user_list_fail_server *ctx = + (struct _user_list_fail_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 USER_LIST_FAIL_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = USER_LIST_FAIL_STATE_USER; + continue; + case USER_LIST_FAIL_STATE_USER: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "LIST") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + i_error("Bad LIST request"); + server_connection_deinit(&conn); + return; + } + line = t_strdup_printf("DONE\t%u\tfail\n", id); + o_stream_nsend_str(conn->conn.output, line); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_user_list_fail_init(struct server_connection *conn) +{ + struct _user_list_fail_server *ctx; + + ctx = p_new(conn->pool, struct _user_list_fail_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_user_list_fail(void) +{ + test_server_init = test_user_list_fail_init; + test_server_input = test_user_list_fail_input; + test_server_run(); +} + +/* client */ + +static bool test_client_user_list_fail(void) +{ + int ret; + + ret = test_client_user_list_simple(); + test_out("run (ret < 0)", ret < 0); + + return FALSE; +} + +/* test */ + +static void test_user_list_fail(void) +{ + test_begin("user list fail"); + test_expect_errors(1); + test_run_client_server(test_client_user_list_fail, + test_server_user_list_fail); + test_end(); +} + +/* + * Passdb lookup + */ + +/* server */ + +enum _passdb_lookup_state { + PASSDB_LOOKUP_STATE_VERSION = 0, + PASSDB_LOOKUP_STATE_PASS +}; + +struct _passdb_lookup_server { + enum _passdb_lookup_state state; +}; + +static void test_passdb_lookup_input(struct server_connection *conn) +{ + struct _passdb_lookup_server *ctx = + (struct _passdb_lookup_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 PASSDB_LOOKUP_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = PASSDB_LOOKUP_STATE_PASS; + continue; + case PASSDB_LOOKUP_STATE_PASS: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "PASS") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + i_error("Bad PASS request"); + server_connection_deinit(&conn); + return; + } + line = t_strdup_printf("PASS\t%u\tuser=frop\n", id); + o_stream_nsend_str(conn->conn.output, line); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_passdb_lookup_init(struct server_connection *conn) +{ + struct _passdb_lookup_server *ctx; + + ctx = p_new(conn->pool, struct _passdb_lookup_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_passdb_lookup(void) +{ + test_server_init = test_passdb_lookup_init; + test_server_input = test_passdb_lookup_input; + test_server_run(); +} + +/* client */ + +static bool test_client_passdb_lookup(void) +{ + const char *error; + int ret; + + ret = test_client_passdb_lookup_simple("harrie", FALSE, &error); + test_out("run (ret > 0)", ret > 0); + + return FALSE; +} + +/* test */ + +static void test_passdb_lookup(void) +{ + test_begin("passdb lookup"); + test_run_client_server(test_client_passdb_lookup, + test_server_passdb_lookup); + test_end(); +} + +/* + * Userdb lookup + */ + +/* server */ + +enum _userdb_lookup_state { + USERDB_LOOKUP_STATE_VERSION = 0, + USERDB_LOOKUP_STATE_PASS +}; + +struct _userdb_lookup_server { + enum _userdb_lookup_state state; +}; + +static void test_userdb_lookup_input(struct server_connection *conn) +{ + struct _userdb_lookup_server *ctx = + (struct _userdb_lookup_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 USERDB_LOOKUP_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = USERDB_LOOKUP_STATE_PASS; + continue; + case USERDB_LOOKUP_STATE_PASS: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "USER") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + i_error("Bad PASS request"); + server_connection_deinit(&conn); + return; + } + line = t_strdup_printf( + "USER\t%u\tharrie\t" + "uid=1000\tgid=110\thome=/home/harrie\n", id); + o_stream_nsend_str(conn->conn.output, line); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_userdb_lookup_init(struct server_connection *conn) +{ + struct _userdb_lookup_server *ctx; + + ctx = p_new(conn->pool, struct _userdb_lookup_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_userdb_lookup(void) +{ + test_server_init = test_userdb_lookup_init; + test_server_input = test_userdb_lookup_input; + test_server_run(); +} + +/* client */ + +static bool test_client_userdb_lookup(void) +{ + const char *error; + int ret; + + ret = test_client_userdb_lookup_simple("harrie", FALSE, &error); + test_out("run (ret > 0)", ret > 0); + + return FALSE; +} + +/* test */ + +static void test_userdb_lookup(void) +{ + test_begin("userdb lookup"); + test_run_client_server(test_client_userdb_lookup, + test_server_userdb_lookup); + test_end(); +} + +/* + * User list + */ + +/* server */ + +enum _user_list_state { + USER_LIST_STATE_VERSION = 0, + USER_LIST_STATE_USER +}; + +struct _user_list_server { + enum _user_list_state state; +}; + +static void test_user_list_input(struct server_connection *conn) +{ + struct _user_list_server *ctx = + (struct _user_list_server *)conn->context; + const char *line; + const char *const *args; + unsigned int id; + string_t *str; + + 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 USER_LIST_STATE_VERSION: + if (!str_begins(line, "VERSION\t")) { + i_error("Bad VERSION"); + server_connection_deinit(&conn); + return; + } + ctx->state = USER_LIST_STATE_USER; + continue; + case USER_LIST_STATE_USER: + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "LIST") != 0 || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + i_error("Bad LIST request"); + server_connection_deinit(&conn); + return; + } + str = t_str_new(256); + str_printfa(str, "LIST\t%u\tuser1\n", id); + str_printfa(str, "LIST\t%u\tuser2\n", id); + str_printfa(str, "LIST\t%u\tuser3\n", id); + str_printfa(str, "LIST\t%u\tuser4\n", id); + str_printfa(str, "DONE\t%u\n", id); + o_stream_nsend_str(conn->conn.output, str_c(str)); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_user_list_init(struct server_connection *conn) +{ + struct _user_list_server *ctx; + + ctx = p_new(conn->pool, struct _user_list_server, 1); + conn->context = (void*)ctx; + + o_stream_nsend_str(conn->conn.output, "VERSION\t1\t0\n"); + o_stream_nsend_str(conn->conn.output, "SPID\t23234\n"); +} + +static void test_server_user_list(void) +{ + test_server_init = test_user_list_init; + test_server_input = test_user_list_input; + test_server_run(); +} + +/* client */ + +static bool test_client_user_list(void) +{ + int ret; + + ret = test_client_user_list_simple(); + test_out("run (ret == 0)", ret == 0); + + return FALSE; +} + +/* test */ + +static void test_user_list(void) +{ + test_begin("user list"); + test_expect_errors(0); + test_run_client_server(test_client_user_list, + test_server_user_list); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_connection_refused, + test_connection_timed_out, + test_bad_version, + test_disconnect_version, + test_passdb_fail, + test_userdb_fail, + test_user_list_fail, + test_passdb_lookup, + test_userdb_lookup, + test_user_list, + NULL +}; + +/* + * Test client + */ + +static void test_client_deinit(void) +{ +} + +static int +test_client_passdb_lookup_simple(const char *username, bool retry, + const char **error_r) +{ + struct auth_master_connection *auth_conn; + enum auth_master_flags flags = 0; + struct auth_user_info info; + const char *const *fields; + pool_t pool; + int ret; + + i_zero(&info); + info.service = "test"; + info.debug = debug; + + if (debug) + flags |= AUTH_MASTER_FLAG_DEBUG; + + pool = pool_alloconly_create("test", 1024); + + auth_conn = auth_master_init(TEST_SOCKET, flags); + auth_master_set_timeout(auth_conn, 1000); + ret = auth_master_pass_lookup(auth_conn, username, &info, + pool, &fields); + if (ret < 0 && retry) { + ret = auth_master_pass_lookup(auth_conn, username, &info, + pool, &fields); + } + auth_master_deinit(&auth_conn); + + *error_r = (ret < 0 ? t_strdup(fields[0]) : NULL); + pool_unref(&pool); + + return ret; +} + +static int +test_client_userdb_lookup_simple(const char *username, bool retry, + const char **error_r) +{ + struct auth_master_connection *auth_conn; + enum auth_master_flags flags = 0; + struct auth_user_info info; + const char *const *fields; + const char *username_out; + pool_t pool; + int ret; + + i_zero(&info); + info.service = "test"; + info.debug = debug; + + if (debug) + flags |= AUTH_MASTER_FLAG_DEBUG; + + pool = pool_alloconly_create("test", 1024); + + auth_conn = auth_master_init(TEST_SOCKET, flags); + auth_master_set_timeout(auth_conn, 1000); + ret = auth_master_user_lookup(auth_conn, username, &info, + pool, &username_out, &fields); + if (ret < 0 && retry) { + ret = auth_master_user_lookup(auth_conn, username, &info, + pool, &username_out, &fields); + } + auth_master_deinit(&auth_conn); + + *error_r = (ret < 0 ? t_strdup(fields[0]) : NULL); + pool_unref(&pool); + + return ret; +} + +static int test_client_user_list_simple(void) +{ + struct auth_master_connection *auth_conn; + struct auth_master_user_list_ctx *list_ctx; + enum auth_master_flags flags = 0; + struct auth_user_info info; + int ret; + + i_zero(&info); + info.service = "test"; + info.debug = debug; + + if (debug) + flags |= AUTH_MASTER_FLAG_DEBUG; + + auth_conn = auth_master_init(TEST_SOCKET, flags); + auth_master_set_timeout(auth_conn, 1000); + list_ctx = auth_master_user_list_init(auth_conn, "*", &info); + while (auth_master_user_list_next(list_ctx) != NULL); + ret = auth_master_user_list_deinit(&list_ctx); + auth_master_deinit(&auth_conn); + + return ret; +} + +/* + * 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; +} |