From: Art Cancro Date: Thu, 13 Jan 2022 16:48:37 +0000 (-0500) Subject: Big change to mailing list subscription/unsubscription! X-Git-Tag: v950~47 X-Git-Url: https://code.citadel.org/?p=citadel.git;a=commitdiff_plain;h=60e1890d866f912f94b604f3af05bcd421109469 Big change to mailing list subscription/unsubscription! The old confirmation method involved generating a confirmation token during the first opt, which was mailed to the user and saved to disk so they could confirm it in the second opt. In the new code, the token can be re-generated persistently by the server using a combination of the email address, the room name, and a host key that is known only to the site operator (stored in the config db). So there is no longer a need to store the pending request, and the confirmation links are valid forever (and reusable!). Aside from being algorithmically nifty, this will also give us the ability to implement "one click unsubscribe" in the near future. --- diff --git a/citadel/configure.ac b/citadel/configure.ac index 8320a9dbf..e2b50749e 100644 --- a/citadel/configure.ac +++ b/citadel/configure.ac @@ -181,6 +181,25 @@ AC_CHECK_HEADER(zlib.h, ) CFLAGS="$saved_CFLAGS" +dnl Checks for the crypt_r function +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $SERVER_LIBS" +AC_CHECK_HEADER(crypt.h, + [AC_CHECK_LIB(crypt, crypt_r, + [ + SERVER_LIBS="-lcrypt $SERVER_LIBS" + ], + [ + AC_MSG_ERROR(crypt_r was not found or is not usable.) + ] + , + )], + [ + AC_MSG_ERROR(crypt.h was not found or is not usable.) + ] +) +CFLAGS="$saved_CFLAGS" + dnl Here is the check for a libc integrated iconv AC_ARG_ENABLE(iconv, [ --disable-iconv do not use iconv charset conversion], diff --git a/citadel/modules/listsub/serv_listsub.c b/citadel/modules/listsub/serv_listsub.c index 073ddafae..0c8335a50 100644 --- a/citadel/modules/listsub/serv_listsub.c +++ b/citadel/modules/listsub/serv_listsub.c @@ -1,7 +1,6 @@ -// // This module handles self-service subscription/unsubscription to mail lists. // -// Copyright (c) 2002-2021 by the citadel.org team +// Copyright (c) 2002-2022 by the citadel.org team // // This program is open source software. It runs great on the // Linux operating system (and probably elsewhere). You can use, @@ -28,6 +27,7 @@ #include #include #include +#include #include #include "citadel.h" #include "server.h" @@ -48,9 +48,26 @@ enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it kno }; -/* - * This generates an email with a link the user clicks to confirm a list subscription. - */ +// The confirmation token will be generated by combining the room name and email address with the host key, +// and then generating an encrypted hash of that string. The encrypted hash is included as part of the +// confirmation link. +void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) { + char string_to_hash[1024]; + struct crypt_data cd; + char *ptr; + + snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key")); + memset(&cd, 0, sizeof cd); + + strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len); + + for (ptr=token_buf; *ptr; ++ptr) { + if (!isalnum((char)*ptr)) *ptr='X'; + } +} + + +// 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]; @@ -73,7 +90,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?room=%s&token=%s&cmd=confirm\n" + "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%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" @@ -85,8 +102,8 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur "

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

" "

Please go here to confirm this request:

" - "

" - "%s?room=%s&token=%s&cmd=confirm

" + "

" + "%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.

" @@ -95,22 +112,21 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur "--__ctdlmultipart__--\n" , emailaddr, roomname, - url, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, roomname , emailaddr, roomname, - url, urlroom, confirmation_token, - url, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, roomname ); quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription"); + cprintf("%d confirmation email sent\n", CIT_OK); } -/* - * This generates an email with a link the user clicks to confirm a list unsubscription. - */ +// 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]; @@ -133,7 +149,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?room=%s&token=%s&cmd=confirm\n" + "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%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" @@ -145,8 +161,8 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char * "

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

" "

Please go here to confirm this request:

" - "

" - "%s?room=%s&token=%s&cmd=confirm

" + "

" + "%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.

