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