2 * This module handles self-service subscription/unsubscription to mail lists.
4 * Copyright (c) 2002-2016 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 FreeRoomNetworkStruct(&OneRNCfg);
151 end_critical_section(S_NETCONFIGS);
156 * Now add it to the config
159 RoomMailAddressLen = strlen(RoomMailAddress);
160 listsub_generate_token(token);
161 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
162 memset(Line, 0, sizeof(RoomNetCfgLine));
164 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
166 Line->Value[0] = NewStrBufDup(*email);
167 Line->Value[1] = *subtype; *subtype = NULL;
168 Line->Value[2] = NewStrBufPlain(token, -1);
169 Line->Value[3] = NewStrBufPlain(NULL, 10);
170 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
171 Line->Value[4] = *webpage; *webpage = NULL;
174 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
176 /* Generate and send the confirmation request */
177 UrlRoom = NewStrBuf();
178 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
180 cf_req = NewStrBufPlain(NULL, 2048);
181 StrBufAppendBufPlain(
183 HKEY("MIME-Version: 1.0\n"
184 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
186 "This is a multipart message in MIME format.\n"
188 "--__ctdlmultipart__\n"
189 "Content-type: text/plain\n"
191 "Someone (probably you) has submitted a request to subscribe\n"
193 StrBufAppendBuf(cf_req, Line->Value[0], 0);
195 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
196 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
198 StrBufAppendBufPlain(
200 HKEY("' mailing list.\n"
202 "Please go here to confirm this request:\n"
204 StrBufAppendBuf(cf_req, Line->Value[4], 0);
206 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
207 StrBufAppendBuf(cf_req, UrlRoom, 0);
209 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
210 StrBufAppendBuf(cf_req, Line->Value[2], 0);
212 StrBufAppendBufPlain(
214 HKEY("&cmd=confirm \n"
216 "If this request has been submitted in error and you do not\n"
217 "wish to receive the '"), 0);
218 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
220 StrBufAppendBufPlain(
222 HKEY("' mailing list, simply do nothing,\n"
223 "and you will not receive any further mailings.\n"
225 "--__ctdlmultipart__\n"
226 "Content-type: text/html\n"
229 "Someone (probably you) has submitted a request to subscribe\n"
231 StrBufAppendBuf(cf_req, Line->Value[0], 0);
233 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
235 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
237 StrBufAppendBufPlain(
239 HKEY("'</B> mailing list.<BR><BR>\n"
240 "Please click here to confirm this request:<BR>\n"
242 StrBufAppendBuf(cf_req, Line->Value[4], 0);
244 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
245 StrBufAppendBuf(cf_req, UrlRoom, 0);
247 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
248 StrBufAppendBuf(cf_req, Line->Value[2], 0);
250 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
251 StrBufAppendBuf(cf_req, Line->Value[4], 0);
253 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
254 StrBufAppendBuf(cf_req, UrlRoom, 0);
256 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
257 StrBufAppendBuf(cf_req, Line->Value[2], 0);
259 StrBufAppendBufPlain(
261 HKEY("&cmd=confirm</A><BR><BR>\n"
262 "If this request has been submitted in error and you do not\n"
263 "wish to receive the '"), 0);
264 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
266 StrBufAppendBufPlain(
268 HKEY("' mailing list, simply do nothing,\n"
269 "and you will not receive any further mailings.\n"
272 "--__ctdlmultipart__--\n"), 0);
274 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
275 FreeRoomNetworkStruct(&OneRNCfg);
276 end_critical_section(S_NETCONFIGS);
278 pcf_req = SmashStrBuf(&cf_req);
279 quickie_message( /* This delivers the message */
286 "Please confirm your list subscription"
289 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
291 FreeStrBuf(&UrlRoom);
296 * Enter an unsubscription request
298 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
299 struct ctdlroom qrbuf;
300 const char *EmailSender = NULL;
306 const char *RoomMailAddress;
307 OneRoomNetCfg *OneRNCfg;
308 RoomNetCfgLine *Line;
309 long RoomMailAddressLen;
311 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
312 cprintf("%d There is no list called '%s'\n",
313 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
317 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
319 "does not accept subscribe/unsubscribe requests.\n",
320 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
324 listsub_generate_token(token);
327 * Make sure there's actually a subscription there to remove
329 begin_critical_section(S_NETCONFIGS);
330 RoomMailAddress = qrbuf.QRname;
331 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
332 if (OneRNCfg!=NULL) {
333 found_sub = CountThisSubscriber(OneRNCfg, *email);
334 if (StrLength(OneRNCfg->Sender) > 0)
335 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
338 if (found_sub == 0) {
339 cprintf("%d '%s' is not subscribed to '%s'.\n", ERROR + NO_SUCH_USER, ChrPtr(*email), qrbuf.QRname);
340 FreeRoomNetworkStruct(&OneRNCfg);
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 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
465 FreeRoomNetworkStruct(&OneRNCfg);
466 end_critical_section(S_NETCONFIGS);
468 pcf_req = SmashStrBuf(&cf_req);
469 quickie_message( /* This delivers the message */
476 "Please confirm your unsubscribe request"
480 FreeStrBuf(&UrlRoom);
481 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
485 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
488 * Confirm a subscribe/unsubscribe request.
490 void do_confirm(StrBuf **room, StrBuf **token) {
491 struct ctdlroom qrbuf;
492 OneRoomNetCfg *OneRNCfg;
493 RoomNetCfgLine *Line;
494 RoomNetCfgLine *ConfirmLine = NULL;
495 RoomNetCfgLine *RemoveLine = NULL;
496 RoomNetCfgLine **PrevLine;
498 RoomNetCfg ConfirmType;
499 const char *errmsg = "";
502 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
503 cprintf("%d There is no list called '%s'\n",
504 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
508 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
510 "does not accept subscribe/unsubscribe requests.\n",
511 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
516 if (StrLength(*token) == 0) {
517 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
521 * Now start scanning this room's netconfig file for the
524 begin_critical_section(S_NETCONFIGS);
525 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
527 ConfirmType = maxRoomNetCfg;
530 errmsg = "no networking config found";
532 else for (i = 0; i < 2; i++)
536 if (ConfirmSubscribers[i] == subpending)
540 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
544 if (!strcasecmp(ChrPtr(*token),
545 ChrPtr(Line->Value[offset])))
548 *PrevLine = Line->next; /* Remove it from the list */
549 ConfirmType = ConfirmSubscribers[i];
550 ConfirmLine->next = NULL;
556 PrevLine = &(*PrevLine)->next;
559 if (ConfirmType == maxRoomNetCfg)
561 errmsg = "No active un/subscribe request found";
565 if (ConfirmType == subpending)
567 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
569 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
572 ConfirmType = digestrecp;
576 ConfirmType = listrecp;
580 "Mailing list: %s subscribed to %s with token %s\n",
581 ChrPtr(ConfirmLine->Value[0]),
585 FreeStrBuf(&ConfirmLine->Value[1]);
586 FreeStrBuf(&ConfirmLine->Value[2]);
587 FreeStrBuf(&ConfirmLine->Value[3]);
588 FreeStrBuf(&ConfirmLine->Value[4]);
589 ConfirmLine->nValues = 1;
591 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
596 /* whipe duplicate subscribe entry... */
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);
641 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
642 FreeRoomNetworkStruct(&OneRNCfg);
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 */