Back to index

citadel  8.12
serv_listsub.c
Go to the documentation of this file.
00001 /*
00002  * This module handles self-service subscription/unsubscription to mail lists.
00003  *
00004  * Copyright (c) 2002-2012 by the citadel.org team
00005  *
00006  *  This program is open source software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License version 3.
00008  *  
00009  *  
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  
00017  *  
00018  *  
00019  *
00020  */
00021 
00022 #include "sysdep.h"
00023 #include <stdlib.h>
00024 #include <unistd.h>
00025 #include <stdio.h>
00026 #include <fcntl.h>
00027 #include <ctype.h>
00028 #include <signal.h>
00029 #include <pwd.h>
00030 #include <errno.h>
00031 #include <sys/types.h>
00032 #include <dirent.h>
00033 #if TIME_WITH_SYS_TIME
00034 # include <sys/time.h>
00035 # include <time.h>
00036 #else
00037 # if HAVE_SYS_TIME_H
00038 #  include <sys/time.h>
00039 # else
00040 #  include <time.h>
00041 # endif
00042 #endif
00043 
00044 #include <sys/wait.h>
00045 #include <string.h>
00046 #include <limits.h>
00047 #include <libcitadel.h>
00048 #include "citadel.h"
00049 #include "server.h"
00050 #include "citserver.h"
00051 #include "support.h"
00052 #include "config.h"
00053 #include "user_ops.h"
00054 #include "database.h"
00055 #include "msgbase.h"
00056 #include "internet_addressing.h"
00057 #include "clientsocket.h"
00058 #include "file_ops.h"
00059 
00060 #ifndef HAVE_SNPRINTF
00061 #include "snprintf.h"
00062 #endif
00063 
00064 
00065 
00066 #include "ctdl_module.h"
00067 
00068 
00069 /*
00070  * Generate a randomizationalisticized token to use for authentication of
00071  * a subscribe or unsubscribe request.
00072  */
00073 void listsub_generate_token(char *buf) {
00074        char sourcebuf[SIZ];
00075        static int seq = 0;
00076 
00077        /* Theo, please sit down and shut up.  This key doesn't have to be
00078         * tinfoil-hat secure, it just needs to be reasonably unguessable
00079         * and unique.
00080         */
00081        sprintf(sourcebuf, "%lx",
00082               (long) (++seq + getpid() + time(NULL))
00083        );
00084 
00085        /* Convert it to base64 so it looks cool */      
00086        CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf), 0);
00087 }
00088 
00089 
00090 /*
00091  * Enter a subscription request
00092  */
00093 void do_subscribe(char *room, char *email, char *subtype, char *webpage) {
00094        struct ctdlroom qrbuf;
00095        FILE *ncfp;
00096        char filename[256];
00097        char token[256];
00098        char confirmation_request[2048];
00099        char buf[512];
00100        char urlroom[ROOMNAMELEN];
00101        char scancmd[64];
00102        char scanemail[256];
00103        int found_sub = 0;
00104 
00105        if (CtdlGetRoom(&qrbuf, room) != 0) {
00106               cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, room);
00107               return;
00108        }
00109 
00110        if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
00111               cprintf("%d '%s' "
00112                      "does not accept subscribe/unsubscribe requests.\n",
00113                      ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
00114               return;
00115        }
00116 
00117        listsub_generate_token(token);
00118 
00119        assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
00120 
00121        /* 
00122         * Make sure the requested address isn't already subscribed
00123         */
00124        begin_critical_section(S_NETCONFIGS);
00125        ncfp = fopen(filename, "r");
00126        if (ncfp != NULL) {
00127               while (fgets(buf, sizeof buf, ncfp) != NULL) {
00128                      buf[strlen(buf)-1] = 0;
00129                      extract_token(scancmd, buf, 0, '|', sizeof scancmd);
00130                      extract_token(scanemail, buf, 1, '|', sizeof scanemail);
00131                      if ((!strcasecmp(scancmd, "listrecp"))
00132                         || (!strcasecmp(scancmd, "digestrecp"))) {
00133                             if (!strcasecmp(scanemail, email)) {
00134                                    ++found_sub;
00135                             }
00136                      }
00137               }
00138               fclose(ncfp);
00139        }
00140        end_critical_section(S_NETCONFIGS);
00141 
00142        if (found_sub != 0) {
00143               cprintf("%d '%s' is already subscribed to '%s'.\n",
00144                      ERROR + ALREADY_EXISTS,
00145                      email, qrbuf.QRname);
00146               return;
00147        }
00148 
00149        /*
00150         * Now add it to the file
00151         */    
00152        begin_critical_section(S_NETCONFIGS);
00153        ncfp = fopen(filename, "a");
00154        if (ncfp != NULL) {
00155               fprintf(ncfp, "subpending|%s|%s|%s|%ld|%s\n",
00156                      email,
00157                      subtype,
00158                      token,
00159                      time(NULL),
00160                      webpage
00161               );
00162               fclose(ncfp);
00163        }
00164        end_critical_section(S_NETCONFIGS);
00165 
00166        /* Generate and send the confirmation request */
00167 
00168        urlesc(urlroom, ROOMNAMELEN, qrbuf.QRname);
00169 
00170        snprintf(confirmation_request, sizeof confirmation_request,
00171 
00172               "MIME-Version: 1.0\n"
00173               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
00174               "\n"
00175               "This is a multipart message in MIME format.\n"
00176               "\n"
00177               "--__ctdlmultipart__\n"
00178               "Content-type: text/plain\n"
00179               "\n"
00180               "Someone (probably you) has submitted a request to subscribe\n"
00181               "<%s> to the '%s' mailing list.\n"
00182               "\n"
00183               "Please go here to confirm this request:\n"
00184               "  %s?room=%s&token=%s&cmd=confirm  \n"
00185               "\n"
00186               "If this request has been submitted in error and you do not\n"
00187               "wish to receive the '%s' mailing list, simply do nothing,\n"
00188               "and you will not receive any further mailings.\n"
00189               "\n"
00190               "--__ctdlmultipart__\n"
00191               "Content-type: text/html\n"
00192               "\n"
00193               "<HTML><BODY>\n"
00194               "Someone (probably you) has submitted a request to subscribe\n"
00195               "&lt;%s&gt; to the <B>%s</B> mailing list.<BR><BR>\n"
00196               "Please click here to confirm this request:<BR>\n"
00197               "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
00198               "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
00199               "If this request has been submitted in error and you do not\n"
00200               "wish to receive the '%s' mailing list, simply do nothing,\n"
00201               "and you will not receive any further mailings.\n"
00202               "</BODY></HTML>\n"
00203               "\n"
00204               "--__ctdlmultipart__--\n",
00205 
00206               email, qrbuf.QRname,
00207               webpage, urlroom, token,
00208               qrbuf.QRname,
00209 
00210               email, qrbuf.QRname,
00211               webpage, urlroom, token,
00212               webpage, urlroom, token,
00213               qrbuf.QRname
00214        );
00215 
00216        quickie_message(     /* This delivers the message */
00217               "Citadel",
00218               NULL,
00219               email,
00220               NULL,
00221               confirmation_request,
00222               FMT_RFC822,
00223               "Please confirm your list subscription"
00224        );
00225 
00226        cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
00227 }
00228 
00229 
00230 /*
00231  * Enter an unsubscription request
00232  */
00233 void do_unsubscribe(char *room, char *email, char *webpage) {
00234        struct ctdlroom qrbuf;
00235        FILE *ncfp;
00236        char filename[256];
00237        char token[256];
00238        char buf[512];
00239        char confirmation_request[2048];
00240        char urlroom[ROOMNAMELEN];
00241        char scancmd[256];
00242        char scanemail[256];
00243        int found_sub = 0;
00244 
00245        if (CtdlGetRoom(&qrbuf, room) != 0) {
00246               cprintf("%d There is no list called '%s'\n",
00247                      ERROR + ROOM_NOT_FOUND, room);
00248               return;
00249        }
00250 
00251        if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
00252               cprintf("%d '%s' "
00253                      "does not accept subscribe/unsubscribe requests.\n",
00254                      ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
00255               return;
00256        }
00257 
00258        listsub_generate_token(token);
00259 
00260        assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
00261 
00262        /* 
00263         * Make sure there's actually a subscription there to remove
00264         */
00265        begin_critical_section(S_NETCONFIGS);
00266        ncfp = fopen(filename, "r");
00267        if (ncfp != NULL) {
00268               while (fgets(buf, sizeof buf, ncfp) != NULL) {
00269                      buf[strlen(buf)-1] = 0;
00270                      extract_token(scancmd, buf, 0, '|', sizeof scancmd);
00271                      extract_token(scanemail, buf, 1, '|', sizeof scanemail);
00272                      if ((!strcasecmp(scancmd, "listrecp"))
00273                         || (!strcasecmp(scancmd, "digestrecp"))) {
00274                             if (!strcasecmp(scanemail, email)) {
00275                                    ++found_sub;
00276                             }
00277                      }
00278               }
00279               fclose(ncfp);
00280        }
00281        end_critical_section(S_NETCONFIGS);
00282 
00283        if (found_sub == 0) {
00284               cprintf("%d '%s' is not subscribed to '%s'.\n",
00285                      ERROR + NO_SUCH_USER,
00286                      email, qrbuf.QRname);
00287               return;
00288        }
00289        
00290        /* 
00291         * Ok, now enter the unsubscribe-pending entry.
00292         */
00293        begin_critical_section(S_NETCONFIGS);
00294        ncfp = fopen(filename, "a");
00295        if (ncfp != NULL) {
00296               fprintf(ncfp, "unsubpending|%s|%s|%ld|%s\n",
00297                      email,
00298                      token,
00299                      time(NULL),
00300                      webpage
00301               );
00302               fclose(ncfp);
00303        }
00304        end_critical_section(S_NETCONFIGS);
00305 
00306        /* Generate and send the confirmation request */
00307 
00308        urlesc(urlroom, ROOMNAMELEN, qrbuf.QRname);
00309 
00310        snprintf(confirmation_request, sizeof confirmation_request,
00311 
00312               "MIME-Version: 1.0\n"
00313               "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
00314               "\n"
00315               "This is a multipart message in MIME format.\n"
00316               "\n"
00317               "--__ctdlmultipart__\n"
00318               "Content-type: text/plain\n"
00319               "\n"
00320               "Someone (probably you) has submitted a request to unsubscribe\n"
00321               "<%s> from the '%s' mailing list.\n"
00322               "\n"
00323               "Please go here to confirm this request:\n"
00324               "  %s?room=%s&token=%s&cmd=confirm  \n"
00325               "\n"
00326               "If this request has been submitted in error and you do not\n"
00327               "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
00328               "and the request will not be processed.\n"
00329               "\n"
00330               "--__ctdlmultipart__\n"
00331               "Content-type: text/html\n"
00332               "\n"
00333               "<HTML><BODY>\n"
00334               "Someone (probably you) has submitted a request to unsubscribe\n"
00335               "&lt;%s&gt; from the <B>%s</B> mailing list.<BR><BR>\n"
00336               "Please click here to confirm this request:<BR>\n"
00337               "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
00338               "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
00339               "If this request has been submitted in error and you do not\n"
00340               "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
00341               "and the request will not be processed.\n"
00342               "</BODY></HTML>\n"
00343               "\n"
00344               "--__ctdlmultipart__--\n",
00345 
00346               email, qrbuf.QRname,
00347               webpage, urlroom, token,
00348               qrbuf.QRname,
00349 
00350               email, qrbuf.QRname,
00351               webpage, urlroom, token,
00352               webpage, urlroom, token,
00353               qrbuf.QRname
00354        );
00355 
00356        quickie_message(     /* This delivers the message */
00357               "Citadel",
00358               NULL,
00359               email,
00360               NULL,
00361               confirmation_request,
00362               FMT_RFC822,
00363               "Please confirm your unsubscribe request"
00364        );
00365 
00366        cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
00367 }
00368 
00369 
00370 /*
00371  * Confirm a subscribe/unsubscribe request.
00372  */
00373 void do_confirm(char *room, char *token) {
00374        struct ctdlroom qrbuf;
00375        FILE *ncfp;
00376        char filename[256];
00377        char line_token[256];
00378        long line_offset;
00379        int line_length;
00380        char buf[512];
00381        char cmd[256];
00382        char email[256] = "";
00383        char subtype[128];
00384        int success = 0;
00385        char address_to_unsubscribe[256] = "";
00386        char scancmd[256];
00387        char scanemail[256];
00388        char *holdbuf = NULL;
00389        int linelen = 0;
00390        int buflen = 0;
00391 
00392        if (CtdlGetRoom(&qrbuf, room) != 0) {
00393               cprintf("%d There is no list called '%s'\n",
00394                      ERROR + ROOM_NOT_FOUND, room);
00395               return;
00396        }
00397 
00398        if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
00399               cprintf("%d '%s' "
00400                      "does not accept subscribe/unsubscribe requests.\n",
00401                      ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
00402               return;
00403        }
00404 
00405        /*
00406         * Now start scanning this room's netconfig file for the
00407         * specified token.
00408         */
00409        assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
00410        begin_critical_section(S_NETCONFIGS);
00411        ncfp = fopen(filename, "r+");
00412        if (ncfp != NULL) {
00413               while (line_offset = ftell(ncfp),
00414                     (fgets(buf, sizeof buf, ncfp) != NULL) ) {
00415                      buf[strlen(buf)-1] = 0;
00416                      line_length = strlen(buf);
00417                      extract_token(cmd, buf, 0, '|', sizeof cmd);
00418                      if (!strcasecmp(cmd, "subpending")) {
00419                             extract_token(email, buf, 1, '|', sizeof email);
00420                             extract_token(subtype, buf, 2, '|', sizeof subtype);
00421                             extract_token(line_token, buf, 3, '|', sizeof line_token);
00422                             if (!strcasecmp(token, line_token)) {
00423                                    if (!strcasecmp(subtype, "digest")) {
00424                                           safestrncpy(buf, "digestrecp|", sizeof buf);
00425                                    }
00426                                    else {
00427                                           safestrncpy(buf, "listrecp|", sizeof buf);
00428                                    }
00429                                    strcat(buf, email);
00430                                    strcat(buf, "|");
00431                                    /* SLEAZY HACK: pad the line out so
00432                                     * it's the same length as the line
00433                                     * we're replacing.
00434                                     */
00435                                    while (strlen(buf) < line_length) {
00436                                           strcat(buf, " ");
00437                                    }
00438                                    fseek(ncfp, line_offset, SEEK_SET);
00439                                    fprintf(ncfp, "%s\n", buf);
00440                                    ++success;
00441                             }
00442                      }
00443                      if (!strcasecmp(cmd, "unsubpending")) {
00444                             extract_token(line_token, buf, 2, '|', sizeof line_token);
00445                             if (!strcasecmp(token, line_token)) {
00446                                    extract_token(address_to_unsubscribe, buf, 1, '|',
00447                                           sizeof address_to_unsubscribe);
00448                             }
00449                      }
00450               }
00451               fclose(ncfp);
00452        }
00453        end_critical_section(S_NETCONFIGS);
00454 
00455        /*
00456         * If "address_to_unsubscribe" contains something, then we have to
00457         * make another pass at the file, stripping out lines referring to
00458         * that address.
00459         */
00460        if (!IsEmptyStr(address_to_unsubscribe)) {
00461               holdbuf = malloc(SIZ);
00462               begin_critical_section(S_NETCONFIGS);
00463               ncfp = fopen(filename, "r+");
00464               if (ncfp != NULL) {
00465                      while (line_offset = ftell(ncfp),
00466                            (fgets(buf, sizeof buf, ncfp) != NULL) ) {
00467                             buf[strlen(buf)-1]=0;
00468                             extract_token(scancmd, buf, 0, '|', sizeof scancmd);
00469                             extract_token(scanemail, buf, 1, '|', sizeof scanemail);
00470                             if ( (!strcasecmp(scancmd, "listrecp"))
00471                                && (!strcasecmp(scanemail,
00472                                           address_to_unsubscribe)) ) {
00473                                    ++success;
00474                             }
00475                             else if ( (!strcasecmp(scancmd, "digestrecp"))
00476                                && (!strcasecmp(scanemail,
00477                                           address_to_unsubscribe)) ) {
00478                                    ++success;
00479                             }
00480                             else if ( (!strcasecmp(scancmd, "subpending"))
00481                                && (!strcasecmp(scanemail,
00482                                           address_to_unsubscribe)) ) {
00483                                    ++success;
00484                             }
00485                             else if ( (!strcasecmp(scancmd, "unsubpending"))
00486                                && (!strcasecmp(scanemail,
00487                                           address_to_unsubscribe)) ) {
00488                                    ++success;
00489                             }
00490                             else { /* Not relevant, so *keep* it! */
00491                                    linelen = strlen(buf);
00492                                    holdbuf = realloc(holdbuf,
00493                                           (buflen + linelen + 2) );
00494                                    strcpy(&holdbuf[buflen], buf);
00495                                    buflen += linelen;
00496                                    strcpy(&holdbuf[buflen], "\n");
00497                                    buflen += 1;
00498                             }
00499                      }
00500                      fclose(ncfp);
00501               }
00502               ncfp = fopen(filename, "w");
00503               if (ncfp != NULL) {
00504                      fwrite(holdbuf, buflen+1, 1, ncfp);
00505                      fclose(ncfp);
00506               }
00507               end_critical_section(S_NETCONFIGS);
00508               free(holdbuf);
00509        }
00510 
00511        /*
00512         * Did we do anything useful today?
00513         */
00514        if (success) {
00515               cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
00516               syslog(LOG_NOTICE, 
00517                      "Mailing list: %s %ssubscribed to %s with token %s\n", 
00518                      email, 
00519                      (!IsEmptyStr(address_to_unsubscribe)) ? "un" : "", 
00520                      room, 
00521                      token);
00522        }
00523        else {
00524               cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
00525        }
00526 
00527 }
00528 
00529 
00530 
00531 /* 
00532  * process subscribe/unsubscribe requests and confirmations
00533  */
00534 void cmd_subs(char *cmdbuf) {
00535 
00536        char opr[256];
00537        char room[ROOMNAMELEN];
00538        char email[256];
00539        char subtype[256];
00540        char token[256];
00541        char webpage[256];
00542 
00543        extract_token(opr, cmdbuf, 0, '|', sizeof opr);
00544        if (!strcasecmp(opr, "subscribe")) {
00545               extract_token(subtype, cmdbuf, 3, '|', sizeof subtype);
00546               if ( (strcasecmp(subtype, "list"))
00547                  && (strcasecmp(subtype, "digest")) ) {
00548                      cprintf("%d Invalid subscription type '%s'\n",
00549                             ERROR + ILLEGAL_VALUE, subtype);
00550               }
00551               else {
00552                      extract_token(room, cmdbuf, 1, '|', sizeof room);
00553                      extract_token(email, cmdbuf, 2, '|', sizeof email);
00554                      extract_token(webpage, cmdbuf, 4, '|', sizeof webpage);
00555                      do_subscribe(room, email, subtype, webpage);
00556               }
00557        }
00558        else if (!strcasecmp(opr, "unsubscribe")) {
00559               extract_token(room, cmdbuf, 1, '|', sizeof room);
00560               extract_token(email, cmdbuf, 2, '|', sizeof email);
00561               extract_token(webpage, cmdbuf, 3, '|', sizeof webpage);
00562               do_unsubscribe(room, email, webpage);
00563        }
00564        else if (!strcasecmp(opr, "confirm")) {
00565               extract_token(room, cmdbuf, 1, '|', sizeof room);
00566               extract_token(token, cmdbuf, 2, '|', sizeof token);
00567               do_confirm(room, token);
00568        }
00569        else {
00570               cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
00571        }
00572 }
00573 
00574 
00575 /*
00576  * Module entry point
00577  */
00578 CTDL_MODULE_INIT(listsub)
00579 {
00580        if (!threading)
00581        {
00582               CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
00583        }
00584        
00585        /* return our module name for the log */
00586        return "listsub";
00587 }