X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Flistsub%2Fserv_listsub.c;h=34939eb15cdc060c3719417307815d100ba85cdb;hb=fd396aeb6d3e10be928dd899ae228147b1728fb3;hp=a233e6c0e4d349a1b2c0c634fc46a24a40e3285d;hpb=d3a29d82d6492191ae8208617e8ac1f088bcf14a;p=citadel.git diff --git a/citadel/modules/listsub/serv_listsub.c b/citadel/modules/listsub/serv_listsub.c index a233e6c0e..34939eb15 100644 --- a/citadel/modules/listsub/serv_listsub.c +++ b/citadel/modules/listsub/serv_listsub.c @@ -1,23 +1,17 @@ -/* - * This module handles self-service subscription/unsubscription to mail lists. - * - * Copyright (c) 2002-2012 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 distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * - * - * - * - */ +// +// This module handles self-service subscription/unsubscription to mail lists. +// +// Copyright (c) 2002-2021 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, +// copy, and run it under the terms of the GNU General Public +// License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. #include "sysdep.h" #include @@ -30,17 +24,7 @@ #include #include #include -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - +#include #include #include #include @@ -55,120 +39,28 @@ #include "msgbase.h" #include "internet_addressing.h" #include "clientsocket.h" -#include "file_ops.h" - -#ifndef HAVE_SNPRINTF -#include "snprintf.h" -#endif - - - #include "ctdl_module.h" -/* - * Generate a randomizationalisticized token to use for authentication of - * a subscribe or unsubscribe request. - */ -void listsub_generate_token(char *buf) { - char sourcebuf[SIZ]; - static int seq = 0; - - /* Theo, please sit down and shut up. This key doesn't have to be - * tinfoil-hat secure, it just needs to be reasonably unguessable - * and unique. - */ - sprintf(sourcebuf, "%lx", - (long) (++seq + getpid() + time(NULL)) - ); - - /* Convert it to base64 so it looks cool */ - CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf), 0); -} +enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for + UNSUBSCRIBE, + SUBSCRIBE +}; /* - * Enter a subscription request + * This generates an email with a link the user clicks to confirm a list subscription. */ -void do_subscribe(char *room, char *email, char *subtype, char *webpage) { - struct ctdlroom qrbuf; - FILE *ncfp; - char filename[256]; - char token[256]; - char confirmation_request[2048]; - char buf[512]; - char urlroom[ROOMNAMELEN]; - char scancmd[64]; - char scanemail[256]; - int found_sub = 0; - - if (CtdlGetRoom(&qrbuf, room) != 0) { - cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, room); - return; - } - - if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { - cprintf("%d '%s' " - "does not accept subscribe/unsubscribe requests.\n", - ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); - return; - } - - listsub_generate_token(token); - - assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir); - - /* - * Make sure the requested address isn't already subscribed - */ - begin_critical_section(S_NETCONFIGS); - ncfp = fopen(filename, "r"); - if (ncfp != NULL) { - while (fgets(buf, sizeof buf, ncfp) != NULL) { - buf[strlen(buf)-1] = 0; - extract_token(scancmd, buf, 0, '|', sizeof scancmd); - extract_token(scanemail, buf, 1, '|', sizeof scanemail); - if ((!strcasecmp(scancmd, "listrecp")) - || (!strcasecmp(scancmd, "digestrecp"))) { - if (!strcasecmp(scanemail, email)) { - ++found_sub; - } - } - } - fclose(ncfp); - } - end_critical_section(S_NETCONFIGS); - - if (found_sub != 0) { - cprintf("%d '%s' is already subscribed to '%s'.\n", - ERROR + ALREADY_EXISTS, - email, qrbuf.QRname); - return; - } - - /* - * Now add it to the file - */ - begin_critical_section(S_NETCONFIGS); - ncfp = fopen(filename, "a"); - if (ncfp != NULL) { - fprintf(ncfp, "subpending|%s|%s|%s|%ld|%s\n", - email, - subtype, - token, - time(NULL), - webpage - ); - fclose(ncfp); - } - end_critical_section(S_NETCONFIGS); +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); - /* Generate and send the confirmation request */ - - urlesc(urlroom, ROOMNAMELEN, qrbuf.QRname); - - snprintf(confirmation_request, sizeof confirmation_request, + char from_address[1024]; + snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn")); + char emailtext[SIZ]; + snprintf(emailtext, sizeof emailtext, "MIME-Version: 1.0\n" "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n" "\n" @@ -178,137 +70,57 @@ void do_subscribe(char *room, char *email, char *subtype, char *webpage) { "Content-type: text/plain\n" "\n" "Someone (probably you) has submitted a request to subscribe\n" - "<%s> to the '%s' mailing list.\n" + "<%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?room=%s&token=%s&cmd=confirm\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" + "wish to receive the <%s> mailing list, simply do nothing,\n" "and you will not receive any further mailings.\n" "\n" "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" - "\n" - "Someone (probably you) has submitted a request to subscribe\n" - "<%s> to the %s mailing list.

