Moved to new module init structure.
[citadel.git] / citadel / serv_listsub.c
1 /*
2  * $Id$
3  *
4  * This module handles self-service subscription/unsubscription to mail lists.
5  *
6  * Copyright (C) 2002-2005 by Art Cancro and others.
7  * This code is released under the terms of the GNU General Public License.
8  *
9  */
10
11 #include "sysdep.h"
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stdio.h>
15 #include <fcntl.h>
16 #include <ctype.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <sys/wait.h>
34 #include <string.h>
35 #include <limits.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "citserver.h"
39 #include "support.h"
40 #include "config.h"
41 #include "room_ops.h"
42 #include "user_ops.h"
43 #include "policy.h"
44 #include "database.h"
45 #include "msgbase.h"
46 #include "tools.h"
47 #include "internet_addressing.h"
48 #include "serv_network.h"
49 #include "clientsocket.h"
50 #include "file_ops.h"
51
52 #ifndef HAVE_SNPRINTF
53 #include "snprintf.h"
54 #endif
55
56
57
58 #include "ctdl_module.h"
59
60
61 /*
62  * Generate a randomizationalisticized token to use for authentication of
63  * a subscribe or unsubscribe request.
64  */
65 void listsub_generate_token(char *buf) {
66         char sourcebuf[SIZ];
67         static int seq = 0;
68
69         /* Theo, please sit down and shut up.  This key doesn't have to be
70          * tinfoil-hat secure, it just needs to be reasonably unguessable
71          * and unique.
72          */
73         sprintf(sourcebuf, "%lx",
74                 (long) (++seq + getpid() + time(NULL))
75         );
76
77         /* Convert it to base64 so it looks cool */     
78         CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf));
79 }
80
81
82 /*
83  * Enter a subscription request
84  */
85 void do_subscribe(char *room, char *email, char *subtype, char *webpage) {
86         struct ctdlroom qrbuf;
87         FILE *ncfp;
88         char filename[256];
89         char token[256];
90         char confirmation_request[2048];
91         char buf[512];
92         char urlroom[ROOMNAMELEN];
93         char scancmd[64];
94         char scanemail[256];
95         int found_sub = 0;
96
97         if (getroom(&qrbuf, room) != 0) {
98                 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, room);
99                 return;
100         }
101
102         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
103                 cprintf("%d '%s' "
104                         "does not accept subscribe/unsubscribe requests.\n",
105                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
106                 return;
107         }
108
109         listsub_generate_token(token);
110
111         assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
112
113         /* 
114          * Make sure the requested address isn't already subscribed
115          */
116         begin_critical_section(S_NETCONFIGS);
117         ncfp = fopen(filename, "r");
118         if (ncfp != NULL) {
119                 while (fgets(buf, sizeof buf, ncfp) != NULL) {
120                         buf[strlen(buf)-1] = 0;
121                         extract_token(scancmd, buf, 0, '|', sizeof scancmd);
122                         extract_token(scanemail, buf, 1, '|', sizeof scanemail);
123                         if ((!strcasecmp(scancmd, "listrecp"))
124                            || (!strcasecmp(scancmd, "digestrecp"))) {
125                                 if (!strcasecmp(scanemail, email)) {
126                                         ++found_sub;
127                                 }
128                         }
129                 }
130                 fclose(ncfp);
131         }
132         end_critical_section(S_NETCONFIGS);
133
134         if (found_sub != 0) {
135                 cprintf("%d '%s' is already subscribed to '%s'.\n",
136                         ERROR + ALREADY_EXISTS,
137                         email, qrbuf.QRname);
138                 return;
139         }
140
141         /*
142          * Now add it to the file
143          */     
144         begin_critical_section(S_NETCONFIGS);
145         ncfp = fopen(filename, "a");
146         if (ncfp != NULL) {
147                 fprintf(ncfp, "subpending|%s|%s|%s|%ld|%s\n",
148                         email,
149                         subtype,
150                         token,
151                         time(NULL),
152                         webpage
153                 );
154                 fclose(ncfp);
155         }
156         end_critical_section(S_NETCONFIGS);
157
158         /* Generate and send the confirmation request */
159
160         urlesc(urlroom, qrbuf.QRname);
161
162         snprintf(confirmation_request, sizeof confirmation_request,
163
164                 "MIME-Version: 1.0\n"
165                 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
166                 "\n"
167                 "This is a multipart message in MIME format.\n"
168                 "\n"
169                 "--__ctdlmultipart__\n"
170                 "Content-type: text/plain\n"
171                 "\n"
172                 "Someone (probably you) has submitted a request to subscribe\n"
173                 "<%s> to the '%s' mailing list.\n"
174                 "\n"
175                 "Please go here to confirm this request:\n"
176                 "  %s?room=%s&token=%s&cmd=confirm  \n"
177                 "\n"
178                 "If this request has been submitted in error and you do not\n"
179                 "wish to receive the '%s' mailing list, simply do nothing,\n"
180                 "and you will not receive any further mailings.\n"
181                 "\n"
182                 "--__ctdlmultipart__\n"
183                 "Content-type: text/html\n"
184                 "\n"
185                 "<HTML><BODY>\n"
186                 "Someone (probably you) has submitted a request to subscribe\n"
187                 "&lt;%s&gt; to the <B>%s</B> mailing list.<BR><BR>\n"
188                 "Please click here to confirm this request:<BR>\n"
189                 "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
190                 "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
191                 "If this request has been submitted in error and you do not\n"
192                 "wish to receive the '%s' mailing list, simply do nothing,\n"
193                 "and you will not receive any further mailings.\n"
194                 "</BODY></HTML>\n"
195                 "\n"
196                 "--__ctdlmultipart__--\n",
197
198                 email, qrbuf.QRname,
199                 webpage, urlroom, token,
200                 qrbuf.QRname,
201
202                 email, qrbuf.QRname,
203                 webpage, urlroom, token,
204                 webpage, urlroom, token,
205                 qrbuf.QRname
206         );
207
208         quickie_message(        /* This delivers the message */
209                 "Citadel",
210                 NULL,
211                 email,
212                 NULL,
213                 confirmation_request,
214                 FMT_RFC822,
215                 "Please confirm your list subscription"
216         );
217
218         cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
219 }
220
221
222 /*
223  * Enter an unsubscription request
224  */
225 void do_unsubscribe(char *room, char *email, char *webpage) {
226         struct ctdlroom qrbuf;
227         FILE *ncfp;
228         char filename[256];
229         char token[256];
230         char buf[512];
231         char confirmation_request[2048];
232         char urlroom[ROOMNAMELEN];
233         char scancmd[256];
234         char scanemail[256];
235         int found_sub = 0;
236
237         if (getroom(&qrbuf, room) != 0) {
238                 cprintf("%d There is no list called '%s'\n",
239                         ERROR + ROOM_NOT_FOUND, room);
240                 return;
241         }
242
243         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
244                 cprintf("%d '%s' "
245                         "does not accept subscribe/unsubscribe requests.\n",
246                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
247                 return;
248         }
249
250         listsub_generate_token(token);
251
252         assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
253
254         /* 
255          * Make sure there's actually a subscription there to remove
256          */
257         begin_critical_section(S_NETCONFIGS);
258         ncfp = fopen(filename, "r");
259         if (ncfp != NULL) {
260                 while (fgets(buf, sizeof buf, ncfp) != NULL) {
261                         buf[strlen(buf)-1] = 0;
262                         extract_token(scancmd, buf, 0, '|', sizeof scancmd);
263                         extract_token(scanemail, buf, 1, '|', sizeof scanemail);
264                         if ((!strcasecmp(scancmd, "listrecp"))
265                            || (!strcasecmp(scancmd, "digestrecp"))) {
266                                 if (!strcasecmp(scanemail, email)) {
267                                         ++found_sub;
268                                 }
269                         }
270                 }
271                 fclose(ncfp);
272         }
273         end_critical_section(S_NETCONFIGS);
274
275         if (found_sub == 0) {
276                 cprintf("%d '%s' is not subscribed to '%s'.\n",
277                         ERROR + NO_SUCH_USER,
278                         email, qrbuf.QRname);
279                 return;
280         }
281         
282         /* 
283          * Ok, now enter the unsubscribe-pending entry.
284          */
285         begin_critical_section(S_NETCONFIGS);
286         ncfp = fopen(filename, "a");
287         if (ncfp != NULL) {
288                 fprintf(ncfp, "unsubpending|%s|%s|%ld|%s\n",
289                         email,
290                         token,
291                         time(NULL),
292                         webpage
293                 );
294                 fclose(ncfp);
295         }
296         end_critical_section(S_NETCONFIGS);
297
298         /* Generate and send the confirmation request */
299
300         urlesc(urlroom, qrbuf.QRname);
301
302         snprintf(confirmation_request, sizeof confirmation_request,
303
304                 "MIME-Version: 1.0\n"
305                 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
306                 "\n"
307                 "This is a multipart message in MIME format.\n"
308                 "\n"
309                 "--__ctdlmultipart__\n"
310                 "Content-type: text/plain\n"
311                 "\n"
312                 "Someone (probably you) has submitted a request to unsubscribe\n"
313                 "<%s> from the '%s' mailing list.\n"
314                 "\n"
315                 "Please go here to confirm this request:\n"
316                 "  %s?room=%s&token=%s&cmd=confirm  \n"
317                 "\n"
318                 "If this request has been submitted in error and you do not\n"
319                 "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
320                 "and the request will not be processed.\n"
321                 "\n"
322                 "--__ctdlmultipart__\n"
323                 "Content-type: text/html\n"
324                 "\n"
325                 "<HTML><BODY>\n"
326                 "Someone (probably you) has submitted a request to unsubscribe\n"
327                 "&lt;%s&gt; from the <B>%s</B> mailing list.<BR><BR>\n"
328                 "Please click here to confirm this request:<BR>\n"
329                 "<A HREF=\"%s?room=%s&token=%s&cmd=confirm\">"
330                 "%s?room=%s&token=%s&cmd=confirm</A><BR><BR>\n"
331                 "If this request has been submitted in error and you do not\n"
332                 "wish to unsubscribe from the '%s' mailing list, simply do nothing,\n"
333                 "and the request will not be processed.\n"
334                 "</BODY></HTML>\n"
335                 "\n"
336                 "--__ctdlmultipart__--\n",
337
338                 email, qrbuf.QRname,
339                 webpage, urlroom, token,
340                 qrbuf.QRname,
341
342                 email, qrbuf.QRname,
343                 webpage, urlroom, token,
344                 webpage, urlroom, token,
345                 qrbuf.QRname
346         );
347
348         quickie_message(        /* This delivers the message */
349                 "Citadel",
350                 NULL,
351                 email,
352                 NULL,
353                 confirmation_request,
354                 FMT_RFC822,
355                 "Please confirm your unsubscribe request"
356         );
357
358         cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
359 }
360
361
362 /*
363  * Confirm a subscribe/unsubscribe request.
364  */
365 void do_confirm(char *room, char *token) {
366         struct ctdlroom qrbuf;
367         FILE *ncfp;
368         char filename[256];
369         char line_token[256];
370         long line_offset;
371         int line_length;
372         char buf[512];
373         char cmd[256];
374         char email[256];
375         char subtype[128];
376         int success = 0;
377         char address_to_unsubscribe[256];
378         char scancmd[256];
379         char scanemail[256];
380         char *holdbuf = NULL;
381         int linelen = 0;
382         int buflen = 0;
383
384         strcpy(address_to_unsubscribe, "");
385
386         if (getroom(&qrbuf, room) != 0) {
387                 cprintf("%d There is no list called '%s'\n",
388                         ERROR + ROOM_NOT_FOUND, room);
389                 return;
390         }
391
392         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
393                 cprintf("%d '%s' "
394                         "does not accept subscribe/unsubscribe requests.\n",
395                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
396                 return;
397         }
398
399         /*
400          * Now start scanning this room's netconfig file for the
401          * specified token.
402          */
403         assoc_file_name(filename, sizeof filename, &qrbuf, ctdl_netcfg_dir);
404         begin_critical_section(S_NETCONFIGS);
405         ncfp = fopen(filename, "r+");
406         if (ncfp != NULL) {
407                 while (line_offset = ftell(ncfp),
408                       (fgets(buf, sizeof buf, ncfp) != NULL) ) {
409                         buf[strlen(buf)-1] = 0;
410                         line_length = strlen(buf);
411                         extract_token(cmd, buf, 0, '|', sizeof cmd);
412                         if (!strcasecmp(cmd, "subpending")) {
413                                 extract_token(email, buf, 1, '|', sizeof email);
414                                 extract_token(subtype, buf, 2, '|', sizeof subtype);
415                                 extract_token(line_token, buf, 3, '|', sizeof line_token);
416                                 if (!strcasecmp(token, line_token)) {
417                                         if (!strcasecmp(subtype, "digest")) {
418                                                 safestrncpy(buf, "digestrecp|", sizeof buf);
419                                         }
420                                         else {
421                                                 safestrncpy(buf, "listrecp|", sizeof buf);
422                                         }
423                                         strcat(buf, email);
424                                         strcat(buf, "|");
425                                         /* SLEAZY HACK: pad the line out so
426                                          * it's the same length as the line
427                                          * we're replacing.
428                                          */
429                                         while (strlen(buf) < line_length) {
430                                                 strcat(buf, " ");
431                                         }
432                                         fseek(ncfp, line_offset, SEEK_SET);
433                                         fprintf(ncfp, "%s\n", buf);
434                                         ++success;
435                                 }
436                         }
437                         if (!strcasecmp(cmd, "unsubpending")) {
438                                 extract_token(line_token, buf, 2, '|', sizeof line_token);
439                                 if (!strcasecmp(token, line_token)) {
440                                         extract_token(address_to_unsubscribe, buf, 1, '|',
441                                                 sizeof address_to_unsubscribe);
442                                 }
443                         }
444                 }
445                 fclose(ncfp);
446         }
447         end_critical_section(S_NETCONFIGS);
448
449         /*
450          * If "address_to_unsubscribe" contains something, then we have to
451          * make another pass at the file, stripping out lines referring to
452          * that address.
453          */
454         if (strlen(address_to_unsubscribe) > 0) {
455                 holdbuf = malloc(SIZ);
456                 begin_critical_section(S_NETCONFIGS);
457                 ncfp = fopen(filename, "r+");
458                 if (ncfp != NULL) {
459                         while (line_offset = ftell(ncfp),
460                               (fgets(buf, sizeof buf, ncfp) != NULL) ) {
461                                 buf[strlen(buf)-1]=0;
462                                 extract_token(scancmd, buf, 0, '|', sizeof scancmd);
463                                 extract_token(scanemail, buf, 1, '|', sizeof scanemail);
464                                 if ( (!strcasecmp(scancmd, "listrecp"))
465                                    && (!strcasecmp(scanemail,
466                                                 address_to_unsubscribe)) ) {
467                                         ++success;
468                                 }
469                                 else if ( (!strcasecmp(scancmd, "digestrecp"))
470                                    && (!strcasecmp(scanemail,
471                                                 address_to_unsubscribe)) ) {
472                                         ++success;
473                                 }
474                                 else if ( (!strcasecmp(scancmd, "subpending"))
475                                    && (!strcasecmp(scanemail,
476                                                 address_to_unsubscribe)) ) {
477                                         ++success;
478                                 }
479                                 else if ( (!strcasecmp(scancmd, "unsubpending"))
480                                    && (!strcasecmp(scanemail,
481                                                 address_to_unsubscribe)) ) {
482                                         ++success;
483                                 }
484                                 else {  /* Not relevant, so *keep* it! */
485                                         linelen = strlen(buf);
486                                         holdbuf = realloc(holdbuf,
487                                                 (buflen + linelen + 2) );
488                                         strcpy(&holdbuf[buflen], buf);
489                                         buflen += linelen;
490                                         strcpy(&holdbuf[buflen], "\n");
491                                         buflen += 1;
492                                 }
493                         }
494                         fclose(ncfp);
495                 }
496                 ncfp = fopen(filename, "w");
497                 if (ncfp != NULL) {
498                         fwrite(holdbuf, buflen+1, 1, ncfp);
499                         fclose(ncfp);
500                 }
501                 end_critical_section(S_NETCONFIGS);
502                 free(holdbuf);
503         }
504
505         /*
506          * Did we do anything useful today?
507          */
508         if (success) {
509                 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
510                 lprintf(CTDL_NOTICE, "Mailing list: %s %ssubscribed to %s with token %s\n", email, (strlen(address_to_unsubscribe) > 0) ? "un" : "", room, token);
511         }
512         else {
513                 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
514         }
515
516 }
517
518
519
520 /* 
521  * process subscribe/unsubscribe requests and confirmations
522  */
523 void cmd_subs(char *cmdbuf) {
524
525         char opr[256];
526         char room[ROOMNAMELEN];
527         char email[256];
528         char subtype[256];
529         char token[256];
530         char webpage[256];
531
532         extract_token(opr, cmdbuf, 0, '|', sizeof opr);
533         if (!strcasecmp(opr, "subscribe")) {
534                 extract_token(subtype, cmdbuf, 3, '|', sizeof subtype);
535                 if ( (strcasecmp(subtype, "list"))
536                    && (strcasecmp(subtype, "digest")) ) {
537                         cprintf("%d Invalid subscription type '%s'\n",
538                                 ERROR + ILLEGAL_VALUE, subtype);
539                 }
540                 else {
541                         extract_token(room, cmdbuf, 1, '|', sizeof room);
542                         extract_token(email, cmdbuf, 2, '|', sizeof email);
543                         extract_token(webpage, cmdbuf, 4, '|', sizeof webpage);
544                         do_subscribe(room, email, subtype, webpage);
545                 }
546         }
547         else if (!strcasecmp(opr, "unsubscribe")) {
548                 extract_token(room, cmdbuf, 1, '|', sizeof room);
549                 extract_token(email, cmdbuf, 2, '|', sizeof email);
550                 extract_token(webpage, cmdbuf, 3, '|', sizeof webpage);
551                 do_unsubscribe(room, email, webpage);
552         }
553         else if (!strcasecmp(opr, "confirm")) {
554                 extract_token(room, cmdbuf, 1, '|', sizeof room);
555                 extract_token(token, cmdbuf, 2, '|', sizeof token);
556                 do_confirm(room, token);
557         }
558         else {
559                 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
560         }
561 }
562
563
564 /*
565  * Module entry point
566  */
567 CTDL_MODULE_INIT(listsub)
568 {
569         CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
570
571         /* return our Subversion id for the Log */
572         return "$Id$";
573 }