]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-sasl: test-sasl-authentication - Test winbind NTLM mechanism using dummy implemen...
authorStephan Bosch <stephan.bosch@open-xchange.com>
Thu, 26 Oct 2023 17:58:04 +0000 (19:58 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Thu, 9 Oct 2025 08:41:22 +0000 (08:41 +0000)
.gitignore
src/lib-sasl/Makefile.am
src/lib-sasl/dsasl-client-mech-ntlm-dummy.c [new file with mode: 0644]
src/lib-sasl/dsasl-client-mech-ntlm-dummy.h [new file with mode: 0644]
src/lib-sasl/ntlm_dummy.c [new file with mode: 0644]
src/lib-sasl/test-sasl-authentication.c

index 11d0798c9862b022c9eaee3bea5195331057bc39..b174277dd0594d6ea6be57b1762db47487398da6 100644 (file)
@@ -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
index f4d7eb0915553473178164f65426ce28ca1fe460..1341aba8760b4c7e73cf9d9e85dbcf58f7d51405 100644 (file)
@@ -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 (file)
index 0000000..b4cd4e4
--- /dev/null
@@ -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 (file)
index 0000000..7a1998b
--- /dev/null
@@ -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 (file)
index 0000000..f2895fd
--- /dev/null
@@ -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 <stdio.h>
+
+/* 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();
+}
index 098f1bc20dafa00989a65eb8ca3120a32b9fc7b4..69d6becaf9a90540bbcdc612d7774574d4b4ebaf 100644 (file)
@@ -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 <unistd.h>
 
@@ -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;
 }