From ca45229cbf61d4ecb92c7fcee49692980066cc73 Mon Sep 17 00:00:00 2001 From: Stephan Bosch Date: Thu, 26 Oct 2023 19:58:04 +0200 Subject: [PATCH] lib-sasl: test-sasl-authentication - Test winbind NTLM mechanism using dummy implementation --- .gitignore | 1 + src/lib-sasl/Makefile.am | 16 ++- src/lib-sasl/dsasl-client-mech-ntlm-dummy.c | 93 ++++++++++++++++++ src/lib-sasl/dsasl-client-mech-ntlm-dummy.h | 6 ++ src/lib-sasl/ntlm_dummy.c | 102 ++++++++++++++++++++ src/lib-sasl/test-sasl-authentication.c | 37 ++++++- 6 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/lib-sasl/dsasl-client-mech-ntlm-dummy.c create mode 100644 src/lib-sasl/dsasl-client-mech-ntlm-dummy.h create mode 100644 src/lib-sasl/ntlm_dummy.c diff --git a/.gitignore b/.gitignore index 11d0798c98..b174277dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,4 @@ src/plugins/quota/quota-status src/lib-var-expand/var-expand-parser.c src/lib-var-expand/var-expand-parser.h src/lib-var-expand/var-expand-lexer.c +src/lib-sasl/ntlm_dummy diff --git a/src/lib-sasl/Makefile.am b/src/lib-sasl/Makefile.am index f4d7eb0915..1341aba876 100644 --- a/src/lib-sasl/Makefile.am +++ b/src/lib-sasl/Makefile.am @@ -12,7 +12,8 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib-json \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-otp \ - -I$(top_srcdir)/src/lib-auth + -I$(top_srcdir)/src/lib-auth \ + -DTEST_WINBIND_HELPER_PATH=\""$(abs_builddir)/ntlm_dummy"\" client_mechanisms = \ dsasl-client-mech-anonymous.c \ @@ -70,12 +71,15 @@ headers = \ pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) +noinst_HEADERS =\ + dsasl-client-mech-ntlm-dummy.h + test_programs = \ test-sasl-oauth2 \ test-sasl-client \ test-sasl-authentication -noinst_PROGRAMS = $(test_programs) +noinst_PROGRAMS = $(test_programs) ntlm_dummy test_libs = \ libsasl.la \ @@ -97,10 +101,16 @@ test_sasl_client_SOURCES = test-sasl-client.c test_sasl_client_LDADD = $(test_libs) test_sasl_client_DEPENDENCIES = $(test_deps) -test_sasl_authentication_SOURCES = test-sasl-authentication.c +test_sasl_authentication_SOURCES = \ + dsasl-client-mech-ntlm-dummy.c \ + test-sasl-authentication.c test_sasl_authentication_LDADD = $(test_libs) test_sasl_authentication_DEPENDENCIES = $(test_deps) +ntlm_dummy_SOURCES = ntlm_dummy.c +ntlm_dummy_LDADD = ../lib/liblib.la +ntlm_dummy_DEPENDENCIES = ../lib/liblib.la + check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ diff --git a/src/lib-sasl/dsasl-client-mech-ntlm-dummy.c b/src/lib-sasl/dsasl-client-mech-ntlm-dummy.c new file mode 100644 index 0000000000..b4cd4e4450 --- /dev/null +++ b/src/lib-sasl/dsasl-client-mech-ntlm-dummy.c @@ -0,0 +1,93 @@ +/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dsasl-client-private.h" +#include "dsasl-client-mech-ntlm-dummy.h" + +/* Dummy NTLM mechanism + + This has nothing to do with actual NTLM; it just serves as a means to test + the winbind server mechanisms. + */ + +enum ntlm_state { + STATE_INIT = 0, + STATE_RESPONSE +}; + +struct ntlm_dsasl_client { + struct dsasl_client client; + enum ntlm_state state; +}; + +void dasl_client_mech_ntlm_init_dummy(void); + +static enum dsasl_client_result +mech_ntlm_input(struct dsasl_client *_client, + const unsigned char *input, size_t input_len, + const char **error_r) +{ + static const char *challenge = "Challenge"; + struct ntlm_dsasl_client *client = + container_of(_client, struct ntlm_dsasl_client, client); + size_t chal_size; + + if (client->state == STATE_RESPONSE) { + *error_r = "Server didn't finish authentication"; + return DSASL_CLIENT_RESULT_ERR_PROTOCOL; + } + chal_size = strlen(challenge); + if (input_len != chal_size || + memcmp(input, challenge, chal_size) != 0) { + *error_r = "Invalid challenge"; + return DSASL_CLIENT_RESULT_ERR_PROTOCOL; + } + client->state++; + return DSASL_CLIENT_RESULT_OK; +} + +static enum dsasl_client_result +mech_ntlm_output(struct dsasl_client *_client, + const unsigned char **output_r, size_t *output_len_r, + const char **error_r) +{ + struct ntlm_dsasl_client *client = + container_of(_client, struct ntlm_dsasl_client, client); + + if (_client->set.authid == NULL) { + *error_r = "authid not set"; + return DSASL_CLIENT_RESULT_ERR_INTERNAL; + } + if (_client->password == NULL) { + *error_r = "password not set"; + return DSASL_CLIENT_RESULT_ERR_INTERNAL; + } + + const char *response; + + switch (client->state) { + case STATE_INIT: + *output_r = uchar_empty_ptr; + *output_len_r = 0; + return DSASL_CLIENT_RESULT_OK; + case STATE_RESPONSE: + response = t_strconcat("Response: ", _client->set.authid, NULL); + *output_r = (const unsigned char *)response; + *output_len_r = strlen(response); + return DSASL_CLIENT_RESULT_OK; + } + i_unreached(); +} + +const struct dsasl_client_mech dsasl_client_mech_ntlm = { + .name = SASL_MECH_NAME_NTLM, + .struct_size = sizeof(struct ntlm_dsasl_client), + + .input = mech_ntlm_input, + .output = mech_ntlm_output +}; + +void dsasl_client_mech_ntlm_init_dummy(void) +{ + dsasl_client_mech_register(&dsasl_client_mech_ntlm); +} diff --git a/src/lib-sasl/dsasl-client-mech-ntlm-dummy.h b/src/lib-sasl/dsasl-client-mech-ntlm-dummy.h new file mode 100644 index 0000000000..7a1998bda9 --- /dev/null +++ b/src/lib-sasl/dsasl-client-mech-ntlm-dummy.h @@ -0,0 +1,6 @@ +#ifndef DSASL_CLIENT_MECH_NTLM_DUMMY_H +#define DSASL_CLIENT_MECH_NTLM_DUMMY_H + +void dsasl_client_mech_ntlm_init_dummy(void); + +#endif diff --git a/src/lib-sasl/ntlm_dummy.c b/src/lib-sasl/ntlm_dummy.c new file mode 100644 index 0000000000..f2895fd2b8 --- /dev/null +++ b/src/lib-sasl/ntlm_dummy.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "base64.h" +#include "istream.h" + +#include + +/* Dummy NTLM helper + + This has nothing to do with actual NTLM; it just serves as a means to test + the winbind server mechanisms. + */ + +#define USER "user" +#define MAX_LINE_LENGTH 16384 + +static void run_ntlm_cmd_yr(void) +{ + static const char *challenge = "Challenge"; + const size_t chal_size = strlen(challenge); + string_t *str; + + str = t_str_new(MAX_BASE64_ENCODED_SIZE(chal_size + 1)); + base64_encode(challenge, chal_size, str); + + printf("TT %s\n", str_c(str)); +} + +static void run_ntlm_cmd_kk(const char *param) +{ + static const char *response = "Response: "; + static const char *user = USER; + const size_t resp_size = strlen(response); + const size_t user_size = strlen(user); + const unsigned char *data; + size_t size; + buffer_t *buf; + + buf = t_base64_decode_str(param); + data = buf->data; + size = buf->used; + + if (size <= resp_size || + memcmp(data, response, resp_size) != 0) { + printf("BH Invalid client response\n"); + return; + } + data += resp_size; + size -= resp_size; + + if (size <= user_size || memcmp(data, user, user_size) != 0 || + data[user_size] != '@') { + printf("NA No such user\n"); + return; + } + + printf("AF user@EXAMPLE.COM\n"); +} + +static void run_ntlm(void) +{ + struct istream *input; + const char *line; + int fd = 0; + + input = i_stream_create_fd_autoclose(&fd, MAX_LINE_LENGTH); + + while ((line = i_stream_read_next_line(input)) != NULL) { + const char *const *args = t_strsplit_spaces(line, " "); + + if (strcmp(args[0], "YR") == 0) { + if (args[1] != NULL && strlen(args[1]) > 0) + i_fatal("Invalid YR command: %s", line); + run_ntlm_cmd_yr(); + } else if (strcmp(args[0], "KK") == 0) { + if (args[1] == NULL || args[2] != NULL) + i_fatal("Invalid KK command: %s", line); + run_ntlm_cmd_kk(args[1]); + } else { + i_fatal("Invalid command: %s", line); + } + fflush(stdout); + } + + i_stream_destroy(&input); +} + +int main(int argc, char *argv[]) +{ + lib_init(); + + if (argc < 2) + i_fatal("Invalid arguments"); + if (strcmp(argv[1], "--helper-protocol=squid-2.5-ntlmssp") == 0) + run_ntlm(); + else + i_fatal("Invalid arguments"); + + lib_deinit(); +} diff --git a/src/lib-sasl/test-sasl-authentication.c b/src/lib-sasl/test-sasl-authentication.c index 098f1bc20d..69d6becaf9 100644 --- a/src/lib-sasl/test-sasl-authentication.c +++ b/src/lib-sasl/test-sasl-authentication.c @@ -1,6 +1,7 @@ /* Copyright (c) 2025 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "lib-signals.h" #include "str.h" #include "base64.h" #include "randgen.h" @@ -9,6 +10,7 @@ #include "sasl-server.h" #include "sasl-server-oauth2.h" #include "dsasl-client.h" +#include "dsasl-client-mech-ntlm-dummy.h" #include @@ -408,7 +410,6 @@ test_sasl_run(const struct test_sasl *test, const char *label, { struct sasl_server *server; struct sasl_server_instance *server_inst; - const struct sasl_server_mech *server_mech; test_begin(t_strdup_printf("sasl %s %s%s", label, test->mech, @@ -434,6 +435,13 @@ test_sasl_run(const struct test_sasl *test, const char *label, sasl_server_mech_register_xoauth2(server_inst, &server_oauth2_funcs, NULL); + struct sasl_server_winbind_settings winbind_set = { + .helper_path = TEST_WINBIND_HELPER_PATH, + }; + sasl_server_mech_register_winbind_ntlm(server_inst, &winbind_set); + + const struct sasl_server_mech *server_mech; + server_mech = sasl_server_mech_find(server_inst, test->mech); i_assert(server_mech != NULL); @@ -577,6 +585,16 @@ static const struct test_sasl success_tests[] = { .password = "", }, }, + /* NTLM */ + { + .mech = "NTLM", + .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME, + .server = { + .authid = "user@EXAMPLE.COM", + .authzid = "", + .password = "", + }, + }, /* XOAUTH2 */ { .mech = "XOAUTH2", @@ -1056,6 +1074,20 @@ static const struct test_sasl bad_creds_tests[] = { }, .failure = TRUE, }, + /* NTLM */ + { + .mech = "NTLM", + .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME, + .server = { + .authid = "user@EXAMPLE.COM", + .authzid = "", + .password = "", + }, + .client = { + .authid = "userb@EXAMPLE.COM", + }, + .failure = TRUE, + }, /* XOAUTH2 */ { .mech = "XOAUTH2", @@ -1107,6 +1139,7 @@ int main(int argc, char *argv[]) int ret, c; lib_init(); + lib_signals_init(); while ((c = getopt(argc, argv, "D")) > 0) { switch (c) { @@ -1122,12 +1155,14 @@ int main(int argc, char *argv[]) event_set_forced_debug(test_event, debug); password_schemes_init(); dsasl_clients_init(); + dsasl_client_mech_ntlm_init_dummy(); ret = test_run(test_functions); dsasl_clients_deinit(); password_schemes_deinit(); event_unref(&test_event); + lib_signals_deinit(); lib_deinit(); return ret; } -- 2.47.3