\n" - "Please click here to confirm this request:
\n" - "" - "%s?room=%s&token=%s&cmd=confirm

\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" + "

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

" + "

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.

" + "\n" "\n" - "--__ctdlmultipart__--\n", - - email, qrbuf.QRname, - webpage, urlroom, token, - qrbuf.QRname, - - email, qrbuf.QRname, - webpage, urlroom, token, - webpage, urlroom, token, - qrbuf.QRname - ); - - quickie_message( /* This delivers the message */ - "Citadel", - NULL, - email, - NULL, - confirmation_request, - FMT_RFC822, - "Please confirm your list subscription" + "--__ctdlmultipart__--\n" + , + emailaddr, roomname, + url, urlroom, confirmation_token, + roomname + , + emailaddr, roomname, + url, urlroom, confirmation_token, + url, urlroom, confirmation_token, + roomname ); - cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK); + quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription"); } /* - * Enter an unsubscription request + * This generates an email with a link the user clicks to confirm a list unsubscription. */ -void do_unsubscribe(char *room, char *email, char *webpage) { - struct ctdlroom qrbuf; - FILE *ncfp; - char filename[256]; - char token[256]; - char buf[512]; - char confirmation_request[2048]; - char urlroom[ROOMNAMELEN]; - char scancmd[256]; - char scanemail[256]; - int found_sub = 0; - - if (CtdlGetRoom(&qrbuf, room) != 0) { - cprintf("%d There is no list called '%s'\n", - ERROR + ROOM_NOT_FOUND, room); - return; - } - - if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { - cprintf("%d '%s' " - "does not accept subscribe/unsubscribe requests.\n", - ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); - return; - } - - listsub_generate_token(token); - - assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir); +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); - /* - * Make sure there's actually a subscription there to remove - */ - begin_critical_section(S_NETCONFIGS); - ncfp = fopen(filename, "r"); - if (ncfp != NULL) { - while (fgets(buf, sizeof buf, ncfp) != NULL) { - buf[strlen(buf)-1] = 0; - extract_token(scancmd, buf, 0, '|', sizeof scancmd); - extract_token(scanemail, buf, 1, '|', sizeof scanemail); - if ((!strcasecmp(scancmd, "listrecp")) - || (!strcasecmp(scancmd, "digestrecp"))) { - if (!strcasecmp(scanemail, email)) { - ++found_sub; - } - } - } - fclose(ncfp); - } - end_critical_section(S_NETCONFIGS); - - if (found_sub == 0) { - cprintf("%d '%s' is not subscribed to '%s'.\n", - ERROR + NO_SUCH_USER, - email, qrbuf.QRname); - return; - } - - /* - * Ok, now enter the unsubscribe-pending entry. - */ - begin_critical_section(S_NETCONFIGS); - ncfp = fopen(filename, "a"); - if (ncfp != NULL) { - fprintf(ncfp, "unsubpending|%s|%s|%ld|%s\n", - email, - token, - time(NULL), - webpage - ); - fclose(ncfp); - } - end_critical_section(S_NETCONFIGS); - - /* Generate and send the confirmation request */ - - urlesc(urlroom, ROOMNAMELEN, qrbuf.QRname); - - snprintf(confirmation_request, sizeof confirmation_request, + char from_address[1024]; + snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn")); + char emailtext[SIZ]; + snprintf(emailtext, sizeof emailtext, "MIME-Version: 1.0\n" "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n" "\n" @@ -318,256 +130,266 @@ void do_unsubscribe(char *room, char *email, char *webpage) { "Content-type: text/plain\n" "\n" "Someone (probably you) has submitted a request to unsubscribe\n" - "<%s> from the '%s' mailing list.\n" + "<%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?room=%s&token=%s&cmd=confirm\n" "\n" - "If this request has been submitted in error and you do not\n" - "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n" - "and the request will not be processed.\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" "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" - "\n" - "Someone (probably you) has submitted a request to unsubscribe\n" - "<%s> from the %s mailing list.

\n" - "Please click here to confirm this request:
\n" - "" - "%s?room=%s&token=%s&cmd=confirm

\n" - "If this request has been submitted in error and you do not\n" - "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n" - "and the request will not be processed.\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?room=%s&token=%s&cmd=confirm

" + "

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.

" + "\n" "\n" - "--__ctdlmultipart__--\n", + "--__ctdlmultipart__--\n" + , + emailaddr, roomname, + url, urlroom, confirmation_token, + roomname + , + emailaddr, roomname, + url, urlroom, confirmation_token, + url, urlroom, confirmation_token, + roomname + ); - email, qrbuf.QRname, - webpage, urlroom, token, - qrbuf.QRname, + quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription"); +} - email, qrbuf.QRname, - webpage, urlroom, token, - webpage, urlroom, token, - qrbuf.QRname - ); - quickie_message( /* This delivers the message */ - "Citadel", - NULL, - email, - NULL, - confirmation_request, - FMT_RFC822, - "Please confirm your unsubscribe request" - ); +/* + * "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 subscribe/unsubscribe request. + * Confirm a list subscription or unsubscription */ -void do_confirm(char *room, char *token) { - struct ctdlroom qrbuf; - FILE *ncfp; - char filename[256]; - char line_token[256]; - long line_offset; - int line_length; - char buf[512]; - char cmd[256]; - char email[256] = ""; - char subtype[128]; - int success = 0; - char address_to_unsubscribe[256] = ""; - char scancmd[256]; - char scanemail[256]; - char *holdbuf = NULL; - int linelen = 0; - int buflen = 0; - - if (CtdlGetRoom(&qrbuf, room) != 0) { - cprintf("%d There is no list called '%s'\n", - ERROR + ROOM_NOT_FOUND, room); +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. + int i; + char buf[1024]; + int config_lines = 0; + char pending_directive[128]; + char pending_email[256]; + char pending_token[128]; + + // 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); return; } - if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { - cprintf("%d '%s' " - "does not accept subscribe/unsubscribe requests.\n", - ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); - return; - } + config_lines = num_tokens(oldnetconfig, '\n'); + for (i=0; iroom.QRnumber); + if (!oldnetconfig) { + oldnetconfig = strdup(""); } - else { - cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE); + + // The new netconfig begins with an empty buffer... + begin_critical_section(S_NETCONFIGS); + char *newnetconfig = malloc(strlen(oldnetconfig) + 1024); + newnetconfig[0] = 0; + + config_lines = num_tokens(oldnetconfig, '\n'); + for (i=0; iroom.QRnumber, newnetconfig); + end_critical_section(S_NETCONFIGS); + free(oldnetconfig); + free(newnetconfig); + cprintf("%d The pending request was confirmed.\n", CIT_OK); +} /* * process subscribe/unsubscribe requests and confirmations */ -void cmd_subs(char *cmdbuf) { - - char opr[256]; - char room[ROOMNAMELEN]; - char email[256]; - char subtype[256]; - char token[256]; - char webpage[256]; - - extract_token(opr, cmdbuf, 0, '|', sizeof opr); - if (!strcasecmp(opr, "subscribe")) { - extract_token(subtype, cmdbuf, 3, '|', sizeof subtype); - if ( (strcasecmp(subtype, "list")) - && (strcasecmp(subtype, "digest")) ) { - cprintf("%d Invalid subscription type '%s'\n", - ERROR + ILLEGAL_VALUE, subtype); - } - else { - extract_token(room, cmdbuf, 1, '|', sizeof room); - extract_token(email, cmdbuf, 2, '|', sizeof email); - extract_token(webpage, cmdbuf, 4, '|', sizeof webpage); - do_subscribe(room, email, subtype, webpage); - } +void cmd_lsub(char *cmdbuf) { + char cmd[20]; + char roomname[ROOMNAMELEN]; + char emailaddr[1024]; + char options[256]; + char url[1024]; + char 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 + + // First confirm that the caller is referencing a room that actually exists. + if (CtdlGetRoom(&CC->room, roomname) != 0) { + cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname); + return; + } + + if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) { + cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname); + return; + } + + // Room confirmed, now parse the command. + + 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); } - else if (!strcasecmp(opr, "unsubscribe")) { - extract_token(room, cmdbuf, 1, '|', sizeof room); - extract_token(email, cmdbuf, 2, '|', sizeof email); - extract_token(webpage, cmdbuf, 3, '|', sizeof webpage); - do_unsubscribe(room, email, webpage); + + 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); } - else if (!strcasecmp(opr, "confirm")) { - extract_token(room, cmdbuf, 1, '|', sizeof room); - extract_token(token, cmdbuf, 2, '|', sizeof token); - do_confirm(room, token); + + else if (!strcasecmp(cmd, "confirm")) { + extract_token(token, cmdbuf, 2, '|', sizeof token); // token 2 is the confirmation token + do_confirm(token); } - else { - cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE); + + else { // sorry man, I can't deal with that + cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd); } } @@ -579,7 +401,7 @@ CTDL_MODULE_INIT(listsub) { if (!threading) { - CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe"); + CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe"); } /* return our module name for the log */