Mailing list header changes (fuck you Google) master
authorArt Cancro <ajc@citadel.org>
Fri, 19 Apr 2024 15:06:05 +0000 (15:06 +0000)
committerArt Cancro <ajc@citadel.org>
Fri, 26 Apr 2024 02:40:08 +0000 (02:40 +0000)
DRY the code to generate listsub/unsub confirmations.

We're going to need this to do One Click Unsubscribe, which we have to add
because the godless commies at Google will use any excuse to put non-Google
senders on their spam list.

Add the List-Unsubscribe-Post header

citadel/server/citserver.c
citadel/server/internet_addressing.h
citadel/server/modules/listdeliver/serv_listdeliver.c
citadel/server/modules/listsub/serv_listsub.c
citadel/server/modules/smtp/serv_smtpclient.c

index da0865899516b30e7c93a4eb44d81d0493a07b3f..a6c9505967fec1c16ea2eb0874c8f83063332a4e 100644 (file)
@@ -29,24 +29,8 @@ int panic_fd;
 
 // We need pseudo-random numbers for a few things.  Seed generously.
 void seed_random_number_generator(void) {
 
 // We need pseudo-random numbers for a few things.  Seed generously.
 void seed_random_number_generator(void) {
-       FILE *urandom;
-       struct timeval tv;
-       unsigned int seed;
-
-       syslog(LOG_INFO, "Seeding the pseudo-random number generator...");
-       urandom = fopen("/dev/urandom", "r");
-       if (urandom != NULL) {
-               if (fread(&seed, sizeof seed, 1, urandom) == -1) {
-                       syslog(LOG_ERR, "citserver: failed to read random seed: %m");
-               }
-               fclose(urandom);
-       }
-       else {
-               gettimeofday(&tv, NULL);
-               seed = tv.tv_usec;
-       }
-       srand(seed);
-       srandom(seed);
+       syslog(LOG_INFO, "citserver: seeding the pseudo-random number generator");
+       srand(time(NULL) + getpid() + clock());
 }
 
 
 }
 
 
@@ -56,10 +40,10 @@ void master_startup(void) {
        struct passwd *pw;
        gid_t gid;
 
        struct passwd *pw;
        gid_t gid;
 
-       syslog(LOG_DEBUG, "master_startup() started");
+       syslog(LOG_DEBUG, "citserver: master_startup() started");
        time(&server_startup_time);
 
        time(&server_startup_time);
 
-       syslog(LOG_INFO, "Checking directory access");
+       syslog(LOG_INFO, "citserver: checking directory access");
        if ((pw = getpwuid(ctdluid)) == NULL) {
                gid = getgid();
        }
        if ((pw = getpwuid(ctdluid)) == NULL) {
                gid = getgid();
        }
@@ -77,13 +61,13 @@ void master_startup(void) {
        syslog(LOG_DEBUG, "citserver: ctdl_key_dir is %s", ctdl_key_dir);
        syslog(LOG_DEBUG, "citserver: ctdl_run_dir is %s", ctdl_run_dir);
 
        syslog(LOG_DEBUG, "citserver: ctdl_key_dir is %s", ctdl_key_dir);
        syslog(LOG_DEBUG, "citserver: ctdl_run_dir is %s", ctdl_run_dir);
 
-       syslog(LOG_INFO, "Opening databases");
+       syslog(LOG_INFO, "citserver: opening databases");
        cdb_init_backends();
        cdb_open_databases();
 
        // Load site-specific configuration
        seed_random_number_generator();                                 // must be done before config system
        cdb_init_backends();
        cdb_open_databases();
 
        // Load site-specific configuration
        seed_random_number_generator();                                 // must be done before config system
-       syslog(LOG_INFO, "Initializing configuration system");
+       syslog(LOG_INFO, "citserver: initializing configuration system");
        initialize_config_system();
        validate_config();
        migrate_legacy_control_record();
        initialize_config_system();
        validate_config();
        migrate_legacy_control_record();
@@ -98,7 +82,7 @@ void master_startup(void) {
        // Check floor reference counts
        check_ref_counts();
 
        // Check floor reference counts
        check_ref_counts();
 
-       syslog(LOG_INFO, "Creating base rooms (if necessary)");
+       syslog(LOG_INFO, "citserver: creating base rooms (if necessary)");
        CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
@@ -116,7 +100,7 @@ void master_startup(void) {
                CtdlPutRoomLock(&qrbuf);
        }
 
                CtdlPutRoomLock(&qrbuf);
        }
 
-       syslog(LOG_DEBUG, "master_startup() finished");
+       syslog(LOG_DEBUG, "citserver: master_startup() finished");
 }
 
 
 }
 
 
