"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/listsub/serv_listsub.c" (5 Jun 2021, 13952 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "serv_listsub.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 //
    2 // This module handles self-service subscription/unsubscription to mail lists.
    3 //
    4 // Copyright (c) 2002-2021 by the citadel.org team
    5 //
    6 // This program is open source software.  It runs great on the
    7 // Linux operating system (and probably elsewhere).  You can use,
    8 // copy, and run it under the terms of the GNU General Public
    9 // License version 3.
   10 //
   11 // This program is distributed in the hope that it will be useful,
   12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 // GNU General Public License for more details.
   15 
   16 #include "sysdep.h"
   17 #include <stdlib.h>
   18 #include <unistd.h>
   19 #include <stdio.h>
   20 #include <fcntl.h>
   21 #include <ctype.h>
   22 #include <signal.h>
   23 #include <pwd.h>
   24 #include <errno.h>
   25 #include <sys/types.h>
   26 #include <dirent.h>
   27 #include <time.h>
   28 #include <sys/wait.h>
   29 #include <string.h>
   30 #include <limits.h>
   31 #include <libcitadel.h>
   32 #include "citadel.h"
   33 #include "server.h"
   34 #include "citserver.h"
   35 #include "support.h"
   36 #include "config.h"
   37 #include "user_ops.h"
   38 #include "database.h"
   39 #include "msgbase.h"
   40 #include "internet_addressing.h"
   41 #include "clientsocket.h"
   42 #include "ctdl_module.h"
   43 
   44 
   45 enum {              // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
   46     UNSUBSCRIBE,
   47     SUBSCRIBE
   48 };
   49 
   50 
   51 /*
   52  * This generates an email with a link the user clicks to confirm a list subscription.
   53  */
   54 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
   55     // We need a URL-safe representation of the room name
   56     char urlroom[ROOMNAMELEN+10];
   57     urlesc(urlroom, sizeof(urlroom), roomname);
   58 
   59     char from_address[1024];
   60     snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
   61 
   62     char emailtext[SIZ];
   63     snprintf(emailtext, sizeof emailtext,
   64         "MIME-Version: 1.0\n"
   65         "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
   66         "\n"
   67         "This is a multipart message in MIME format.\n"
   68         "\n"
   69         "--__ctdlmultipart__\n"
   70         "Content-type: text/plain\n"
   71         "\n"
   72         "Someone (probably you) has submitted a request to subscribe\n"
   73         "<%s> to the <%s> mailing list.\n"
   74         "\n"
   75         "Please go here to confirm this request:\n"
   76         "%s?room=%s&token=%s&cmd=confirm\n"
   77         "\n"
   78         "If this request has been submitted in error and you do not\n"
   79         "wish to receive the <%s> mailing list, simply do nothing,\n"
   80         "and you will not receive any further mailings.\n"
   81         "\n"
   82         "--__ctdlmultipart__\n"
   83         "Content-type: text/html\n"
   84         "\n"
   85         "<html><body><p>Someone (probably you) has submitted a request to subscribe "
   86         "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
   87         "<p>Please go here to confirm this request:</p>"
   88         "<p><a href=\"%s?room=%s&token=%s&cmd=confirm\">"
   89         "%s?room=%s&token=%s&cmd=confirm</a></p>"
   90         "<p>If this request has been submitted in error and you do not "
   91         "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
   92         "and you will not receive any further mailings.</p>"
   93         "</body></html>\n"
   94         "\n"
   95         "--__ctdlmultipart__--\n"
   96         ,
   97         emailaddr, roomname,
   98         url, urlroom, confirmation_token,
   99         roomname
  100         ,
  101         emailaddr, roomname,
  102         url, urlroom, confirmation_token,
  103         url, urlroom, confirmation_token,
  104         roomname
  105     );
  106 
  107     quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
  108 }
  109 
  110 
  111 /*
  112  * This generates an email with a link the user clicks to confirm a list unsubscription.
  113  */
  114 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
  115     // We need a URL-safe representation of the room name
  116     char urlroom[ROOMNAMELEN+10];
  117     urlesc(urlroom, sizeof(urlroom), roomname);
  118 
  119     char from_address[1024];
  120     snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
  121 
  122     char emailtext[SIZ];
  123     snprintf(emailtext, sizeof emailtext,
  124         "MIME-Version: 1.0\n"
  125         "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
  126         "\n"
  127         "This is a multipart message in MIME format.\n"
  128         "\n"
  129         "--__ctdlmultipart__\n"
  130         "Content-type: text/plain\n"
  131         "\n"
  132         "Someone (probably you) has submitted a request to unsubscribe\n"
  133         "<%s> from the <%s> mailing list.\n"
  134         "\n"
  135         "Please go here to confirm this request:\n"
  136         "%s?room=%s&token=%s&cmd=confirm\n"
  137         "\n"
  138         "If this request has been submitted in error and you still\n"
  139         "wish to receive the <%s> mailing list, simply do nothing,\n"
  140         "and you will remain subscribed.\n"
  141         "\n"
  142         "--__ctdlmultipart__\n"
  143         "Content-type: text/html\n"
  144         "\n"
  145         "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
  146         "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
  147         "<p>Please go here to confirm this request:</p>"
  148         "<p><a href=\"%s?room=%s&token=%s&cmd=confirm\">"
  149         "%s?room=%s&token=%s&cmd=confirm</a></p>"
  150         "<p>If this request has been submitted in error and you still "
  151         "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
  152         "and you will remain subscribed.</p>"
  153         "</body></html>\n"
  154         "\n"
  155         "--__ctdlmultipart__--\n"
  156         ,
  157         emailaddr, roomname,
  158         url, urlroom, confirmation_token,
  159         roomname
  160         ,
  161         emailaddr, roomname,
  162         url, urlroom, confirmation_token,
  163         url, urlroom, confirmation_token,
  164         roomname
  165     );
  166 
  167     quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
  168 }
  169 
  170 
  171 /*
  172  * "Subscribe" and "Unsubscribe" operations are so similar that they share a function.
  173  * The actual subscription doesn't take place here -- we just send out the confirmation request
  174  * and record the address and confirmation token.
  175  */
  176 void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url) {
  177 
  178     int i;
  179     char buf[1024];
  180     char confirmation_token[40];
  181 
  182     // Update this room's netconfig with the updated lastsent
  183     begin_critical_section(S_NETCONFIGS);
  184         char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
  185         if (!oldnetconfig) {
  186         oldnetconfig = strdup("");
  187     }
  188 
  189     // The new netconfig begins with an empty buffer...
  190     char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
  191     newnetconfig[0] = 0;
  192 
  193     // And then we...
  194     int is_already_subscribed = 0;
  195     int config_lines = num_tokens(oldnetconfig, '\n');
  196     for (i=0; i<config_lines; ++i) {
  197         extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
  198         int keep_this_line =1;                      // set to nonzero if we are discarding a line
  199 
  200         if (IsEmptyStr(buf)) {
  201             keep_this_line = 0;
  202         }
  203 
  204         char buf_directive[1024];
  205         char buf_email[1024];
  206         extract_token(buf_directive, buf, 0, '|', sizeof buf_directive);
  207         extract_token(buf_email, buf, 1, '|', sizeof buf_email);
  208 
  209         if (    ( (!strcasecmp(buf_directive, "listrecp")) || (!strcasecmp(buf_directive, "digestrecp")) )
  210             && (!strcasecmp(buf_email, emailaddr)) 
  211         ) {
  212             is_already_subscribed = 1;
  213         }
  214 
  215         if ( (!strcasecmp(buf_directive, "subpending")) || (!strcasecmp(buf_directive, "unsubpending")) ) {
  216             time_t pendingtime = extract_long(buf, 3);
  217             if ((time(NULL) - pendingtime) > 259200) {
  218                 syslog(LOG_DEBUG, "%s %s is %ld seconds old - deleting it", buf_email, buf_directive, time(NULL) - pendingtime);
  219                 keep_this_line = 0;
  220             }
  221         }
  222         
  223         if (keep_this_line) {
  224             sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
  225         }
  226     }
  227 
  228     // Do we need to send out a confirmation email?
  229     if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
  230         generate_uuid(confirmation_token);
  231         sprintf(&newnetconfig[strlen(newnetconfig)], "subpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
  232         send_subscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token);
  233     }
  234     if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
  235         generate_uuid(confirmation_token);
  236         sprintf(&newnetconfig[strlen(newnetconfig)], "unsubpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
  237         send_unsubscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token);
  238     }
  239 
  240     // Write the new netconfig back to disk
  241     SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
  242     end_critical_section(S_NETCONFIGS);
  243     free(newnetconfig);         // this was the new netconfig, free it because we're done with it
  244     free(oldnetconfig);         // this was the old netconfig, free it even if we didn't do anything
  245 
  246     // Tell the client what happened.
  247     if ((action == SUBSCRIBE) && (is_already_subscribed)) {
  248         cprintf("%d This email address is already subscribed.\n", ERROR + ALREADY_EXISTS);
  249     }
  250     else if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
  251         cprintf("%d Subscription was requested, and a confirmation email was sent.\n", CIT_OK);
  252     }
  253     else if ((action == UNSUBSCRIBE) && (!is_already_subscribed)) {
  254         cprintf("%d This email address is not subscribed.\n", ERROR + NO_SUCH_USER);
  255     }
  256     else if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
  257         cprintf("%d Unsubscription was requested, and a confirmation email was sent.\n", CIT_OK);
  258     }
  259     else {
  260         cprintf("%d Nothing happens.\n", ERROR);
  261     }
  262 }
  263 
  264 
  265 /*
  266  * Confirm a list subscription or unsubscription
  267  */
  268 void do_confirm(char *token) {
  269     int yes_subscribe = 0;              // Set to 1 if the confirmation to subscribe is validated.
  270     int yes_unsubscribe = 0;            // Set to 1 if the confirmation to unsubscribe is validated.
  271     int i;
  272     char buf[1024];
  273     int config_lines = 0;
  274     char pending_directive[128];
  275     char pending_email[256];
  276     char pending_token[128];
  277 
  278     // We will have to do this in two passes.  The first pass checks to see if we have a confirmation request matching the token.
  279         char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
  280         if (!oldnetconfig) {
  281         cprintf("%d There are no pending requests.\n", ERROR + NO_SUCH_USER);
  282         return;
  283     }
  284 
  285     config_lines = num_tokens(oldnetconfig, '\n');
  286     for (i=0; i<config_lines; ++i) {
  287         extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
  288         extract_token(pending_directive, buf, 0, '|', sizeof pending_directive);
  289         extract_token(pending_email, buf, 1, '|', sizeof pending_email);
  290         extract_token(pending_token, buf, 2, '|', sizeof pending_token);
  291 
  292         if (!strcasecmp(pending_token, token)) {
  293             if (!strcasecmp(pending_directive, "subpending")) {
  294                 yes_subscribe = 1;
  295             }
  296             else if (!strcasecmp(pending_directive, "unsubpending")) {
  297                 yes_unsubscribe = 1;
  298             }
  299         }
  300     }
  301     free(oldnetconfig);
  302 
  303     // We didn't find a pending subscribe or unsubscribe request with the supplied token.
  304     if ((!yes_subscribe) && (!yes_unsubscribe)) {
  305         cprintf("%d The request you are trying to confirm was not found.\n", ERROR + NO_SUCH_USER);
  306         return;
  307     }
  308 
  309     // The second pass performs the now confirmed operation.
  310     // We will have to do this in two passes.  The first pass checks to see if we have a confirmation request matching the token.
  311         oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
  312         if (!oldnetconfig) {
  313         oldnetconfig = strdup("");
  314     }
  315 
  316     // The new netconfig begins with an empty buffer...
  317     begin_critical_section(S_NETCONFIGS);
  318     char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
  319     newnetconfig[0] = 0;
  320 
  321     config_lines = num_tokens(oldnetconfig, '\n');
  322     for (i=0; i<config_lines; ++i) {
  323         char buf_email[256];
  324         extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
  325         extract_token(buf_email, buf, 1, '|', sizeof pending_email);
  326         if (strcasecmp(buf_email, pending_email)) {
  327             sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);  // only keep lines that do not reference this subscriber
  328         }
  329     }
  330 
  331     // We have now removed all lines containing the subscriber's email address.  This deletes any pending requests.
  332     // If this was an unsubscribe operation, they're now gone from the list.
  333     // But if this was a subscribe operation, we now need to add them.
  334     if (yes_subscribe) {
  335         sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", pending_email);
  336     }
  337 
  338     // FIXME write it back to disk
  339     SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
  340     end_critical_section(S_NETCONFIGS);
  341     free(oldnetconfig);
  342     free(newnetconfig);
  343     cprintf("%d The pending request was confirmed.\n", CIT_OK);
  344 }
  345 
  346 
  347 /* 
  348  * process subscribe/unsubscribe requests and confirmations
  349  */
  350 void cmd_lsub(char *cmdbuf) {
  351     char cmd[20];
  352     char roomname[ROOMNAMELEN];
  353     char emailaddr[1024];
  354     char options[256];
  355     char url[1024];
  356     char token[128];
  357 
  358     extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);             // token 0 is the sub-command being sent
  359     extract_token(roomname, cmdbuf, 1, '|', sizeof roomname);       // token 1 is always a room name
  360 
  361     // First confirm that the caller is referencing a room that actually exists.
  362     if (CtdlGetRoom(&CC->room, roomname) != 0) {
  363         cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
  364         return;
  365     }
  366 
  367     if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
  368         cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
  369         return;
  370     }
  371 
  372     // Room confirmed, now parse the command.
  373 
  374     if (!strcasecmp(cmd, "subscribe")) {
  375         extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
  376         extract_token(url, cmdbuf, 3, '|', sizeof url);         // token 3 is the URL at which we subscribed
  377         do_subscribe_or_unsubscribe(SUBSCRIBE, emailaddr, url);
  378     }
  379 
  380     else if (!strcasecmp(cmd, "unsubscribe")) {
  381         extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
  382         extract_token(url, cmdbuf, 3, '|', sizeof url);         // token 3 is the URL at which we subscribed
  383         do_subscribe_or_unsubscribe(UNSUBSCRIBE, emailaddr, url);
  384     }
  385 
  386     else if (!strcasecmp(cmd, "confirm")) {
  387         extract_token(token, cmdbuf, 2, '|', sizeof token);     // token 2 is the confirmation token
  388         do_confirm(token);
  389     }
  390 
  391     else {                                  // sorry man, I can't deal with that
  392         cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
  393     }
  394 }
  395 
  396 
  397 /*
  398  * Module entry point
  399  */
  400 CTDL_MODULE_INIT(listsub)
  401 {
  402     if (!threading)
  403     {
  404         CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
  405     }
  406     
  407     /* return our module name for the log */
  408     return "listsub";
  409 }