2 * This module handles self-service subscription/unsubscription to mail lists.
4 * Copyright (c) 2002-2012 by the citadel.org team
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.
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.
24 #include <sys/types.h>
26 #if TIME_WITH_SYS_TIME
27 # include <sys/time.h>
31 # include <sys/time.h>
40 #include <libcitadel.h>
43 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "clientsocket.h"
51 #include "ctdl_module.h"
54 * Generate a randomizationalisticized token to use for authentication of
55 * a subscribe or unsubscribe request.
57 void listsub_generate_token(char *buf) {
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
66 len = sprintf(sourcebuf, "%lx",
67 (long) (++seq + getpid() + time(NULL))
70 /* Convert it to base64 so it looks cool */
71 len = CtdlEncodeBase64(buf, sourcebuf, len, 0);
72 if (buf[len - 1] == '\n') {
77 const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp};
79 int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email)
85 for (i = 0; i < 2; i++)
87 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
90 if (!strcmp(ChrPtr(email),
91 ChrPtr(Line->Value[0])))
103 * Enter a subscription request
105 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
106 struct ctdlroom qrbuf;
112 const char *RoomMailAddress;
113 OneRoomNetCfg *OneRNCfg;
114 RoomNetCfgLine *Line;
115 const char *EmailSender = NULL;
116 long RoomMailAddressLen;
118 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
119 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
123 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
125 "does not accept subscribe/unsubscribe requests.\n",
126 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
131 * Make sure the requested address isn't already subscribed
133 begin_critical_section(S_NETCONFIGS);
135 RoomMailAddress = qrbuf.QRname;
136 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
137 if (OneRNCfg!=NULL) {
138 found_sub = CountThisSubscriber(OneRNCfg, *email);
139 if (StrLength(OneRNCfg->Sender) > 0) {
140 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
144 if (found_sub != 0) {
145 cprintf("%d '%s' is already subscribed to '%s'.\n",
146 ERROR + ALREADY_EXISTS,
150 end_critical_section(S_NETCONFIGS);
155 * Now add it to the config
158 RoomMailAddressLen = strlen(RoomMailAddress);
159 listsub_generate_token(token);
160 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
161 memset(Line, 0, sizeof(RoomNetCfgLine));
163 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
165 Line->Value[0] = NewStrBufDup(*email);
166 Line->Value[1] = *subtype; *subtype = NULL;
167 Line->Value[2] = NewStrBufPlain(token, -1);
168 Line->Value[3] = NewStrBufPlain(NULL, 10);
169 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
170 Line->Value[4] = *webpage; *webpage = NULL;
173 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
175 /* Generate and send the confirmation request */
176 UrlRoom = NewStrBuf();
177 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
179 cf_req = NewStrBufPlain(NULL, 2048);
180 StrBufAppendBufPlain(
182 HKEY("MIME-Version: 1.0\n"
183 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
185 "This is a multipart message in MIME format.\n"
187 "--__ctdlmultipart__\n"
188 "Content-type: text/plain\n"
190 "Someone (probably you) has submitted a request to subscribe\n"
192 StrBufAppendBuf(cf_req, Line->Value[0], 0);
194 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
195 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
197 StrBufAppendBufPlain(
199 HKEY("' mailing list.\n"
201 "Please go here to confirm this request:\n"
203 StrBufAppendBuf(cf_req, Line->Value[4], 0);
205 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
206 StrBufAppendBuf(cf_req, UrlRoom, 0);
208 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
209 StrBufAppendBuf(cf_req, Line->Value[2], 0);
211 StrBufAppendBufPlain(
213 HKEY("&cmd=confirm \n"
215 "If this request has been submitted in error and you do not\n"
216 "wish to receive the '"), 0);
217 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
219 StrBufAppendBufPlain(
221 HKEY("' mailing list, simply do nothing,\n"
222 "and you will not receive any further mailings.\n"
224 "--__ctdlmultipart__\n"
225 "Content-type: text/html\n"
228 "Someone (probably you) has submitted a request to subscribe\n"
230 StrBufAppendBuf(cf_req, Line->Value[0], 0);
232 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
234 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
236 StrBufAppendBufPlain(
238 HKEY("'</B> mailing list.<BR><BR>\n"
239 "Please click here to confirm this request:<BR>\n"
241 StrBufAppendBuf(cf_req, Line->Value[4], 0);
243 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
244 StrBufAppendBuf(cf_req, UrlRoom, 0);
246 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
247 StrBufAppendBuf(cf_req, Line->Value[2], 0);
249 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
250 StrBufAppendBuf(cf_req, Line->Value[4], 0);
252 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
253 StrBufAppendBuf(cf_req, UrlRoom, 0);
255 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
256 StrBufAppendBuf(cf_req, Line->Value[2], 0);
258 StrBufAppendBufPlain(
260 HKEY("&cmd=confirm</A><BR><BR>\n"
261 "If this request has been submitted in error and you do not\n"
262 "wish to receive the '"), 0);
263 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
265 StrBufAppendBufPlain(
267 HKEY("' mailing list, simply do nothing,\n"
268 "and you will not receive any further mailings.\n"
271 "--__ctdlmultipart__--\n"), 0);
273 end_critical_section(S_NETCONFIGS);
275 pcf_req = SmashStrBuf(&cf_req);
276 quickie_message( /* This delivers the message */
283 "Please confirm your list subscription"
286 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
288 FreeStrBuf(&UrlRoom);
293 * Enter an unsubscription request
295 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
296 struct ctdlroom qrbuf;
297 const char *EmailSender = NULL;
303 const char *RoomMailAddress;
304 OneRoomNetCfg *OneRNCfg;
305 RoomNetCfgLine *Line;
306 long RoomMailAddressLen;
308 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
309 cprintf("%d There is no list called '%s'\n",
310 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
314 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
316 "does not accept subscribe/unsubscribe requests.\n",
317 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
321 listsub_generate_token(token);
324 * Make sure there's actually a subscription there to remove
326 begin_critical_section(S_NETCONFIGS);
327 RoomMailAddress = qrbuf.QRname;
328 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
329 if (OneRNCfg!=NULL) {
330 found_sub = CountThisSubscriber(OneRNCfg, *email);
331 if (StrLength(OneRNCfg->Sender) > 0)
332 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
335 if (found_sub == 0) {
336 cprintf("%d '%s' is not subscribed to '%s'.\n",
337 ERROR + NO_SUCH_USER,
341 end_critical_section(S_NETCONFIGS);
346 * Ok, now enter the unsubscribe-pending entry.
348 RoomMailAddressLen = strlen(RoomMailAddress);
349 listsub_generate_token(token);
350 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
351 memset(Line, 0, sizeof(RoomNetCfgLine));
353 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4);
355 Line->Value[0] = NewStrBufDup(*email);
356 Line->Value[1] = NewStrBufPlain(token, -1);
357 Line->Value[2] = NewStrBufPlain(NULL, 10);
358 StrBufPrintf(Line->Value[2], "%ld", time(NULL));
359 Line->Value[3] = *webpage; *webpage = NULL;
362 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
364 /* Generate and send the confirmation request */
365 UrlRoom = NewStrBuf();
366 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
368 cf_req = NewStrBufPlain(NULL, 2048);
370 StrBufAppendBufPlain(
372 HKEY("MIME-Version: 1.0\n"
373 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
375 "This is a multipart message in MIME format.\n"
377 "--__ctdlmultipart__\n"
378 "Content-type: text/plain\n"
380 "Someone (probably you) has submitted a request to unsubscribe\n"
382 StrBufAppendBuf(cf_req, Line->Value[0], 0);
385 StrBufAppendBufPlain(
387 HKEY("> from the '"), 0);
388 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
390 StrBufAppendBufPlain(
392 HKEY("' mailing list.\n"
394 "Please go here to confirm this request:\n "), 0);
395 StrBufAppendBuf(cf_req, Line->Value[3], 0);
396 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
397 StrBufAppendBuf(cf_req, UrlRoom, 0);
398 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
399 StrBufAppendBuf(cf_req, Line->Value[1], 0);
401 StrBufAppendBufPlain(
403 HKEY("&cmd=confirm \n"
405 "If this request has been submitted in error and you do not\n"
406 "wish to unsubscribe from the '"), 0);
408 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
410 StrBufAppendBufPlain(
412 HKEY("' mailing list, simply do nothing,\n"
413 "and the request will not be processed.\n"
415 "--__ctdlmultipart__\n"
416 "Content-type: text/html\n"
419 "Someone (probably you) has submitted a request to unsubscribe\n"
421 StrBufAppendBuf(cf_req, Line->Value[0], 0);
423 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
424 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
426 StrBufAppendBufPlain(
428 HKEY("</B> mailing list.<BR><BR>\n"
429 "Please click here to confirm this request:<BR>\n"
431 StrBufAppendBuf(cf_req, Line->Value[3], 0);
433 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
434 StrBufAppendBuf(cf_req, UrlRoom, 0);
436 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
437 StrBufAppendBuf(cf_req, Line->Value[1], 0);
439 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
440 StrBufAppendBuf(cf_req, Line->Value[3], 0);
442 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
443 StrBufAppendBuf(cf_req, UrlRoom, 0);
445 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
446 StrBufAppendBuf(cf_req, Line->Value[1], 0);
449 StrBufAppendBufPlain(
451 HKEY("&cmd=confirm</A><BR><BR>\n"
452 "If this request has been submitted in error and you do not\n"
453 "wish to unsubscribe from the '"), 0);
454 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
456 StrBufAppendBufPlain(
458 HKEY("' mailing list, simply do nothing,\n"
459 "and the request will not be processed.\n"
462 "--__ctdlmultipart__--\n"), 0);
464 end_critical_section(S_NETCONFIGS);
466 pcf_req = SmashStrBuf(&cf_req);
467 quickie_message( /* This delivers the message */
474 "Please confirm your unsubscribe request"
478 FreeStrBuf(&UrlRoom);
479 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
483 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
486 * Confirm a subscribe/unsubscribe request.
488 void do_confirm(StrBuf **room, StrBuf **token) {
489 struct ctdlroom qrbuf;
490 OneRoomNetCfg *OneRNCfg;
491 RoomNetCfgLine *Line;
492 RoomNetCfgLine *ConfirmLine = NULL;
493 RoomNetCfgLine *RemoveLine = NULL;
494 RoomNetCfgLine **PrevLine;
496 RoomNetCfg ConfirmType;
497 const char *errmsg = "";
500 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
501 cprintf("%d There is no list called '%s'\n",
502 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
506 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
508 "does not accept subscribe/unsubscribe requests.\n",
509 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
514 if (StrLength(*token) == 0) {
515 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
519 * Now start scanning this room's netconfig file for the
522 begin_critical_section(S_NETCONFIGS);
523 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
525 ConfirmType = maxRoomNetCfg;
528 errmsg = "no networking config found";
530 else for (i = 0; i < 2; i++)
534 if (ConfirmSubscribers[i] == subpending)
538 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
542 if (!strcasecmp(ChrPtr(*token),
543 ChrPtr(Line->Value[offset])))
546 *PrevLine = Line->next; /* Remove it from the list */
547 ConfirmType = ConfirmSubscribers[i];
548 ConfirmLine->next = NULL;
554 PrevLine = &(*PrevLine)->next;
557 if (ConfirmType == maxRoomNetCfg)
559 errmsg = "No active un/subscribe request found";
563 if (ConfirmType == subpending)
565 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
567 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
570 ConfirmType = digestrecp;
574 ConfirmType = listrecp;
578 "Mailing list: %s subscribed to %s with token %s\n",
579 ChrPtr(ConfirmLine->Value[0]),
583 FreeStrBuf(&ConfirmLine->Value[1]);
584 FreeStrBuf(&ConfirmLine->Value[2]);
585 FreeStrBuf(&ConfirmLine->Value[3]);
586 FreeStrBuf(&ConfirmLine->Value[4]);
587 ConfirmLine->nValues = 1;
589 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
594 /* whipe duplicate subscribe entry... */
595 OneRNCfg->changed = 1;
596 SaveChangedConfigs();
597 errmsg = "already subscribed";
600 else if (ConfirmType == unsubpending)
603 for (i = 0; i < 2; i++)
605 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
609 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
610 ChrPtr(Line->Value[0])))
614 *PrevLine = Line->next; /* Remove it from the list */
615 RemoveLine->next = NULL;
616 if (RemoveLine != NULL)
617 DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine);
621 PrevLine = &(*PrevLine)->next;
629 "Mailing list: %s unsubscribed to %s with token %s\n",
630 ChrPtr(ConfirmLine->Value[0]),
636 errmsg = "no subscriber found for this unsubscription request";
638 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
639 OneRNCfg->changed = 1;
640 SaveChangedConfigs();
643 end_critical_section(S_NETCONFIGS);
646 * Did we do anything useful today?
649 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
652 syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s",
654 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
662 * process subscribe/unsubscribe requests and confirmations
664 void cmd_subs(char *cmdbuf)
666 const char *Pos = NULL;
667 StrBuf *Segments[20];
670 memset(Segments, 0, sizeof(StrBuf*) * 20);
671 Segments[0] = NewStrBufPlain(cmdbuf, -1);
672 while ((Pos != StrBufNOTNULL) && (i < 20))
674 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
675 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
679 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
680 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
681 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
682 cprintf("%d Invalid subscription type '%s'\n",
683 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
686 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
689 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
690 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
692 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
693 do_confirm(&Segments[2], &Segments[3]);
696 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
701 FreeStrBuf(&Segments[i]);
709 CTDL_MODULE_INIT(listsub)
713 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
716 /* return our module name for the log */