index 16f1a016425466d24c2199f89312e7902c58744a..b0e48075c5e0b9a430e78ce3745021848d3959db 100644 (file)
@@ -22,6 +22,7 @@ int CtdlIsMe(char *addr, int addr_buf_len);
 int CtdlHostAlias(char *fqdn);
 char *harvest_collected_addresses(struct CtdlMessage *msg);
 int is_email_subscribed_to_list(char *email, char *room_name);
 int CtdlHostAlias(char *fqdn);
 char *harvest_collected_addresses(struct CtdlMessage *msg);
 int is_email_subscribed_to_list(char *email, char *room_name);
+void generate_one_click_url(char *target_buf, char *base_url, char *action, char *roomname, char *emailaddr);
 
 // Values that can be returned by CtdlHostAlias()
 enum {
 
 // Values that can be returned by CtdlHostAlias()
 enum {
index c342b0799d087cf6054f7115ba7ca32cb27a37f4..fd1356079f104eaa907075af27275336949b8442 100644 (file)
@@ -1,6 +1,6 @@
 // This module delivers messages to mailing lists.
 //
 // This module delivers messages to mailing lists.
 //
-// Copyright (c) 2002-2023 by the citadel.org team
+// Copyright (c) 2002-2024 by the citadel.org team
 //
 // This program is open source software; you can redistribute it and/or modify
 // it under the terms of the GNU General Public License version 3.
 //
 // This program is open source software; you can redistribute it and/or modify
 // it under the terms of the GNU General Public License version 3.
@@ -245,5 +245,5 @@ char *ctdl_module_init_listdeliver(void) {
        }
        
        // return our module name for the log
        }
        
        // return our module name for the log
-       return "listsub";
+       return "listdeliver";
 }
 }
index 9eec9646329756e5be07c073cc8d4f9757c9357d..08aaabadf8c00d8c17e018c46e03a5d83d60c394 100644 (file)
@@ -57,11 +57,35 @@ void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *ro
 }
 
 
 }
 
 
+// Generate a pre-authorized subscribe/unsubscribe URL for a particular email address for a particular room.
+// This can be used as the second part of a double-opt-in or double-opt-out process.
+// It can also be used to generate a "one click unsubscribe" link.
+void generate_one_click_url(char *target_buf, char *base_url, char *action, char *roomname, char *emailaddr) {
+
+       // We need a URL-safe representation of the room name
+       char encoded_roomname[ROOMNAMELEN+10];
+       urlesc(encoded_roomname, sizeof(encoded_roomname), roomname);
+
+       // The confirmation token pre-authorizes the generated URL.  It is hashed by the host key so it can't be guessed.
+       char confirmation_token[128];
+       generate_confirmation_token(confirmation_token, sizeof confirmation_token, roomname, emailaddr);
+
+       // Write to the buffer
+       snprintf(target_buf, SIZ, "%s?cmd=%s&email=%s&room=%s&token=%s",
+               base_url,
+               action,
+               emailaddr,
+               encoded_roomname,
+               confirmation_token
+       );
+}
+
+
 // This generates an email with a link the user clicks to confirm a list subscription.
 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
 // This generates an email with a link the user clicks to confirm a list subscription.
 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char confirm_subscribe_url[SIZ];
+       generate_one_click_url(confirm_subscribe_url, url, "confirm_subscribe", roomname, emailaddr);
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
@@ -80,7 +104,7 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
                "<%s> to the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
                "<%s> to the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
+               "%s\n"
                "\n"
                "If this request has been submitted in error and you do not\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
                "\n"
                "If this request has been submitted in error and you do not\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
@@ -89,26 +113,19 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to subscribe "
-               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you do not "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will not receive any further mailings.</p>"
+               "<html><body><p>Someone (probably you) has submitted a request to subscribe\n"
+               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>\n"
+               "<p>Please go here to confirm this request:</p>\n"
+               "<p><a href=\"%s\">%s</a></p>\n"
+               "<p>If this request has been submitted in error and you do not\n"
+               "wish to receive the <strong>%s</strong> mailing list, simply do nothing,\n"
+               "and you will not receive any further mailings.</p>\n"
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
+               emailaddr, roomname, confirm_subscribe_url, roomname,
+               emailaddr, roomname, confirm_subscribe_url, confirm_subscribe_url, roomname
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
@@ -118,9 +135,9 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
 
 // This generates an email with a link the user clicks to confirm a list unsubscription.
 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
 
 // This generates an email with a link the user clicks to confirm a list unsubscription.
 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char confirm_unsubscribe_url[SIZ];
