Removed the comments about RMS being an asshole communist. I don't want anyone to...
[citadel.git] / citadel / modules / listsub / serv_listsub.c
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 }