" @@ -155,159 +171,37 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char * "--__ctdlmultipart__--\n" , emailaddr, roomname, - url, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, roomname , emailaddr, roomname, - url, urlroom, confirmation_token, - url, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, + url, emailaddr, urlroom, confirmation_token, roomname ); quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription"); + cprintf("%d confirmation email sent\n", CIT_OK); } -/* - * "Subscribe" and "Unsubscribe" operations are so similar that they share a function. - * The actual subscription doesn't take place here -- we just send out the confirmation request - * and record the address and confirmation token. - */ -void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url) { - - int i; - char buf[1024]; - char confirmation_token[40]; - - // Update this room's netconfig with the updated lastsent - begin_critical_section(S_NETCONFIGS); - char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber); - if (!oldnetconfig) { - oldnetconfig = strdup(""); - } - - // The new netconfig begins with an empty buffer... - char *newnetconfig = malloc(strlen(oldnetconfig) + 1024); - newnetconfig[0] = 0; - - // And then we... - int is_already_subscribed = 0; - int config_lines = num_tokens(oldnetconfig, '\n'); - for (i=0; i 259200) { - syslog(LOG_DEBUG, "%s %s is %ld seconds old - deleting it", buf_email, buf_directive, time(NULL) - pendingtime); - keep_this_line = 0; - } - } - - if (keep_this_line) { - sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf); - } - } - - // Do we need to send out a confirmation email? - if ((action == SUBSCRIBE) && (!is_already_subscribed)) { - generate_uuid(confirmation_token); - sprintf(&newnetconfig[strlen(newnetconfig)], "subpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url); - send_subscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token); - } - if ((action == UNSUBSCRIBE) && (is_already_subscribed)) { - generate_uuid(confirmation_token); - sprintf(&newnetconfig[strlen(newnetconfig)], "unsubpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url); - send_unsubscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token); - } - - // Write the new netconfig back to disk - SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig); - end_critical_section(S_NETCONFIGS); - free(newnetconfig); // this was the new netconfig, free it because we're done with it - free(oldnetconfig); // this was the old netconfig, free it even if we didn't do anything - - // Tell the client what happened. - if ((action == SUBSCRIBE) && (is_already_subscribed)) { - cprintf("%d This email address is already subscribed.\n", ERROR + ALREADY_EXISTS); - } - else if ((action == SUBSCRIBE) && (!is_already_subscribed)) { - cprintf("%d Subscription was requested, and a confirmation email was sent.\n", CIT_OK); - } - else if ((action == UNSUBSCRIBE) && (!is_already_subscribed)) { - cprintf("%d This email address is not subscribed.\n", ERROR + NO_SUCH_USER); - } - else if ((action == UNSUBSCRIBE) && (is_already_subscribed)) { - cprintf("%d Unsubscription was requested, and a confirmation email was sent.\n", CIT_OK); - } - else { - cprintf("%d Nothing happens.\n", ERROR); - } -} - - -/* - * Confirm a list subscription or unsubscription - */ -void do_confirm(char *token) { - int yes_subscribe = 0; // Set to 1 if the confirmation to subscribe is validated. - int yes_unsubscribe = 0; // Set to 1 if the confirmation to unsubscribe is validated. +// Confirm a list subscription or unsubscription +void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) { int i; char buf[1024]; int config_lines = 0; - char pending_directive[128]; - char pending_email[256]; - char pending_token[128]; + char *oldnetconfig, *newnetconfig; - // We will have to do this in two passes. The first pass checks to see if we have a confirmation request matching the token. - char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber); - if (!oldnetconfig) { - cprintf("%d There are no pending requests.\n", ERROR + NO_SUCH_USER); + // The server has generated a persistent confirmation token for the user+room combination. + // Let's see if the user has supplied the same token. + if (strcmp(generated_token, supplied_token)) { + cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED); return; } - config_lines = num_tokens(oldnetconfig, '\n'); - for (i=0; iroom.QRnumber); if (!oldnetconfig) { oldnetconfig = strdup(""); @@ -315,27 +209,28 @@ void do_confirm(char *token) { // The new netconfig begins with an empty buffer... begin_critical_section(S_NETCONFIGS); - char *newnetconfig = malloc(strlen(oldnetconfig) + 1024); + newnetconfig = malloc(strlen(oldnetconfig) + 1024); newnetconfig[0] = 0; + // Load the config lines in one by one, skipping any that reference this subscriber. Also remove blank lines. config_lines = num_tokens(oldnetconfig, '\n'); for (i=0; iroom.QRnumber, newnetconfig); end_critical_section(S_NETCONFIGS); free(oldnetconfig); @@ -344,18 +239,24 @@ void do_confirm(char *token) { } -/* - * process subscribe/unsubscribe requests and confirmations - */ +// process subscribe/unsubscribe requests and confirmations void cmd_lsub(char *cmdbuf) { char cmd[20]; char roomname[ROOMNAMELEN]; char emailaddr[1024]; char url[1024]; - char token[128]; + char generated_token[128]; + char supplied_token[128]; extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name + extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address + extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed + extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token); // token 4 is the token supplied by the caller + + syslog(LOG_DEBUG, "cmd_lsub(cmd=%s, roomname=%s, emailaddr=%s, url=%s, token=%s", + cmd, roomname, emailaddr, url, supplied_token + ); // First confirm that the caller is referencing a room that actually exists. if (CtdlGetRoom(&CC->room, roomname) != 0) { @@ -369,22 +270,22 @@ void cmd_lsub(char *cmdbuf) { } // Room confirmed, now parse the command. + generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr); if (!strcasecmp(cmd, "subscribe")) { - extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address - extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed - do_subscribe_or_unsubscribe(SUBSCRIBE, emailaddr, url); + send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token); } else if (!strcasecmp(cmd, "unsubscribe")) { - extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address - extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed - do_subscribe_or_unsubscribe(UNSUBSCRIBE, emailaddr, url); + send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token); + } + + else if (!strcasecmp(cmd, "confirm_subscribe")) { + do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token); } - else if (!strcasecmp(cmd, "confirm")) { - extract_token(token, cmdbuf, 2, '|', sizeof token); // token 2 is the confirmation token - do_confirm(token); + else if (!strcasecmp(cmd, "confirm_unsubscribe")) { + do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token); } else { // sorry man, I can't deal with that diff --git a/webcit/listsub.c b/webcit/listsub.c index 0b6a90b03..6236f4b36 100644 --- a/webcit/listsub.c +++ b/webcit/listsub.c @@ -83,31 +83,12 @@ int Conditional_LISTSUB_EXECUTE_UNSUBSCRIBE(StrBuf *Target, WCTemplputParams *TP } -int Conditional_LISTSUB_EXECUTE_CONFIRM_SUBSCRIBE(StrBuf *Target, WCTemplputParams *TP) { +int confirm_sub_or_unsub(char *cmd, StrBuf *Target, WCTemplputParams *TP) { int rc; StrBuf *Line; - const char *ImpMsg; - const StrBuf *Room, *Token; - - if (strcmp(bstr("cmd"), "confirm")) { - return 0; - } - - Room = sbstr("room"); - if (Room == NULL) { - ImpMsg = _("You need to specify the mailinglist to subscribe to."); - AppendImportantMessage(ImpMsg, -1); - return 0; - } - Token = sbstr("token"); - if (Room == NULL) { - ImpMsg = _("You need to specify the mailinglist to subscribe to."); - AppendImportantMessage(ImpMsg, -1); - return 0; - } Line = NewStrBuf(); - serv_printf("LSUB confirm|%s|%s", ChrPtr(Room), ChrPtr(Token)); + serv_printf("LSUB %s|%s|%s|%s/listsub|%s", cmd, bstr("room"), bstr("email"), ChrPtr(site_prefix), bstr("token")); StrBuf_ServGetln(Line); rc = GetServerStatusMsg(Line, NULL, 1, 2); FreeStrBuf(&Line); @@ -117,6 +98,21 @@ int Conditional_LISTSUB_EXECUTE_CONFIRM_SUBSCRIBE(StrBuf *Target, WCTemplputPara return rc == 2; } +int Conditional_LISTSUB_EXECUTE_CONFIRMSUBSCRIBE(StrBuf *Target, WCTemplputParams *TP) { + if (strcmp(bstr("cmd"), "confirm_subscribe")) { + return 0; + } + return(confirm_sub_or_unsub("confirm_subscribe", Target, TP)); +} + + +int Conditional_LISTSUB_EXECUTE_CONFIRMUNSUBSCRIBE(StrBuf *Target, WCTemplputParams *TP) { + if (strcmp(bstr("cmd"), "confirm_unsubscribe")) { + return 0; + } + return(confirm_sub_or_unsub("confirm_unsubscribe", Target, TP)); +} + void do_listsub(void) { if (!havebstr("cmd")) { @@ -134,6 +130,7 @@ InitModule_LISTSUB { RegisterConditional("COND:LISTSUB:EXECUTE:SUBSCRIBE", 0, Conditional_LISTSUB_EXECUTE_SUBSCRIBE, CTX_NONE); RegisterConditional("COND:LISTSUB:EXECUTE:UNSUBSCRIBE", 0, Conditional_LISTSUB_EXECUTE_UNSUBSCRIBE, CTX_NONE); - RegisterConditional("COND:LISTSUB:EXECUTE:CONFIRM:SUBSCRIBE", 0, Conditional_LISTSUB_EXECUTE_CONFIRM_SUBSCRIBE, CTX_NONE); + RegisterConditional("COND:LISTSUB:EXECUTE:CONFIRMSUBSCRIBE", 0, Conditional_LISTSUB_EXECUTE_CONFIRMSUBSCRIBE, CTX_NONE); + RegisterConditional("COND:LISTSUB:EXECUTE:CONFIRMUNSUBSCRIBE", 0, Conditional_LISTSUB_EXECUTE_CONFIRMUNSUBSCRIBE, CTX_NONE); WebcitAddUrlHandler(HKEY("listsub"), "", 0, do_listsub, ANONYMOUS|COOKIEUNNEEDED|FORCE_SESSIONCLOSE); } diff --git a/webcit/static/t/listsub/display.html b/webcit/static/t/listsub/display.html index 121bb5198..a431f93d8 100644 --- a/webcit/static/t/listsub/display.html +++ b/webcit/static/t/listsub/display.html @@ -52,8 +52,8 @@ - - + +

@@ -66,6 +66,20 @@ + + +

+ +

+ +
    +
  • +
  • already successfully confirmed your subscribe/unsubscribe request and are attempting to do it again.")>
  • +
+ + + +