+       generate_one_click_url(confirm_unsubscribe_url, url, "confirm_unsubscribe", roomname, emailaddr);
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
@@ -139,7 +156,7 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *
                "<%s> from the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
                "<%s> from the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
+               "%s\n"
                "\n"
                "If this request has been submitted in error and you still\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
                "\n"
                "If this request has been submitted in error and you still\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
@@ -148,26 +165,19 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
-               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you still "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will remain subscribed.</p>"
+               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe\n"
+               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>\n"
+               "<p>Please go here to confirm this request:</p>\n"
+               "<p><a href=\"%s\">%s</a></p>\n"
+               "<p>If this request has been submitted in error and you still\n"
+               "wish to receive the <strong>%s</strong> mailing list, simply do nothing,\n"
+               "and you will remain subscribed.</p>\n"
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
+               emailaddr, roomname, confirm_unsubscribe_url, roomname,
+               emailaddr, roomname, confirm_unsubscribe_url, confirm_unsubscribe_url, roomname
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
index edce1f5c387088008f16b42ca651860bb6d42dbb..e169f9e7cdd507ce42a4b77becf0ef4c2bdfbda5 100644 (file)
@@ -2,7 +2,7 @@
 //
 // This is the new, exciting, clever version that makes libcurl do all the work  :)
 //
 //
 // This is the new, exciting, clever version that makes libcurl do all the work  :)
 //
-// Copyright (c) 1997-2023 by the citadel.org team
+// Copyright (c) 1997-2024 by the citadel.org team
 //
 // This program is open source software.  Use, duplication, or disclosure
 // is subject to the terms of the GNU General Public License, version 3.
 //
 // This program is open source software.  Use, duplication, or disclosure
 // is subject to the terms of the GNU General Public License, version 3.
@@ -213,29 +213,32 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *sou
        process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
        num_mx = getmx(mxes, node);
        if (num_mx < 1) {
        process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
        num_mx = getmx(mxes, node);
        if (num_mx < 1) {
-               return (421);
+               return(421);
        }
 
        CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
        }
 
        CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+
+       // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
        if (!IsEmptyStr(source_room)) {
        if (!IsEmptyStr(source_room)) {
-               // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
-               char esc_room[ROOMNAMELEN*2];
-               char esc_email[1024];
-               urlesc(esc_room, sizeof esc_room, source_room);
-               urlesc(esc_email, sizeof esc_email, recp);
-               cprintf("List-Unsubscribe: <http://%s/listsub?cmd=unsubscribe&room=%s&email=%s>\r\n",
-                       CtdlGetConfigStr("c_fqdn"),
-                       esc_room,
-                       esc_email
-               );
+               char base_url[SIZ];
+               char unsubscribe_url[SIZ];
+               snprintf(base_url, sizeof base_url, "https://%s/listsub", CtdlGetConfigStr("c_fqdn"));
+               generate_one_click_url(unsubscribe_url, base_url, "unsubscribe", source_room, recp);
+               cprintf("List-Unsubscribe: %s\r\n", unsubscribe_url);
+               cprintf("List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");       // RFC 8058
+
        }
        }
+
        CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
        s.TheMessage = CC->redirect_buffer;
        CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
        s.TheMessage = CC->redirect_buffer;
-       s.bytes_total = StrLength(CC->redirect_buffer);
-       s.bytes_sent = 0;
        CC->redirect_buffer = NULL;
        CC->redirect_buffer = NULL;
+       s.bytes_total = StrLength(s.TheMessage);
+       s.bytes_sent = 0;
        response_code = 421;
        response_code = 421;
-                                       // keep trying MXes until one works or we run out
+
+       // Future enhancement: if we implement DKIM signing, this is where it must happen.
+
+       // Keep trying MXes until one works or we run out.
        for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
                response_code = 421;    // default 421 makes non-protocol errors transient
                s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
        for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
                response_code = 421;    // default 421 makes non-protocol errors transient
                s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
@@ -323,7 +326,7 @@ void smtp_process_one_msg(long qmsgnum) {
 
        msg = CtdlFetchMessage(qmsgnum, 1);
        if (msg == NULL) {
 
        msg = CtdlFetchMessage(qmsgnum, 1);
        if (msg == NULL) {
-               syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
+               syslog(LOG_WARNING, "smtpclient: msg#%ld does not exist", qmsgnum);
                return;
        }
 
                return;
        }
 
@@ -463,7 +466,7 @@ void smtp_process_one_msg(long qmsgnum) {
                }
        }
        else {
                }
        }
        else {
-               syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
+               syslog(LOG_DEBUG, "smtpclient: msg#%ld retry time not reached", qmsgnum);
        }
 
        if (bounceto != NULL) {
        }
 
        if (bounceto != NULL) {