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