From 99efe55958ebc96ab99b052dc67864ea1db64a62 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Fri, 19 Apr 2024 15:06:05 +0000 Subject: [PATCH] Mailing list header changes (fuck you Google) 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 | 32 ++----- citadel/server/internet_addressing.h | 1 + .../modules/listdeliver/serv_listdeliver.c | 4 +- citadel/server/modules/listsub/serv_listsub.c | 90 ++++++++++--------- citadel/server/modules/smtp/serv_smtpclient.c | 37 ++++---- 5 files changed, 81 insertions(+), 83 deletions(-) diff --git a/citadel/server/citserver.c b/citadel/server/citserver.c index da0865899..a6c950596 100644 --- a/citadel/server/citserver.c +++ b/citadel/server/citserver.c @@ -29,24 +29,8 @@ int panic_fd; // 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; - syslog(LOG_DEBUG, "master_startup() started"); + syslog(LOG_DEBUG, "citserver: master_startup() started"); time(&server_startup_time); - syslog(LOG_INFO, "Checking directory access"); + syslog(LOG_INFO, "citserver: checking directory access"); 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_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 - syslog(LOG_INFO, "Initializing configuration system"); + syslog(LOG_INFO, "citserver: initializing configuration system"); initialize_config_system(); validate_config(); migrate_legacy_control_record(); @@ -98,7 +82,7 @@ void master_startup(void) { // 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); @@ -116,7 +100,7 @@ void master_startup(void) { CtdlPutRoomLock(&qrbuf); } - syslog(LOG_DEBUG, "master_startup() finished"); + syslog(LOG_DEBUG, "citserver: master_startup() finished"); } diff --git a/citadel/server/internet_addressing.h b/citadel/server/internet_addressing.h index 16f1a0164..b0e48075c 100644 --- a/citadel/server/internet_addressing.h +++ b/citadel/server/internet_addressing.h @@ -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); +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 { diff --git a/citadel/server/modules/listdeliver/serv_listdeliver.c b/citadel/server/modules/listdeliver/serv_listdeliver.c index c342b0799..fd1356079 100644 --- a/citadel/server/modules/listdeliver/serv_listdeliver.c +++ b/citadel/server/modules/listdeliver/serv_listdeliver.c @@ -1,6 +1,6 @@ // 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. @@ -245,5 +245,5 @@ char *ctdl_module_init_listdeliver(void) { } // return our module name for the log - return "listsub"; + return "listdeliver"; } diff --git a/citadel/server/modules/listsub/serv_listsub.c b/citadel/server/modules/listsub/serv_listsub.c index 9eec96463..08aaabadf 100644 --- a/citadel/server/modules/listsub/serv_listsub.c +++ b/citadel/server/modules/listsub/serv_listsub.c @@ -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) { - // 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")); @@ -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?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" @@ -89,26 +113,19 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" - "

Someone (probably you) has submitted a request to subscribe " - "%s to the %s mailing list.

" - "

Please go here to confirm this request:

" - "

" - "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s

" - "

If this request has been submitted in error and you do not " - "wish to receive the %s mailing list, simply do nothing, " - "and you will not receive any further mailings.

" + "

Someone (probably you) has submitted a request to subscribe\n" + "%s to the %s mailing list.

\n" + "

Please go here to confirm this request:

\n" + "

%s

\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" + "and you will not receive any further mailings.

\n" "\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"); @@ -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) { - // 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")); @@ -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?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" @@ -148,26 +165,19 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char * "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" - "

Someone (probably you) has submitted a request to unsubscribe " - "%s from the %s mailing list.

" - "

Please go here to confirm this request:

" - "

" - "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s

" - "

If this request has been submitted in error and you still " - "wish to receive the %s mailing list, simply do nothing, " - "and you will remain subscribed.

" + "

Someone (probably you) has submitted a request to unsubscribe\n" + "%s from the %s mailing list.

\n" + "

Please go here to confirm this request:

\n" + "

%s

\n" + "

If this request has been submitted in error and you still\n" + "wish to receive the %s mailing list, simply do nothing,\n" + "and you will remain subscribed.

\n" "\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"); diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c index edce1f5c3..e169f9e7c 100644 --- a/citadel/server/modules/smtp/serv_smtpclient.c +++ b/citadel/server/modules/smtp/serv_smtpclient.c @@ -2,7 +2,7 @@ // // 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. @@ -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) { - return (421); + return(421); } 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 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: \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; - s.bytes_total = StrLength(CC->redirect_buffer); - s.bytes_sent = 0; CC->redirect_buffer = NULL; + s.bytes_total = StrLength(s.TheMessage); + s.bytes_sent = 0; 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 @@ -323,7 +326,7 @@ void smtp_process_one_msg(long qmsgnum) { 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; } @@ -463,7 +466,7 @@ void smtp_process_one_msg(long qmsgnum) { } } 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) { -- 2.30.2