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", (long) (++seq + getpid() + time(NULL)));
68 /* Convert it to base64 so it looks cool */
69 len = CtdlEncodeBase64(buf, sourcebuf, len, 0);
70 if (buf[len - 1] == '\n') {
76 const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp};
78 int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email)
84 for (i = 0; i < 2; i++)
86 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
89 if (!strcmp(ChrPtr(email),
90 ChrPtr(Line->Value[0])))
102 * Enter a subscription request
104 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
105 struct ctdlroom qrbuf;
111 const char *RoomMailAddress;
112 OneRoomNetCfg *OneRNCfg;
113 RoomNetCfgLine *Line;
114 const char *EmailSender = NULL;
115 long RoomMailAddressLen;
117 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
118 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
122 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
124 "does not accept subscribe/unsubscribe requests.\n",
125 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
130 * Make sure the requested address isn't already subscribed
132 begin_critical_section(S_NETCONFIGS);
134 RoomMailAddress = qrbuf.QRname;
135 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
136 if (OneRNCfg != NULL) {
137 found_sub = CountThisSubscriber(OneRNCfg, *email);
138 if (StrLength(OneRNCfg->Sender) > 0) {
139 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
143 if (found_sub != 0) {
144 cprintf("%d '%s' is already subscribed to '%s'.\n",
145 ERROR + ALREADY_EXISTS,
149 FreeRoomNetworkStruct(&OneRNCfg);
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 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
274 FreeRoomNetworkStruct(&OneRNCfg);
275 end_critical_section(S_NETCONFIGS);
277 pcf_req = SmashStrBuf(&cf_req);
278 quickie_message( /* This delivers the message */
285 "Please confirm your list subscription"
288 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
290 FreeStrBuf(&UrlRoom);
295 * Enter an unsubscription request
297 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
298 struct ctdlroom qrbuf;
299 const char *EmailSender = NULL;
305 const char *RoomMailAddress;
306 OneRoomNetCfg *OneRNCfg;
307 RoomNetCfgLine *Line;
308 long RoomMailAddressLen;
310 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
311 cprintf("%d There is no list called '%s'\n",
312 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
316 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
318 "does not accept subscribe/unsubscribe requests.\n",
319 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
323 listsub_generate_token(token);
326 * Make sure there's actually a subscription there to remove
328 begin_critical_section(S_NETCONFIGS);
329 RoomMailAddress = qrbuf.QRname;
330 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
331 if (OneRNCfg!=NULL) {
332 found_sub = CountThisSubscriber(OneRNCfg, *email);
333 if (StrLength(OneRNCfg->Sender) > 0)
334 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
337 if (found_sub == 0) {
338 cprintf("%d '%s' is not subscribed to '%s'.\n", ERROR + NO_SUCH_USER, ChrPtr(*email), qrbuf.QRname);
339 FreeRoomNetworkStruct(&OneRNCfg);
340 end_critical_section(S_NETCONFIGS);
345 * Ok, now enter the unsubscribe-pending entry.
347 RoomMailAddressLen = strlen(RoomMailAddress);
348 listsub_generate_token(token);
349 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
350 memset(Line, 0, sizeof(RoomNetCfgLine));
352 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4);
354 Line->Value[0] = NewStrBufDup(*email);
355 Line->Value[1] = NewStrBufPlain(token, -1);
356 Line->Value[2] = NewStrBufPlain(NULL, 10);
357 StrBufPrintf(Line->Value[2], "%ld", time(NULL));
358 Line->Value[3] = *webpage; *webpage = NULL;
361 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
363 /* Generate and send the confirmation request */
364 UrlRoom = NewStrBuf();
365 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
367 cf_req = NewStrBufPlain(NULL, 2048);
369 StrBufAppendBufPlain(
371 HKEY("MIME-Version: 1.0\n"
372 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
374 "This is a multipart message in MIME format.\n"
376 "--__ctdlmultipart__\n"
377 "Content-type: text/plain\n"
379 "Someone (probably you) has submitted a request to unsubscribe\n"
381 StrBufAppendBuf(cf_req, Line->Value[0], 0);
384 StrBufAppendBufPlain(
386 HKEY("> from the '"), 0);
387 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
389 StrBufAppendBufPlain(
391 HKEY("' mailing list.\n"
393 "Please go here to confirm this request:\n "), 0);
394 StrBufAppendBuf(cf_req, Line->Value[3], 0);
395 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
396 StrBufAppendBuf(cf_req, UrlRoom, 0);
397 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
398 StrBufAppendBuf(cf_req, Line->Value[1], 0);
400 StrBufAppendBufPlain(
402 HKEY("&cmd=confirm \n"
404 "If this request has been submitted in error and you do not\n"
405 "wish to unsubscribe from the '"), 0);
407 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
409 StrBufAppendBufPlain(
411 HKEY("' mailing list, simply do nothing,\n"
412 "and the request will not be processed.\n"
414 "--__ctdlmultipart__\n"
415 "Content-type: text/html\n"
418 "Someone (probably you) has submitted a request to unsubscribe\n"
420 StrBufAppendBuf(cf_req, Line->Value[0], 0);
422 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
423 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
425 StrBufAppendBufPlain(
427 HKEY("</B> mailing list.<BR><BR>\n"
428 "Please click here to confirm this request:<BR>\n"
430 StrBufAppendBuf(cf_req, Line->Value[3], 0);
432 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
433 StrBufAppendBuf(cf_req, UrlRoom, 0);
435 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
436 StrBufAppendBuf(cf_req, Line->Value[1], 0);
438 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
439 StrBufAppendBuf(cf_req, Line->Value[3], 0);
441 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
442 StrBufAppendBuf(cf_req, UrlRoom, 0);
444 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
445 StrBufAppendBuf(cf_req, Line->Value[1], 0);
448 StrBufAppendBufPlain(
450 HKEY("&cmd=confirm</A><BR><BR>\n"
451 "If this request has been submitted in error and you do not\n"
452 "wish to unsubscribe from the '"), 0);
453 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
455 StrBufAppendBufPlain(
457 HKEY("' mailing list, simply do nothing,\n"
458 "and the request will not be processed.\n"
461 "--__ctdlmultipart__--\n"), 0);
463 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
464 FreeRoomNetworkStruct(&OneRNCfg);
465 end_critical_section(S_NETCONFIGS);
467 pcf_req = SmashStrBuf(&cf_req);
468 quickie_message( /* This delivers the message */
475 "Please confirm your unsubscribe request"
479 FreeStrBuf(&UrlRoom);
480 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
484 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
487 * Confirm a subscribe/unsubscribe request.
489 void do_confirm(StrBuf **room, StrBuf **token) {
490 struct ctdlroom qrbuf;
491 OneRoomNetCfg *OneRNCfg;
492 RoomNetCfgLine *Line;
493 RoomNetCfgLine *ConfirmLine = NULL;
494 RoomNetCfgLine *RemoveLine = NULL;
495 RoomNetCfgLine **PrevLine;
497 RoomNetCfg ConfirmType;
498 const char *errmsg = "";
501 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
502 cprintf("%d There is no list called '%s'\n",
503 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
507 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
509 "does not accept subscribe/unsubscribe requests.\n",
510 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
515 if (StrLength(*token) == 0) {
516 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
520 * Now start scanning this room's netconfig file for the
523 begin_critical_section(S_NETCONFIGS);
524 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
526 ConfirmType = maxRoomNetCfg;
529 errmsg = "no networking config found";
531 else for (i = 0; i < 2; i++)
535 if (ConfirmSubscribers[i] == subpending)
539 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
543 if (!strcasecmp(ChrPtr(*token),
544 ChrPtr(Line->Value[offset])))
547 *PrevLine = Line->next; /* Remove it from the list */
548 ConfirmType = ConfirmSubscribers[i];
549 ConfirmLine->next = NULL;
555 PrevLine = &(*PrevLine)->next;
558 if (ConfirmType == maxRoomNetCfg)
560 errmsg = "No active un/subscribe request found";
564 if (ConfirmType == subpending)
566 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
568 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
571 ConfirmType = digestrecp;
575 ConfirmType = listrecp;
579 "Mailing list: %s subscribed to %s with token %s\n",
580 ChrPtr(ConfirmLine->Value[0]),
584 FreeStrBuf(&ConfirmLine->Value[1]);
585 FreeStrBuf(&ConfirmLine->Value[2]);
586 FreeStrBuf(&ConfirmLine->Value[3]);
587 FreeStrBuf(&ConfirmLine->Value[4]);
588 ConfirmLine->nValues = 1;
590 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
595 /* whipe duplicate subscribe entry... */
596 errmsg = "already subscribed";
599 else if (ConfirmType == unsubpending)
602 for (i = 0; i < 2; i++)
604 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
608 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
609 ChrPtr(Line->Value[0])))
613 *PrevLine = Line->next; /* Remove it from the list */
614 RemoveLine->next = NULL;
615 if (RemoveLine != NULL)
616 DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine);
620 PrevLine = &(*PrevLine)->next;
628 "Mailing list: %s unsubscribed to %s with token %s\n",
629 ChrPtr(ConfirmLine->Value[0]),
635 errmsg = "no subscriber found for this unsubscription request";
637 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
640 SaveRoomNetConfigFile(OneRNCfg, qrbuf.QRnumber);
641 FreeRoomNetworkStruct(&OneRNCfg);
642 end_critical_section(S_NETCONFIGS);
645 * Did we do anything useful today?
648 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
651 syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s",
653 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
661 * process subscribe/unsubscribe requests and confirmations
663 void cmd_subs(char *cmdbuf)
665 const char *Pos = NULL;
666 StrBuf *Segments[20];
669 memset(Segments, 0, sizeof(StrBuf*) * 20);
670 Segments[0] = NewStrBufPlain(cmdbuf, -1);
671 while ((Pos != StrBufNOTNULL) && (i < 20))
673 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
674 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
678 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
679 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
680 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
681 cprintf("%d Invalid subscription type '%s'\n",
682 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
685 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
688 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
689 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
691 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
692 do_confirm(&Segments[2], &Segments[3]);
695 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
700 FreeStrBuf(&Segments[i]);
708 CTDL_MODULE_INIT(listsub)
712 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
715 /* return our module name for the log */