889926ac48d250bd5717d7645c5d2500dfd4eaab
[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-2012 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *  
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  */
14
15 #include "sysdep.h"
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <fcntl.h>
20 #include <ctype.h>
21 #include <signal.h>
22 #include <pwd.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <dirent.h>
26 #if TIME_WITH_SYS_TIME
27 # include <sys/time.h>
28 # include <time.h>
29 #else
30 # if HAVE_SYS_TIME_H
31 #  include <sys/time.h>
32 # else
33 #  include <time.h>
34 # endif
35 #endif
36
37 #include <sys/wait.h>
38 #include <string.h>
39 #include <limits.h>
40 #include <libcitadel.h>
41 #include "citadel.h"
42 #include "server.h"
43 #include "citserver.h"
44 #include "support.h"
45 #include "config.h"
46 #include "user_ops.h"
47 #include "database.h"
48 #include "msgbase.h"
49 #include "internet_addressing.h"
50 #include "clientsocket.h"
51 #include "ctdl_module.h"
52
53 /*
54  * Generate a randomizationalisticized token to use for authentication of
55  * a subscribe or unsubscribe request.
56  */
57 void listsub_generate_token(char *buf) {
58         char sourcebuf[SIZ];
59         static int seq = 0;
60
61         /* Theo, please sit down and shut up.  This key doesn't have to be
62          * tinfoil-hat secure, it just needs to be reasonably unguessable
63          * and unique.
64          */
65         sprintf(sourcebuf, "%lx",
66                 (long) (++seq + getpid() + time(NULL))
67         );
68
69         /* Convert it to base64 so it looks cool */     
70         CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf), 0);
71 }
72
73 const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp};
74
75 int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email)
76 {
77         RoomNetCfgLine *Line;
78         int found_sub = 0;
79         int i;
80
81         for (i = 0; i < 2; i++)
82         {
83                 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
84                 while (Line != NULL)
85                 {
86                         if (!strcmp(ChrPtr(email),
87                                     ChrPtr(Line->Value[0])))
88                         {
89                                 ++found_sub;
90                                 break;                                  
91                         }
92                         Line = Line->next;
93                 }
94         }
95         return found_sub;
96 }
97
98 /*
99  * Enter a subscription request
100  */
101 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
102         struct ctdlroom qrbuf;
103         char token[256];
104         char *pcf_req;
105         StrBuf *cf_req;
106         StrBuf *UrlRoom;
107         int found_sub = 0;
108         const char *RoomMailAddress;
109         OneRoomNetCfg *OneRNCfg;
110         RoomNetCfgLine *Line;
111         const char *EmailSender = NULL;
112         long RoomMailAddressLen;
113
114         if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
115                 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
116                 return;
117         }
118
119         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
120                 cprintf("%d '%s' "
121                         "does not accept subscribe/unsubscribe requests.\n",
122                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
123                 return;
124         }
125
126         /* 
127          * Make sure the requested address isn't already subscribed
128          */
129         begin_critical_section(S_NETCONFIGS);
130
131         RoomMailAddress = qrbuf.QRname;
132         OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
133         if (OneRNCfg!=NULL) {
134                 found_sub = CountThisSubscriber(OneRNCfg, *email);
135                 if (StrLength(OneRNCfg->Sender) > 0) {
136                        EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
137                 }
138         }
139
140         if (found_sub != 0) {
141                 cprintf("%d '%s' is already subscribed to '%s'.\n",
142                         ERROR + ALREADY_EXISTS,
143                         ChrPtr(*email),
144                         RoomMailAddress);
145
146                 end_critical_section(S_NETCONFIGS);
147                 return;
148         }
149
150         /*
151          * Now add it to the config
152          */     
153         
154         RoomMailAddressLen = strlen(RoomMailAddress);
155         listsub_generate_token(token);
156         Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
157         memset(Line, 0, sizeof(RoomNetCfgLine));
158
159         Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
160         
161         Line->Value[0] = NewStrBufDup(*email);
162         Line->Value[1] = *subtype; *subtype = NULL;
163         Line->Value[2] = NewStrBufPlain(token, -1);
164         Line->Value[3] = NewStrBufPlain(NULL, 10);
165         StrBufPrintf(Line->Value[3], "%ld", time(NULL));
166         Line->Value[4] = *webpage; *webpage = NULL;
167         Line->nValues = 5;
168
169         AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
170
171         /* Generate and send the confirmation request */
172         UrlRoom = NewStrBuf();
173         StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
174
175         cf_req = NewStrBufPlain(NULL, 2048);
176         StrBufAppendBufPlain(
177                 cf_req,
178                 HKEY("MIME-Version: 1.0\n"
179                      "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
180                      "\n"
181                      "This is a multipart message in MIME format.\n"
182                      "\n"
183                      "--__ctdlmultipart__\n"
184                      "Content-type: text/plain\n"
185                      "\n"
186                      "Someone (probably you) has submitted a request to subscribe\n"
187                      "<"), 0);
188         StrBufAppendBuf(cf_req, Line->Value[0], 0);
189
190         StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
191         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
192
193         StrBufAppendBufPlain(
194                 cf_req,
195                 HKEY("' mailing list.\n"
196                      "\n"
197                      "Please go here to confirm this request:\n"
198                      "  "), 0);
199         StrBufAppendBuf(cf_req, Line->Value[4], 0);
200
201         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
202         StrBufAppendBuf(cf_req, UrlRoom, 0);
203
204         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
205         StrBufAppendBuf(cf_req, Line->Value[2], 0);
206
207         StrBufAppendBufPlain(
208                 cf_req,
209                 HKEY("&cmd=confirm  \n"
210                      "\n"
211                      "If this request has been submitted in error and you do not\n"
212                      "wish to receive the '"), 0);
213         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
214
215         StrBufAppendBufPlain(
216                 cf_req,
217                 HKEY("' mailing list, simply do nothing,\n"
218                      "and you will not receive any further mailings.\n"
219                      "\n"
220                      "--__ctdlmultipart__\n"
221                      "Content-type: text/html\n"
222                      "\n"
223                      "<HTML><BODY>\n"
224                      "Someone (probably you) has submitted a request to subscribe\n"
225                      "&lt;"), 0);
226         StrBufAppendBuf(cf_req, Line->Value[0], 0);
227
228         StrBufAppendBufPlain(cf_req, HKEY( "&gt; to the <B>"), 0);
229
230         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
231
232         StrBufAppendBufPlain(
233                 cf_req,
234                 HKEY("'</B> mailing list.<BR><BR>\n"
235                      "Please click here to confirm this request:<BR>\n"
236                      "<A HREF=\""), 0);
237         StrBufAppendBuf(cf_req, Line->Value[4], 0);
238
239         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
240         StrBufAppendBuf(cf_req, UrlRoom, 0);
241
242         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
243         StrBufAppendBuf(cf_req, Line->Value[2], 0);
244
245         StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
246         StrBufAppendBuf(cf_req, Line->Value[4], 0);
247
248         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
249         StrBufAppendBuf(cf_req, UrlRoom, 0);
250
251         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
252         StrBufAppendBuf(cf_req, Line->Value[2], 0);
253
254         StrBufAppendBufPlain(
255                 cf_req,
256                 HKEY("&cmd=confirm</A><BR><BR>\n"
257                      "If this request has been submitted in error and you do not\n"
258                      "wish to receive the '"), 0);
259         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
260         
261         StrBufAppendBufPlain(
262                 cf_req,
263                 HKEY("' mailing list, simply do nothing,\n"
264                      "and you will not receive any further mailings.\n"
265                      "</BODY></HTML>\n"
266                      "\n"
267                      "--__ctdlmultipart__--\n"), 0);
268
269         end_critical_section(S_NETCONFIGS);
270
271         pcf_req = SmashStrBuf(&cf_req);
272         quickie_message(        /* This delivers the message */
273                 "Citadel",
274                 EmailSender,
275                 ChrPtr(*email),
276                 NULL,
277                 pcf_req,
278                 FMT_RFC822,
279                 "Please confirm your list subscription"
280                 );
281         free(pcf_req);
282         cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
283
284         FreeStrBuf(&UrlRoom);
285 }
286
287
288 /*
289  * Enter an unsubscription request
290  */
291 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
292         struct ctdlroom qrbuf;
293         const char *EmailSender = NULL;
294         char token[256];
295         char *pcf_req;
296         StrBuf *cf_req;
297         StrBuf *UrlRoom;
298         int found_sub = 0;
299         const char *RoomMailAddress;
300         OneRoomNetCfg *OneRNCfg;
301         RoomNetCfgLine *Line;
302         long RoomMailAddressLen;
303
304         if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
305                 cprintf("%d There is no list called '%s'\n",
306                         ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
307                 return;
308         }
309
310         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
311                 cprintf("%d '%s' "
312                         "does not accept subscribe/unsubscribe requests.\n",
313                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
314                 return;
315         }
316
317         listsub_generate_token(token);
318
319         /* 
320          * Make sure there's actually a subscription there to remove
321          */
322         begin_critical_section(S_NETCONFIGS);
323         RoomMailAddress = qrbuf.QRname;
324         OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
325         if (OneRNCfg!=NULL) {
326                 found_sub = CountThisSubscriber(OneRNCfg, *email);
327                 if (StrLength(OneRNCfg->Sender) > 0)
328                         EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
329         }
330
331         if (found_sub == 0) {
332                 cprintf("%d '%s' is not subscribed to '%s'.\n",
333                         ERROR + NO_SUCH_USER,
334                         ChrPtr(*email),
335                         qrbuf.QRname);
336
337                 end_critical_section(S_NETCONFIGS);
338                 return;
339         }
340         
341         /* 
342          * Ok, now enter the unsubscribe-pending entry.
343          */
344         RoomMailAddressLen = strlen(RoomMailAddress);
345         listsub_generate_token(token);
346         Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
347         memset(Line, 0, sizeof(RoomNetCfgLine));
348
349         Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4);
350         
351         Line->Value[0] = NewStrBufDup(*email);
352         Line->Value[1] = NewStrBufPlain(token, -1);
353         Line->Value[2] = NewStrBufPlain(NULL, 10);
354         StrBufPrintf(Line->Value[2], "%ld", time(NULL));
355         Line->Value[3] = *webpage; *webpage = NULL;
356         Line->nValues = 4;
357
358         AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
359
360         /* Generate and send the confirmation request */
361         UrlRoom = NewStrBuf();
362         StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
363
364         cf_req = NewStrBufPlain(NULL, 2048);
365
366         StrBufAppendBufPlain(
367                 cf_req,
368                 HKEY("MIME-Version: 1.0\n"
369                      "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
370                      "\n"
371                      "This is a multipart message in MIME format.\n"
372                      "\n"
373                      "--__ctdlmultipart__\n"
374                      "Content-type: text/plain\n"
375                      "\n"
376                      "Someone (probably you) has submitted a request to unsubscribe\n"
377                      "<"), 0);
378         StrBufAppendBuf(cf_req, Line->Value[0], 0);
379
380
381         StrBufAppendBufPlain(
382                 cf_req,
383                 HKEY("> from the '"), 0);
384         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
385
386         StrBufAppendBufPlain(
387                 cf_req,
388                 HKEY("' mailing list.\n"
389                      "\n"
390                      "Please go here to confirm this request:\n  "), 0);
391         StrBufAppendBuf(cf_req, Line->Value[3], 0);
392         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
393         StrBufAppendBuf(cf_req, UrlRoom, 0);
394         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
395         StrBufAppendBuf(cf_req, Line->Value[1], 0);
396
397         StrBufAppendBufPlain(
398                 cf_req,
399                 HKEY("&cmd=confirm  \n"
400                      "\n"
401                      "If this request has been submitted in error and you do not\n"
402                      "wish to unsubscribe from the '"), 0);
403
404         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
405
406         StrBufAppendBufPlain(
407                 cf_req,
408                 HKEY("' mailing list, simply do nothing,\n"
409                      "and the request will not be processed.\n"
410                      "\n"
411                      "--__ctdlmultipart__\n"
412                      "Content-type: text/html\n"
413                      "\n"
414                      "<HTML><BODY>\n"
415                      "Someone (probably you) has submitted a request to unsubscribe\n"
416                      "&lt;"), 0);
417         StrBufAppendBuf(cf_req, Line->Value[0], 0);
418
419         StrBufAppendBufPlain(cf_req, HKEY("&gt; from the <B>"), 0);
420         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
421
422         StrBufAppendBufPlain(
423                 cf_req,
424                 HKEY("</B> mailing list.<BR><BR>\n"
425                      "Please click here to confirm this request:<BR>\n"
426                      "<A HREF=\""), 0);
427         StrBufAppendBuf(cf_req, Line->Value[3], 0);
428
429         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
430         StrBufAppendBuf(cf_req, UrlRoom, 0);
431
432         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
433         StrBufAppendBuf(cf_req, Line->Value[1], 0);
434
435         StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
436         StrBufAppendBuf(cf_req, Line->Value[3], 0);
437
438         StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
439         StrBufAppendBuf(cf_req, UrlRoom, 0);
440
441         StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
442         StrBufAppendBuf(cf_req, Line->Value[1], 0);
443
444
445         StrBufAppendBufPlain(
446                 cf_req,
447                 HKEY("&cmd=confirm</A><BR><BR>\n"
448                      "If this request has been submitted in error and you do not\n"
449                      "wish to unsubscribe from the '"), 0);
450         StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
451
452         StrBufAppendBufPlain(
453                 cf_req,
454                 HKEY("' mailing list, simply do nothing,\n"
455                      "and the request will not be processed.\n"
456                      "</BODY></HTML>\n"
457                      "\n"
458                      "--__ctdlmultipart__--\n"), 0);
459
460         end_critical_section(S_NETCONFIGS);
461
462         pcf_req = SmashStrBuf(&cf_req);
463         quickie_message(        /* This delivers the message */
464                 "Citadel",
465                 EmailSender,
466                 ChrPtr(*email),
467                 NULL,
468                 pcf_req,
469                 FMT_RFC822,
470                 "Please confirm your unsubscribe request"
471         );
472
473         free(pcf_req);
474         FreeStrBuf(&UrlRoom);
475         cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
476 }
477
478
479 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
480
481 /*
482  * Confirm a subscribe/unsubscribe request.
483  */
484 void do_confirm(StrBuf **room, StrBuf **token) {
485         struct ctdlroom qrbuf;
486         OneRoomNetCfg *OneRNCfg;
487         RoomNetCfgLine *Line;
488         RoomNetCfgLine *ConfirmLine = NULL;
489         RoomNetCfgLine *RemoveLine = NULL;
490         RoomNetCfgLine **PrevLine;
491         int success = 0;
492         RoomNetCfg ConfirmType;
493         const char *errmsg = "";
494         int i;
495         
496         if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
497                 cprintf("%d There is no list called '%s'\n",
498                         ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
499                 return;
500         }
501
502         if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
503                 cprintf("%d '%s' "
504                         "does not accept subscribe/unsubscribe requests.\n",
505                         ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
506                 return;
507         }
508
509
510         if (StrLength(*token) == 0) {
511                 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
512                 return;
513         }
514         /*
515          * Now start scanning this room's netconfig file for the
516          * specified token.
517          */
518         begin_critical_section(S_NETCONFIGS);
519         OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
520
521         ConfirmType = maxRoomNetCfg;
522         if (OneRNCfg==NULL)
523         {
524                 errmsg = "no networking config found";
525         }
526         else for (i = 0; i < 2; i++)
527         {
528                 int offset;
529
530                 if (ConfirmSubscribers[i] == subpending)
531                         offset = 2;
532                 else
533                         offset = 1;
534                 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
535                 Line = *PrevLine;
536                 while (Line != NULL)
537                 {
538                         if (!strcasecmp(ChrPtr(*token),
539                                         ChrPtr(Line->Value[offset])))
540                         {
541                                 ConfirmLine = Line;
542                                 *PrevLine = Line->next; /* Remove it from the list */
543                                 ConfirmType = ConfirmSubscribers[i];
544                                 ConfirmLine->next = NULL;
545
546                                 i += 100; 
547                                 break;
548
549                         }
550                         PrevLine = &(*PrevLine)->next;
551                         Line = Line->next;
552                 }
553                 if (ConfirmType == maxRoomNetCfg)
554                 {
555                         errmsg = "No active un/subscribe request found";
556                 }
557         }
558
559         if (ConfirmType == subpending)
560         {
561                 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
562                 {
563                         if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]), 
564                                         ("digest")))
565                         {
566                                 ConfirmType = digestrecp;
567                         }
568                         else /* "list" */
569                         {
570                                 ConfirmType = listrecp;
571                         }
572
573                         syslog(LOG_NOTICE, 
574                                "Mailing list: %s subscribed to %s with token %s\n", 
575                                ChrPtr(ConfirmLine->Value[0]), 
576                                qrbuf.QRname,
577                                ChrPtr(*token));
578
579                         FreeStrBuf(&ConfirmLine->Value[1]);
580                         FreeStrBuf(&ConfirmLine->Value[2]);
581                         FreeStrBuf(&ConfirmLine->Value[3]);
582                         FreeStrBuf(&ConfirmLine->Value[4]);
583                         ConfirmLine->nValues = 1;
584                 
585                         AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
586                         success = 1;
587                 }
588                 else
589                 {
590                         /* whipe duplicate subscribe entry... */
591                         OneRNCfg->changed = 1;
592                         SaveChangedConfigs();
593                         errmsg = "already subscribed";
594                 }
595         }
596         else if (ConfirmType == unsubpending)
597         {
598
599                 for (i = 0; i < 2; i++)
600                 {
601                         PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
602                         Line = *PrevLine;
603                         while (Line != NULL)
604                         {
605                                 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
606                                                 ChrPtr(Line->Value[0])))
607                                 {
608                                         success = 1;
609                                         RemoveLine = Line;
610                                         *PrevLine = Line->next; /* Remove it from the list */
611                                         RemoveLine->next = NULL;
612                                         if (RemoveLine != NULL) 
613                                                 DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine);
614                                         Line = *PrevLine;
615                                         continue;
616                                 }
617                                 PrevLine = &(*PrevLine)->next;
618                                 Line = Line->next;
619                         }
620                 }
621
622                 if (success) 
623                 {
624                         syslog(LOG_NOTICE, 
625                                "Mailing list: %s unsubscribed to %s with token %s\n", 
626                                ChrPtr(ConfirmLine->Value[0]), 
627                                qrbuf.QRname,
628                                ChrPtr(*token));
629                 }
630                 else
631                 {
632                         errmsg = "no subscriber found for this unsubscription request";
633                 }
634                 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
635                 OneRNCfg->changed = 1;
636                 SaveChangedConfigs();
637         }
638
639         end_critical_section(S_NETCONFIGS);
640         
641         /*
642          * Did we do anything useful today?
643          */
644         if (success) {
645                 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
646         }
647         else {
648                 syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s",
649                        errmsg);
650                 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
651         }
652
653 }
654
655
656
657 /* 
658  * process subscribe/unsubscribe requests and confirmations
659  */
660 void cmd_subs(char *cmdbuf)
661 {
662         const char *Pos = NULL;
663         StrBuf *Segments[20];
664         int i=1;
665
666         memset(Segments, 0, sizeof(StrBuf*) * 20);
667         Segments[0] = NewStrBufPlain(cmdbuf, -1);
668         while ((Pos != StrBufNOTNULL) && (i < 20))
669         {
670                 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
671                 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
672                 i++;
673         }
674
675         if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
676                 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
677                    && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
678                         cprintf("%d Invalid subscription type '%s'\n",
679                                 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
680                 }
681                 else {
682                         do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
683                 }
684         }
685         else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
686                 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
687         }
688         else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
689                 do_confirm(&Segments[2], &Segments[3]);
690         }
691         else {
692                 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
693         }
694
695         for (; i>=0; i--)
696         {
697                 FreeStrBuf(&Segments[i]);
698         }
699 }
700
701
702 /*
703  * Module entry point
704  */
705 CTDL_MODULE_INIT(listsub)
706 {
707         if (!threading)
708         {
709                 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
710         }
711         
712         /* return our module name for the log */
713         return "listsub";
714 }