From: Timo Sirainen Date: Thu, 5 Feb 2026 09:52:34 +0000 (+0200) Subject: lib-imap-urlauth: Add test-imap-urlauth X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ed8b751baafea74c2b8ebaf7a6495cf78b41290b;p=thirdparty%2Fdovecot%2Fcore.git lib-imap-urlauth: Add test-imap-urlauth --- diff --git a/src/Makefile.am b/src/Makefile.am index 45a17a1945..7b9ab145ca 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,10 +47,10 @@ SUBDIRS = \ $(LIB_LUA) \ lib-language \ lib-imap-client \ - lib-imap-urlauth \ lib-compression \ lib-index \ lib-storage \ + lib-imap-urlauth \ lib-sql \ lib-lda \ lib-dict-backend \ diff --git a/src/lib-imap-urlauth/Makefile.am b/src/lib-imap-urlauth/Makefile.am index 5b77658691..32cd4f19e9 100644 --- a/src/lib-imap-urlauth/Makefile.am +++ b/src/lib-imap-urlauth/Makefile.am @@ -1,3 +1,5 @@ +include $(top_srcdir)/Makefile.test.include + noinst_LTLIBRARIES = libimap-urlauth.la AM_CPPFLAGS = \ @@ -26,6 +28,15 @@ headers = \ imap-urlauth-backend.h \ imap-urlauth-connection.h +test_programs = test-imap-urlauth + +test_imap_urlauth_SOURCES = test-imap-urlauth.c +test_imap_urlauth_LDADD = \ + imap-urlauth.lo \ + ../lib-dovecot/libdovecot.la \ + ../lib-test/libtest.la \ + $(LIBDOVECOT_TEST_LIBS) + pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) diff --git a/src/lib-imap-urlauth/test-imap-urlauth.c b/src/lib-imap-urlauth/test-imap-urlauth.c new file mode 100644 index 0000000000..603be7f8fb --- /dev/null +++ b/src/lib-imap-urlauth/test-imap-urlauth.c @@ -0,0 +1,430 @@ +/* Copyright (c) 2025 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "auth-master.h" +#include "mail-storage.h" +#include "imap-url.h" +#include "imap-urlauth-private.h" +#include "imap-urlauth-fetch.h" +#include "imap-urlauth-backend.h" +#include "imap-urlauth-connection.h" +#include "imap-msgpart-url.h" + +#include + +/* Mocks */ +int imap_msgpart_url_create(struct mail_user *user ATTR_UNUSED, + const struct imap_url *url ATTR_UNUSED, + struct imap_msgpart_url **mpurl_r, + const char **error_r ATTR_UNUSED) +{ + *mpurl_r = (struct imap_msgpart_url *)"mock-mpurl"; + return 0; +} + +void imap_msgpart_url_free(struct imap_msgpart_url **mpurl ATTR_UNUSED) +{ +} + +int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return 1; +} + +struct mailbox *imap_msgpart_url_get_mailbox(struct imap_msgpart_url *mpurl ATTR_UNUSED) +{ + return (struct mailbox *)"mock-mailbox"; +} + +int imap_msgpart_url_open_mailbox(struct imap_msgpart_url *mpurl ATTR_UNUSED, + struct mailbox **box_r, + enum mail_error *error_code_r ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + *box_r = (struct mailbox *)"mock-mailbox"; + return 1; +} + +int imap_urlauth_backend_get_mailbox_key(struct mailbox *box ATTR_UNUSED, + bool create ATTR_UNUSED, + unsigned char key_r[IMAP_URLAUTH_KEY_LEN], + const char **client_error_r ATTR_UNUSED, + enum mail_error *error_code_r ATTR_UNUSED) +{ + memset(key_r, 0, IMAP_URLAUTH_KEY_LEN); + return 1; +} + +int imap_urlauth_backend_reset_mailbox_key(struct mailbox *box ATTR_UNUSED) +{ + return 0; +} + +int imap_urlauth_backend_reset_all_keys(struct mail_user *user ATTR_UNUSED) +{ + return 0; +} + +int auth_master_user_lookup(struct auth_master_connection *conn ATTR_UNUSED, + const char *user ATTR_UNUSED, + const struct auth_user_info *info ATTR_UNUSED, + pool_t pool ATTR_UNUSED, + const char **username_r ATTR_UNUSED, + const char *const **fields_r ATTR_UNUSED) +{ + return 0; +} +struct auth_master_connection *mail_user_auth_master_conn = NULL; + +struct imap_urlauth_connection * +imap_urlauth_connection_init(const char *path ATTR_UNUSED, + const char *service ATTR_UNUSED, + struct mail_user *user ATTR_UNUSED, + const char *session_id ATTR_UNUSED, + unsigned int idle_timeout_msecs ATTR_UNUSED) +{ + return (struct imap_urlauth_connection *)"mock-conn"; +} + +void imap_urlauth_connection_deinit(struct imap_urlauth_connection **conn) +{ + *conn = NULL; +} + +static void test_imap_urlauth_check(void) +{ + struct imap_urlauth_context uctx = { + .url_host = "localhost", + .url_port = 143, + .access_service = "imap", + .access_user = "user", + }; + struct imap_url url = { + .mailbox = "INBOX", + .uid = 1, + .uauth_rumpurl = "imap://user@localhost/INBOX/;UID=1;URLAUTH=anonymous", + .uauth_mechanism = "INTERNAL", + .userid = "user", + .uauth_access_application = "anonymous", + .host = { .name = "localhost" }, + .uauth_expire = (time_t)-1, + }; + const char *error; + bool result; + time_t now = time(NULL); + + test_begin("imap_urlauth_check()"); + + /* 1. Success case */ + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(valid)", result); + + /* 2. Invalid URL fields */ + url.mailbox = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(missing mailbox)", !result); + test_assert_strcmp(error, "Invalid URL: Must be a full URLAUTH URL"); + url.mailbox = "INBOX"; + + url.uid = 0; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(missing uid)", !result); + test_assert_strcmp(error, "Invalid URL: Must be a full URLAUTH URL"); + url.uid = 1; + + url.search_program = "ALL"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(search program not allowed)", !result); + test_assert_strcmp(error, "Invalid URL: Must be a full URLAUTH URL"); + url.search_program = NULL; + + url.uauth_rumpurl = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(missing rumpurl)", !result); + test_assert_strcmp(error, "Invalid URL: Must be a full URLAUTH URL"); + url.uauth_rumpurl = "imap://user@localhost/INBOX/;UID=1;URLAUTH=anonymous"; + + url.uauth_mechanism = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(missing mechanism)", !result); + test_assert_strcmp(error, "Invalid URL: Must be a full URLAUTH URL"); + url.uauth_mechanism = "INTERNAL"; + + /* 3. Missing userid */ + url.userid = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(missing userid)", !result); + test_assert_strcmp(error, "Invalid URLAUTH: Missing user name"); + url.userid = "user"; + + /* 4. Unsupported mechanism */ + url.uauth_mechanism = "UNKNOWN"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(unsupported mechanism)", !result); + test_assert_strcmp(error, "Unsupported URLAUTH mechanism: UNKNOWN"); + url.uauth_mechanism = "INTERNAL"; + + /* 5. Expired */ + url.uauth_expire = now - 1; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(expired)", !result); + test_assert_strcmp(error, "URLAUTH has expired"); + url.uauth_expire = now + 3600; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(not expired)", result); + url.uauth_expire = (time_t)-1; + + /* 6. Host/port check */ + url.host.name = "otherhost"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(invalid host)", !result); + test_assert_strcmp(error, "Invalid URL: Inappropriate host name"); + url.host.name = "localhost"; + + uctx.url_host = "*"; + url.host.name = "anyhost"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(any host allowed)", result); + uctx.url_host = "localhost"; + url.host.name = "localhost"; + + url.port = 993; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(invalid port)", !result); + test_assert_strcmp(error, "Invalid URL: Inappropriate server port"); + url.port = 0; /* Default 143 */ + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(default port 143)", result); + + /* 7. Access checks - IMAP */ + uctx.access_service = "imap"; + + /* application=user */ + url.uauth_access_application = "user"; + url.uauth_access_user = "testuser"; + uctx.access_user = "testuser"; + uctx.access_anonymous = FALSE; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap user access - match)", result); + + uctx.access_user = "other"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap user access - mismatch)", !result); + test_assert_strcmp(error, "No 'user+testuser' access allowed for user other"); + + uctx.access_anonymous = TRUE; + uctx.access_user = "anonymous"; + url.uauth_access_user = "anonymous"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap user access - anonymous not allowed)", !result); + test_assert_strcmp(error, "No 'user+anonymous' access allowed for anonymous user"); + + /* application=authuser */ + url.uauth_access_application = "authuser"; + url.uauth_access_user = NULL; + uctx.access_anonymous = FALSE; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap authuser access)", result); + uctx.access_anonymous = TRUE; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap authuser access - anonymous not allowed)", !result); + test_assert_strcmp(error, "No 'authuser' access allowed for anonymous user"); + + /* application=anonymous */ + url.uauth_access_application = "anonymous"; + uctx.access_anonymous = TRUE; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap anonymous access)", result); + + /* custom applications */ + const char *apps[] = { "app1", "app2+", NULL }; + uctx.access_applications = apps; + uctx.access_anonymous = FALSE; + uctx.access_user = "testuser"; + + url.uauth_access_application = "app1"; + url.uauth_access_user = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap custom app1 access)", result); + + url.uauth_access_application = "app2"; + url.uauth_access_user = "someuser"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap custom app2+ access)", result); + + url.uauth_access_application = "app2"; + url.uauth_access_user = NULL; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap custom app2+ access - no user)", !result); + test_assert_strcmp(error, "No 'app2' access allowed for user testuser"); + + url.uauth_access_application = "app2prefix"; + url.uauth_access_user = "someuser"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap custom app2prefix access)", !result); + test_assert_strcmp(error, "No 'app2prefix+someuser' access allowed for user testuser"); + + url.uauth_access_application = "app3"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(imap unknown app access)", !result); + test_assert_strcmp(error, "No 'app3+someuser' access allowed for user testuser"); + + result = imap_urlauth_check(&uctx, &url, TRUE, &error); + test_out("imap_urlauth_check(imap unknown app access - ignore_unknown)", result); + + /* 8. Access checks - Submission */ + uctx.access_service = "submission"; + url.uauth_access_application = "submit"; + url.uauth_access_user = "testuser"; + uctx.access_user = "testuser"; + uctx.access_anonymous = FALSE; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(submission submit access)", result); + + url.uauth_access_application = "user"; + result = imap_urlauth_check(&uctx, &url, FALSE, &error); + test_out("imap_urlauth_check(submission user access - not allowed)", !result); + test_assert_strcmp(error, "No 'user+testuser' access allowed for submission service"); + + test_end(); +} + +static void test_imap_urlauth_generate(void) +{ + struct mail_user user = { + .username = "user", + }; + struct imap_urlauth_context uctx = { + .user = &user, + .url_host = "localhost", + .url_port = 143, + .access_service = "imap", + .access_user = "user", + }; + const char *urlauth, *error; + int ret; + + test_begin("imap_urlauth_generate()"); + + /* 1. Unsupported mechanism */ + ret = imap_urlauth_generate(&uctx, "UNKNOWN", "imap://user@localhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(unsupported mechanism)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Unsupported URLAUTH mechanism: UNKNOWN"); + + /* 2. Invalid URL */ + ret = imap_urlauth_generate(&uctx, "INTERNAL", "invalid-url", &urlauth, &error); + test_out("imap_urlauth_generate(invalid url)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Invalid URL: Relative URL not allowed"); + + /* 3. Not a rump URL (has search program) */ + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://user@localhost/INBOX?ALL", &urlauth, &error); + test_out("imap_urlauth_generate(not a rump url - search)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Invalid URL: Must be an URLAUTH rump URL"); + + /* 4. Missing userid */ + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://localhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(missing userid)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Invalid URL: Missing user name"); + + /* 5. Anonymous user not permitted */ + user.anonymous = TRUE; + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://user@localhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(anonymous not permitted)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Anonymous logins not permitted to generate URLAUTH"); + user.anonymous = FALSE; + + /* 6. User mismatch */ + user.username = "other"; + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://wronguser@localhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(user mismatch)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Not permitted to generate URLAUTH for other user wronguser"); + user.username = "user"; + + /* 7. Host mismatch */ + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://user@otherhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(host mismatch)", ret == 0 && error != NULL); + test_assert_strcmp(error, "Invalid URL: Inappropriate host name"); + + /* 8. Success path (with mocks) */ + ret = imap_urlauth_generate(&uctx, "INTERNAL", "imap://user@localhost/INBOX/;UID=1;URLAUTH=user+user", &urlauth, &error); + test_out("imap_urlauth_generate(success)", ret == 1 && urlauth != NULL); + + test_end(); +} + +static void test_imap_urlauth_fetch(void) +{ + struct mail_user user = { + .username = "user", + }; + struct imap_urlauth_context uctx = { + .user = &user, + .url_host = "localhost", + .url_port = 143, + .access_service = "imap", + .access_user = "user", + }; + struct imap_url url = { + .mailbox = "INBOX", + .uid = 1, + .uauth_rumpurl = "imap://user@localhost/INBOX/;UID=1;URLAUTH=anonymous", + .uauth_mechanism = "INTERNAL", + .userid = "user", + .uauth_access_application = "anonymous", + .host = { + .name = "localhost", + }, + .uauth_expire = (time_t)-1, + .uauth_token = (const unsigned char *)"12345678901234567890", + .uauth_token_size = 20, + }; + struct imap_msgpart_url *mpurl; + enum mail_error error_code; + const char *error; + int ret; + + test_begin("imap_urlauth_fetch()"); + + /* 1. imap_urlauth_fetch_parsed failure */ + url.host.name = "otherhost"; + ret = imap_urlauth_fetch_parsed(&uctx, &url, &mpurl, &error_code, &error); + test_out("imap_urlauth_fetch_parsed(check failure)", ret == 0 && error_code == MAIL_ERROR_PARAMS); + url.host.name = "localhost"; + + /* 2. imap_urlauth_fetch failure (invalid string) */ + ret = imap_urlauth_fetch(&uctx, "invalid-url", &mpurl, &error_code, &error); + test_out("imap_urlauth_fetch(invalid url)", ret == 0 && error_code == MAIL_ERROR_PARAMS); + + /* 3. imap_urlauth_fetch success path (calls parsed) */ + ret = imap_urlauth_fetch(&uctx, "imap://user@localhost/INBOX/;UID=1;URLAUTH=anonymous:INTERNAL:4142434445464748494A4B4C4D4E4F5051525354", &mpurl, &error_code, &error); + /* Verification failure is expected because our mock key is all zeros but the token is not a SHA1-HMAC of rumpurl with zero key */ + test_out("imap_urlauth_fetch(verification failure)", ret == 0 && error_code == MAIL_ERROR_PERM); + + test_end(); +} + +static void test_imap_urlauth_reset_keys(void) +{ + struct imap_urlauth_context uctx = { + .user = NULL + }; + + test_begin("imap_urlauth_reset_keys()"); + test_out("imap_urlauth_reset_mailbox_key", imap_urlauth_reset_mailbox_key(&uctx, NULL) == 0); + test_out("imap_urlauth_reset_all_keys", imap_urlauth_reset_all_keys(&uctx) == 0); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_urlauth_check, + test_imap_urlauth_generate, + test_imap_urlauth_fetch, + test_imap_urlauth_reset_keys, + NULL + }; + return test_run(test_functions); +}