-/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
-#include "auth-master.h"
-#include "net.h"
-#include "test-common.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-interface.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+
+#include "auth-master.h"
+
+#include <unistd.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);
static void test_auth_user_info_export(void)
{
test_end();
}
-int main(void)
+/*
+ * 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_with(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_with(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_with(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_with(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)
{
- static void (*const test_functions[])(void) = {
- test_auth_user_info_export,
- NULL
- };
- return test_run(test_functions);
+ 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_with(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_with(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_auth_user_info_export,
+ 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();
+
+ 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[])
+{
+ int c;
+ int ret;
+
+ lib_init();
+ main_init();
+
+ while ((c = getopt(argc, argv, "D")) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D]", argv[0]);
+ }
+ }
+
+ test_subprocesses_init(debug);
+ test_subprocess_set_cleanup_callback(main_cleanup);
+
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ main_deinit();
+ lib_deinit();
+ return ret;
}
+++ /dev/null
-/* 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-interface.h"
-#include "test-common.h"
-#include "test-subprocess.h"
-
-#include "auth-master.h"
-
-#include <unistd.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_with(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_with(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_with(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_with(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_with(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_with(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();
-
- 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[])
-{
- int c;
- int ret;
-
- lib_init();
- main_init();
-
- while ((c = getopt(argc, argv, "D")) > 0) {
- switch (c) {
- case 'D':
- debug = TRUE;
- break;
- default:
- i_fatal("Usage: %s [-D]", argv[0]);
- }
- }
-
- test_subprocesses_init(debug);
- test_subprocess_set_cleanup_callback(main_cleanup);
-
- ret = test_run(test_functions);
-
- test_subprocesses_deinit();
- main_deinit();
- lib_deinit();
- return ret;
-}