/
niceSOFT
/
dovecot
Обзор
Документация
Войти
/
niceSOFT
/
dovecot
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/lib-lua/test-lua-http-client.c
908 строк
22 KB
Michael M Slusarz
COPYING: Simplify and standardize copyright handling
03 июл 2026, 02:25
03 июл 2026, 02:25
2a5809c
Код
Авторство
О чём код?
/* Copyright (c) Dovecot authors, see top-level COPYING file */ #include "lib.h" #include "str.h" #include "str-sanitize.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 "connection.h" #include "master-service.h" #include "master-service-settings.h" #include "iostream-ssl.h" #include "iostream-ssl-test.h" #include "iostream-openssl.h" #include "http-client.h" #include "test-common.h" #include "test-subprocess.h" #include "http-client.h" #include "dlua-script-private.h" #include <unistd.h> #include <sys/signal.h> #define CLIENT_PROGRESS_TIMEOUT 10 #define SERVER_KILL_TIMEOUT_SECS 20 static void main_deinit(void); /* * Types */ struct server_connection { struct connection conn; void *context; struct ssl_iostream *ssl_iostream; struct istream *real_input; struct ostream *real_output; pool_t pool; bool version_sent:1; }; typedef void (*test_server_init_t)(unsigned int index); typedef bool (*test_client_init_t)(void); typedef void (*test_dns_init_t)(void); /* * State */ /* common */ static struct event *common_event; static struct ip_addr bind_ip; static in_port_t *bind_ports = 0; static struct ioloop *ioloop; static bool debug = FALSE; /* server */ static struct event *server_event, *dns_event; static struct io *io_listen; static int fd_listen = -1; static struct connection_list *server_conn_list; static unsigned int server_index; struct ssl_iostream_context *server_ssl_ctx = NULL; bool test_server_ssl = FALSE; static int (*test_server_init)(struct server_connection *conn); static void (*test_server_deinit)(struct server_connection *conn); static void (*test_server_input)(struct server_connection *conn); /* client */ static struct event *client_event; static struct timeout *to_client_progress = NULL; /* * Forward declarations */ /* server */ static void test_server_run(unsigned int index); static void server_connection_deinit(struct server_connection **_conn); /* client */ static void test_client_deinit(void); /* test*/ static void test_run_client_server(test_client_init_t client_test, test_server_init_t server_test, unsigned int server_tests_count, test_dns_init_t dns_test) ATTR_NULL(3); /* * Simple post */ /* dns */ static void test_dns_simple_post_input(struct server_connection *conn) { const char *line; if (!conn->version_sent) { conn->version_sent = TRUE; o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); } while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { if (str_begins_with(line, "VERSION")) continue; e_debug(dns_event, "DNS REQUEST: %s", line); if (strcmp(line, "IP\thosta") == 0) { o_stream_nsend_str(conn->conn.output, "0\t127.0.0.1\n"); } else { i_sleep_msecs(300); o_stream_nsend_str( conn->conn.output, t_strdup_printf("%d\tFAIL\n", EAI_FAIL)); } } } static void test_dns_simple_post(void) { test_server_input = test_dns_simple_post_input; test_server_run(0); } /* server */ struct _simple_post_sctx { struct timeout *to; bool serviced:1; bool eoh:1; bool agent_checked:1; bool post_checked:1; }; static int test_server_simple_post_init(struct server_connection *conn) { struct _simple_post_sctx *ctx; ctx = p_new(conn->pool, struct _simple_post_sctx, 1); conn->context = ctx; return 0; } static void test_server_simple_post_disconnect(struct server_connection *conn) { struct _simple_post_sctx *ctx = conn->context; timeout_remove(&ctx->to); server_connection_deinit(&conn); } static void test_server_simple_post_input(struct server_connection *conn) { struct _simple_post_sctx *ctx = conn->context; const char *line; if (ctx->serviced) { /* Wait for disconnect or beginning of next request */ ssize_t sret = i_stream_read(conn->conn.input); if (sret > 0 || conn->conn.input->eof) server_connection_deinit(&conn); return; } while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { const char *agent; if (*line == '\0') { ctx->eoh = TRUE; o_stream_nsend_str(conn->conn.output, "HTTP/1.1 100 OK\r\n\r\n"); return; } if (!ctx->post_checked) { test_assert(str_begins_with(line, "POST /")); ctx->post_checked = TRUE; } if (!ctx->agent_checked && str_begins(line, "User-Agent: ", &agent)) { test_assert_strcmp(agent, "dovecot/unit-test"); ctx->agent_checked = TRUE; } if (strcmp(line, "some+foolish+payload+for+funsies") == 0) break; } if (conn->conn.input->stream_errno != 0) { i_fatal("server: Stream error: %s", i_stream_get_error(conn->conn.input)); } if (line == NULL) { if (conn->conn.input->eof) server_connection_deinit(&conn); return; } test_assert(ctx->post_checked); test_assert(ctx->agent_checked); i_assert(ctx->eoh); ctx->eoh = FALSE; static const char json_response[] = "{\n" "\"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n" "\"token_type\":\"Bearer\",\n" "\"expires_in\":3600,\n" "\"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n" "\"scope\":\"create\"\n" "}"; string_t *resp = t_str_new(512); str_printfa(resp, "HTTP/1.1 200 OK\r\n" "Content-Length: %zu\r\n" "\r\n" "%s", sizeof(json_response) - 1, json_response); o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); if (o_stream_flush(conn->conn.output) < 0) { i_fatal("server: Flush error: %s", o_stream_get_error(conn->conn.output)); } ctx->serviced = TRUE; ctx->to = timeout_add( 5000, test_server_simple_post_disconnect, conn); } static void test_server_simple_post_deinit(struct server_connection *conn) { struct _simple_post_sctx *ctx = conn->context; timeout_remove(&ctx->to); } static void test_server_simple_post(unsigned int index) { test_server_init = test_server_simple_post_init; test_server_input = test_server_simple_post_input; test_server_deinit = test_server_simple_post_deinit; test_server_run(index); } /* client */ static void test_client_simple_post_run_post(struct dlua_script *script, const char *url) { const char *error; int ret; lua_pushstring(script->L, url); if (dlua_pcall(script->L, "http_request_post", 1, 1, &error) < 0) i_fatal("dlua_pcall() failed: %s", error); test_assert(lua_isinteger(script->L, -1)); if (lua_isinteger(script->L, -1)) { ret = lua_tointeger(script->L, -1); /* not guaranteed to fail, but it will happen often */ e_debug(client_event, "http_request_post() returned %d", ret); test_assert(ret == 0); } lua_pop(script->L, 1); i_assert(lua_gettop(script->L) == 0); } static bool test_client_simple_post(void) { struct dlua_script *script; const char *error; if (event_want_debug(client_event)) test_expect_errors(4); if (dlua_script_create_file( TEST_LUA_SCRIPT_DIR "/test-lua-http-client.lua", &script, client_event, &error) < 0) i_fatal("dlua_script_create_file() failed: %s", error); dlua_dovecot_register(script); if (dlua_script_init(script, &error) < 0) i_fatal("dlua_script_init() failed: %s", error); /* First POST */ test_client_simple_post_run_post( script, t_strdup_printf("http%s://hosta:%u/first-post", test_server_ssl ? "s" : "", bind_ports[0])); /* Second POST */ test_client_simple_post_run_post( script, t_strdup_printf("http%s://hosta:%u/second-post", test_server_ssl ? "s" : "", bind_ports[0])); dlua_script_unref(&script); return TRUE; } static bool test_client_second_post(void) { struct dlua_script *script; const char *error; if (event_want_debug(client_event)) test_expect_errors(4); if (dlua_script_create_file( TEST_LUA_SCRIPT_DIR "/test-lua-http-client.lua", &script, client_event, &error) < 0) i_fatal("dlua_script_create_file() failed: %s", error); dlua_dovecot_register(script); if (dlua_script_init(script, &error) < 0) i_fatal("dlua_script_init() failed: %s", error); /* First POST */ test_client_simple_post_run_post( script, t_strdup_printf("http%s://hosta:%u/first-post", (test_server_ssl ? "s" : ""), bind_ports[0])); /* Second POST */ test_client_simple_post_run_post( script, t_strdup_printf("http%s://hosta:%u/second-post", (test_server_ssl ? "s" : ""), bind_ports[0])); dlua_script_unref(&script); return TRUE; } /* test */ static void test_simple_post(void) { test_begin("simple post"); test_server_ssl = FALSE; test_run_client_server(test_client_simple_post, test_server_simple_post, 1, test_dns_simple_post); test_end(); test_begin("simple post (ssl)"); test_server_ssl = TRUE; test_run_client_server(test_client_simple_post, test_server_simple_post, 1, test_dns_simple_post); test_end(); } static void test_second_post(void) { test_begin("second post"); test_server_ssl = FALSE; test_run_client_server(test_client_second_post, test_server_simple_post, 1, test_dns_simple_post); test_end(); test_begin("second post (ssl)"); test_server_ssl = TRUE; test_run_client_server(test_client_second_post, test_server_simple_post, 1, test_dns_simple_post); test_end(); } static void test_bad_settings(void) { struct dlua_script *script; const char *error; test_begin("bad settings"); if (event_want_debug(common_event)) test_expect_errors(4); if (dlua_script_create_file( TEST_LUA_SCRIPT_DIR "/test-lua-http-client.lua", &script, common_event, &error) < 0) i_fatal("dlua_script_create_file() failed: %s", error); dlua_dovecot_register(script); if (dlua_script_init(script, &error) < 0) i_fatal("dlua_script_init() failed: %s", error); int ret = dlua_pcall(script->L, "test_invalid_set_name", 0, 0, &error); test_assert(ret < 0); error = t_strcut(error, '\n'); /* check the error is there */ test_assert_strcmp(error, "lua_pcall(test_invalid_set_name, 0, 0) failed: " "Invalid HTTP client setting: timeout is unknown setting"); ret = dlua_pcall(script->L, "test_invalid_set_value_1", 0, 0, &error); test_assert(ret < 0); error = t_strcut(error, '\n'); test_assert_strcmp(error, "lua_pcall(test_invalid_set_value_1, 0, 0) failed: " "Invalid HTTP client setting: auto_retry=cow: Invalid boolean value: cow (use yes or no)"); ret = dlua_pcall(script->L, "test_invalid_set_value_2", 0, 0, &error); test_assert(ret < 0); error = t_strcut(error, '\n'); test_assert_strcmp(error, "lua_pcall(test_invalid_set_value_2, 0, 0) failed: " "Invalid HTTP client setting: request_max_attempts=three: Invalid number three: Not a valid number"); /* This needs a bit more roundabout way to check this as SSL settings are lazily evaluated. */ test_assert(dlua_pcall(script->L, "test_invalid_set_value_3", 0, 0, &error) == 0); lua_pushstring(script->L, "https://localhost"); test_assert(dlua_pcall(script->L, "http_request_post", 1, 2, &error) == 2); error = lua_tostring(script->L, 2); test_assert_strcmp(error, "Couldn't initialize SSL client context: Can't set minimum protocol to 'cow' (ssl_min_protocol setting): Unknown value"); dlua_script_unref(&script); test_end(); } static void test_set_event(void) { struct dlua_script *script; const char *error; test_begin("set event"); if (dlua_script_create_file( TEST_LUA_SCRIPT_DIR "/test-lua-http-client.lua", &script, common_event, &error) < 0) i_fatal("dlua_script_create_file() failed: %s", error); dlua_dovecot_register(script); if (dlua_script_init(script, &error) < 0) i_fatal("dlua_script_init() failed: %s", error); struct event *event = event_create(NULL); event_set_forced_debug(event, TRUE); dlua_push_event(script->L, event); lua_pushstring(script->L, "https://localhost"); test_assert(dlua_pcall(script->L, "test_http_request_set_event", 2, 1, &error) == 1); struct event *child_event = dlua_check_event(script->L, 1); test_assert(event_get_parent(child_event) == event); test_assert(event_get_parent(child_event) != common_event); event_unref(&event); dlua_script_unref(&script); test_end(); } static void test_set_no_event(void) { struct dlua_script *script; const char *error; test_begin("set no event"); if (dlua_script_create_file( TEST_LUA_SCRIPT_DIR "/test-lua-http-client.lua", &script, common_event, &error) < 0) i_fatal("dlua_script_create_file() failed: %s", error); dlua_dovecot_register(script); if (dlua_script_init(script, &error) < 0) i_fatal("dlua_script_init() failed: %s", error); struct event *event = event_create(NULL); event_set_forced_debug(event, TRUE); dlua_push_event(script->L, event); lua_pushstring(script->L, "https://localhost"); test_assert(dlua_pcall(script->L, "test_http_request_set_no_event", 2, 1, &error) == 1); struct event *child_event = dlua_check_event(script->L, 1); /* if request:set_event() is not used the returned event will not be a child of the event previously created */ test_assert(event_get_parent(child_event) != event); event_unref(&event); dlua_script_unref(&script); test_end(); } /* * All tests */ static void (*const test_functions[])(void) = { test_simple_post, test_second_post, test_bad_settings, test_set_event, test_set_no_event, NULL }; /* * Test client */ 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 bool test_client_init(test_client_init_t client_test) { i_assert(client_test != NULL); if (!client_test()) return FALSE; to_client_progress = timeout_add(CLIENT_PROGRESS_TIMEOUT*1000, test_client_progress_timeout, NULL); return TRUE; } static void test_client_deinit(void) { timeout_remove(&to_client_progress); http_client_global_context_free(); } static void test_client_run(test_client_init_t client_test) { test_client_init(client_test); test_client_deinit(); } /* * Test server */ /* client connection */ static int server_connection_init_ssl(struct server_connection *conn) { struct ssl_iostream_settings ssl_set; const char *error; if (!test_server_ssl) return 0; connection_input_halt(&conn->conn); ssl_iostream_test_settings_server(&ssl_set); if (server_ssl_ctx == NULL && ssl_iostream_context_init_server(&ssl_set, &server_ssl_ctx, &error) < 0) { i_error("SSL context initialization failed: %s", error); return -1; } if (io_stream_create_ssl_server(server_ssl_ctx, conn->conn.event, &conn->conn.input, &conn->conn.output, &conn->ssl_iostream, &error) < 0) { i_error("SSL init failed: %s", error); return -1; } if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { i_error("SSL handshake failed: %s", ssl_iostream_get_last_error(conn->ssl_iostream)); return -1; } connection_input_resume(&conn->conn); return 0; } static void server_connection_input(struct connection *_conn) { struct server_connection *conn = container_of(_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", 512); conn = p_new(pool, struct server_connection, 1); conn->pool = pool; connection_init_server(server_conn_list, &conn->conn, "server connection", fd, fd); conn->real_input = conn->conn.input; conn->real_output = conn->conn.output; if (server_connection_init_ssl(conn) < 0) { server_connection_deinit(&conn); return; } if (test_server_init != NULL) { if (test_server_init(conn) != 0) return; } } 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); ssl_iostream_destroy(&conn->ssl_iostream); 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) { if (!NET_ACCEPT_ENOCONN(errno)) i_fatal("test server: accept() failed: %m"); return; } 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(unsigned int index) { server_index = index; /* 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); ssl_iostream_context_unref(&server_ssl_ctx); } /* * Tests */ struct test_server_data { unsigned int index; test_server_init_t server_test; }; static int test_open_server_fd(in_port_t *bind_port) { int fd = net_listen(&bind_ip, bind_port, 128); e_debug(common_event, "server listening on %u", *bind_port); if (fd == -1) { i_fatal("listen(%s:%u) failed: %m", net_ip2addr(&bind_ip), *bind_port); } return fd; } static int test_run_server(struct test_server_data *data) { i_set_failure_prefix("SERVER[%u]: ", data->index + 1); e_debug(server_event, "PID=%s", my_pid); server_ssl_ctx = NULL; test_subprocess_notify_signal_send_parent(SIGHUP); ioloop = io_loop_create(); data->server_test(data->index); io_loop_destroy(&ioloop); e_debug(server_event, "Terminated"); i_close_fd(&fd_listen); i_free(bind_ports); main_deinit(); master_service_deinit_forked(&master_service); return 0; } static int test_run_dns(test_dns_init_t dns_test) { test_server_ssl = FALSE; i_set_failure_prefix("DNS: "); e_debug(dns_event, "PID=%s", my_pid); test_subprocess_notify_signal_send_parent(SIGHUP); ioloop = io_loop_create(); dns_test(); io_loop_destroy(&ioloop); e_debug(dns_event, "Terminated"); i_close_fd(&fd_listen); i_free(bind_ports); main_deinit(); master_service_deinit_forked(&master_service); return 0; } static void test_run_client(test_client_init_t client_test) { i_set_failure_prefix("CLIENT: "); e_debug(client_event, "PID=%s", my_pid); ioloop = io_loop_create(); test_client_run(client_test); io_loop_destroy(&ioloop); e_debug(client_event, "Terminated"); } static void test_run_client_server(test_client_init_t client_test, test_server_init_t server_test, unsigned int server_tests_count, test_dns_init_t dns_test) { unsigned int i; test_server_init = NULL; test_server_deinit = NULL; test_server_input = NULL; if (server_tests_count > 0) { int fds[server_tests_count]; bind_ports = i_new(in_port_t, server_tests_count); for (i = 0; i < server_tests_count; i++) fds[i] = test_open_server_fd(&bind_ports[i]); for (i = 0; i < server_tests_count; i++) { struct test_server_data data; i_zero(&data); data.index = i; data.server_test = server_test; /* Fork server */ fd_listen = fds[i]; test_subprocess_notify_signal_reset(SIGHUP); test_subprocess_fork(test_run_server, &data, FALSE); test_subprocess_notify_signal_wait( SIGHUP, TEST_SIGNALS_DEFAULT_TIMEOUT_MS); i_close_fd(&fd_listen); } } if (dns_test != NULL) { int fd; i_unlink_if_exists("./dns-test"); fd = net_listen_unix("./dns-test", 128); if (fd == -1) { i_fatal("listen(./dns-test) failed: %m"); } /* Fork DNS service */ fd_listen = fd; test_subprocess_notify_signal_reset(SIGHUP); test_subprocess_fork(test_run_dns, dns_test, FALSE); test_subprocess_notify_signal_wait( SIGHUP, TEST_SIGNALS_DEFAULT_TIMEOUT_MS); i_close_fd(&fd_listen); } /* Run client */ test_run_client(client_test); i_unset_failure_prefix(); test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); i_free(bind_ports); i_unlink_if_exists("./dns-test"); http_client_global_context_free(); } /* * Main */ static void main_init(void) { ssl_iostream_openssl_init(); common_event = test_event; client_event = event_create(common_event); event_set_append_log_prefix(client_event, "test client: "); server_event = event_create(common_event); event_set_append_log_prefix(server_event, "test server: "); dns_event = event_create(common_event); event_set_append_log_prefix(dns_event, "test dns: "); } static void main_deinit(void) { ssl_iostream_context_cache_free(); ssl_iostream_openssl_deinit(); event_unref(&client_event); event_unref(&server_event); event_unref(&dns_event); common_event = NULL; } int main(int argc, char *argv[]) { const enum master_service_flags service_flags = MASTER_SERVICE_FLAG_CONFIG_BUILTIN | MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT | MASTER_SERVICE_FLAG_DONT_SEND_STATS; const char *error; int c; int ret; while ((c = getopt(argc, argv, "D")) > 0) { switch (c) { case 'D': debug = TRUE; break; default: i_fatal("Usage: %s [-D]", argv[0]); } } master_service = master_service_init("test-lua-http-client", service_flags, &argc, &argv, ""); master_service_parse_option( master_service, 'o', "ssl_client_require_valid_cert=no"); if (master_service_settings_read_simple(master_service, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_finish(master_service); test_init(); event_set_forced_debug(test_event, debug); test_subprocesses_init(); main_init(); /* listen on localhost */ i_zero(&bind_ip); bind_ip.family = AF_INET; bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); ret = test_run(test_functions); main_deinit(); master_service_deinit(&master_service); return ret; }