Big change to mailing list subscription/unsubscription!
authorArt Cancro <ajc@citadel.org>
Thu, 13 Jan 2022 16:48:37 +0000 (11:48 -0500)
committerArt Cancro <ajc@citadel.org>
Thu, 13 Jan 2022 16:48:37 +0000 (11:48 -0500)
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.

citadel/configure.ac
citadel/modules/listsub/serv_listsub.c
webcit/listsub.c
webcit/static/t/listsub/display.html

index 8320a9dbf4b19f9d659f59054baa860675e7dd6e..e2b50749e3a3293ffb4e5d8f31d1ce983ee63886 100644 (file)
@@ -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],
index 073ddafaee01c7cd8b0e771968db1fd76df6a8e7..0c8335a50fd515faee319a58d72447297ef30a8c 100644 (file)
@@ -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 <sys/wait.h>
 #include <string.h>
 #include <limits.h>
+#include <crypt.h>
 #include <libcitadel.h>
 #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
                "<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?room=%s&token=%s&cmd=confirm\">"
-               "%s?room=%s&token=%s&cmd=confirm</a></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>"
@@ -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 *
                "<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?room=%s&token=%s&cmd=confirm\">"
-               "%s?room=%s&token=%s&cmd=confirm</a></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>"
@@ -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<config_lines; ++i) {
-               extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
-               int keep_this_line =1;                                          // set to nonzero if we are discarding a line
-
-               if (IsEmptyStr(buf)) {
-                       keep_this_line = 0;
-               }
-
-               char buf_directive[1024];
-               char buf_email[1024];
-               extract_token(buf_directive, buf, 0, '|', sizeof buf_directive);
-               extract_token(buf_email, buf, 1, '|', sizeof buf_email);
-
-               if (    ( (!strcasecmp(buf_directive, "listrecp")) || (!strcasecmp(buf_directive, "digestrecp")) )
-                       && (!strcasecmp(buf_email, emailaddr)) 
-               ) {
-                       is_already_subscribed = 1;
-               }
-
-               if ( (!strcasecmp(buf_directive, "subpending")) || (!strcasecmp(buf_directive, "unsubpending")) ) {
-                       time_t pendingtime = extract_long(buf, 3);
-                       if ((time(NULL) - pendingtime) > 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; i<config_lines; ++i) {
-               extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
-               extract_token(pending_directive, buf, 0, '|', sizeof pending_directive);
-               extract_token(pending_email, buf, 1, '|', sizeof pending_email);
-               extract_token(pending_token, buf, 2, '|', sizeof pending_token);
-
-               if (!strcasecmp(pending_token, token)) {
-                       if (!strcasecmp(pending_directive, "subpending")) {
-                               yes_subscribe = 1;
-                       }
-                       else if (!strcasecmp(pending_directive, "unsubpending")) {
-                               yes_unsubscribe = 1;
-                       }
-               }
-       }
-       free(oldnetconfig);
-
-       // We didn't find a pending subscribe or unsubscribe request with the supplied token.
-       if ((!yes_subscribe) && (!yes_unsubscribe)) {
-               cprintf("%d The request you are trying to confirm was not found.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
+       // If the generated token matches the supplied token, the request is authentic.  Do what it says.
 
-       // The second pass performs the now confirmed operation.
-       // We will have to do this in two passes.  The first pass checks to see if we have a confirmation request matching the token.
+       // Load the room's network configuration...
         oldnetconfig = LoadRoomNetConfigFile(CC->room.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; i<config_lines; ++i) {
                char buf_email[256];
                extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
-               extract_token(buf_email, buf, 1, '|', sizeof pending_email);
-               if (strcasecmp(buf_email, pending_email)) {
-                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);      // only keep lines that do not reference this subscriber
+               extract_token(buf_email, buf, 1, '|', sizeof buf_email);
+               if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
+                       sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
                }
        }
 
        // We have now removed all lines containing the subscriber's email address.  This deletes any pending requests.
        // If this was an unsubscribe operation, they're now gone from the list.
        // But if this was a subscribe operation, we now need to add them.
-       if (yes_subscribe) {
-               sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", pending_email);
+       if (cmd == SUBSCRIBE) {
+               sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
        }
 
-       // FIXME write it back to disk
+       // write it back to disk
        SaveRoomNetConfigFile(CC->room.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
index 0b6a90b03ca56be1866ad70c4c79be8a3c7ab9f8..6236f4b360bc7c5481e0fc0e3f572eac39280957 100644 (file)
@@ -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);
 }
index 121bb51983e34ebb1f9b4ca0bab081164c215229..a431f93d8076259f9bd27a1f57b0cfa1b659a4c1 100644 (file)
@@ -52,8 +52,8 @@
 <?!("X", 22)>
 <?!("X", 20)>
 
-<?!("COND:BSTR", 30, "cmd", "confirm")>
-<?!("COND:LISTSUB:EXECUTE:CONFIRM:SUBSCRIBE", 31)>
+<?!("COND:BSTR", 30, "cmd", "confirm_subscribe")>
+<?!("COND:LISTSUB:EXECUTE:CONFIRMSUBSCRIBE", 31)>
 <center><h1><?_("Confirmation successful!")></h1></center>
 <?!("X", 31)><??("COND:BSTR", 32, "__FAIL")>
 <center><h1><?_("Confirmation failed.")></h1></center>
 <??("X", 32)>
 <??("X", 30)>
 
+<?!("COND:BSTR", 33, "cmd", "confirm_unsubscribe")>
+<?!("COND:LISTSUB:EXECUTE:CONFIRMUNSUBSCRIBE", 34)>
+<center><h1><?_("Confirmation successful!")></h1></center>
+<?!("X", 34)><??("COND:BSTR", 35, "__FAIL")>
+<center><h1><?_("Confirmation failed.")></h1></center>
+<?_("This could mean one of two things:")>
+<ul>
+<li><?_("You waited too long to confirm your subscribe/unsubscribe request (the confirmation link is only valid for three days)")></li>
+<li><?_("You have <i>already</i> successfully confirmed your subscribe/unsubscribe request and are attempting to do it again.")></li>
+</ul>
+<?_("The error returned by the server was: ")><?IMPORTANTMESSAGE("X")>
+<??("X", 35)>
+<??("X", 33)>
+
 <?!("COND:BSTR", 40, "cmd", "choose")>
 
 <form method="POST" action="listsub">