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