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"
52 #include "ctdl_module.h"
55 * Generate a randomizationalisticized token to use for authentication of
56 * a subscribe or unsubscribe request.
58 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 sprintf(sourcebuf, "%lx",
67 (long) (++seq + getpid() + time(NULL))
70 /* Convert it to base64 so it looks cool */
71 CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf), 0);
74 const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp};
76 int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email)
82 for (i = 0; i < 2; i++)
84 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
87 if (!strcmp(ChrPtr(email),
88 ChrPtr(Line->Value[0])))
100 * Enter a subscription request
102 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
103 struct ctdlroom qrbuf;
109 const char *RoomMailAddress;
110 OneRoomNetCfg *OneRNCfg;
111 RoomNetCfgLine *Line;
112 const char *EmailSender = NULL;
113 long RoomMailAddressLen;
115 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
116 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
120 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
122 "does not accept subscribe/unsubscribe requests.\n",
123 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
128 * Make sure the requested address isn't already subscribed
130 begin_critical_section(S_NETCONFIGS);
132 RoomMailAddress = qrbuf.QRname;
133 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
134 if (OneRNCfg!=NULL) {
135 found_sub = CountThisSubscriber(OneRNCfg, *email);
136 if (StrLength(OneRNCfg->Sender) > 0) {
137 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
141 if (found_sub != 0) {
142 cprintf("%d '%s' is already subscribed to '%s'.\n",
143 ERROR + ALREADY_EXISTS,
147 end_critical_section(S_NETCONFIGS);
152 * Now add it to the config
155 RoomMailAddressLen = strlen(RoomMailAddress);
156 listsub_generate_token(token);
157 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
158 memset(Line, 0, sizeof(RoomNetCfgLine));
160 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
162 Line->Value[0] = NewStrBufDup(*email);
163 Line->Value[1] = *subtype; *subtype = NULL;
164 Line->Value[2] = NewStrBufPlain(token, -1);
165 Line->Value[3] = NewStrBufPlain(NULL, 10);
166 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
167 Line->Value[4] = *webpage; *webpage = NULL;
170 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
172 /* Generate and send the confirmation request */
173 UrlRoom = NewStrBuf();
174 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
176 cf_req = NewStrBufPlain(NULL, 2048);
177 StrBufAppendBufPlain(
179 HKEY("MIME-Version: 1.0\n"
180 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
182 "This is a multipart message in MIME format.\n"
184 "--__ctdlmultipart__\n"
185 "Content-type: text/plain\n"
187 "Someone (probably you) has submitted a request to subscribe\n"
189 StrBufAppendBuf(cf_req, Line->Value[0], 0);
191 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
192 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
194 StrBufAppendBufPlain(
196 HKEY("' mailing list.\n"
198 "Please go here to confirm this request:\n"
200 StrBufAppendBuf(cf_req, Line->Value[4], 0);
202 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
203 StrBufAppendBuf(cf_req, UrlRoom, 0);
205 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
206 StrBufAppendBuf(cf_req, Line->Value[2], 0);
208 StrBufAppendBufPlain(
210 HKEY("&cmd=confirm \n"
212 "If this request has been submitted in error and you do not\n"
213 "wish to receive the '"), 0);
214 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
216 StrBufAppendBufPlain(
218 HKEY("' mailing list, simply do nothing,\n"
219 "and you will not receive any further mailings.\n"
221 "--__ctdlmultipart__\n"
222 "Content-type: text/html\n"
225 "Someone (probably you) has submitted a request to subscribe\n"
227 StrBufAppendBuf(cf_req, Line->Value[0], 0);
229 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
231 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
233 StrBufAppendBufPlain(
235 HKEY("'</B> mailing list.<BR><BR>\n"
236 "Please click here to confirm this request:<BR>\n"
238 StrBufAppendBuf(cf_req, Line->Value[4], 0);
240 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
241 StrBufAppendBuf(cf_req, UrlRoom, 0);
243 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
244 StrBufAppendBuf(cf_req, Line->Value[2], 0);
246 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
247 StrBufAppendBuf(cf_req, Line->Value[4], 0);
249 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
250 StrBufAppendBuf(cf_req, UrlRoom, 0);
252 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
253 StrBufAppendBuf(cf_req, Line->Value[2], 0);
255 StrBufAppendBufPlain(
257 HKEY("&cmd=confirm</A><BR><BR>\n"
258 "If this request has been submitted in error and you do not\n"
259 "wish to receive the '"), 0);
260 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
262 StrBufAppendBufPlain(
264 HKEY("' mailing list, simply do nothing,\n"
265 "and you will not receive any further mailings.\n"
268 "--__ctdlmultipart__--\n"), 0);
270 end_critical_section(S_NETCONFIGS);
272 pcf_req = SmashStrBuf(&cf_req);
273 quickie_message( /* This delivers the message */
280 "Please confirm your list subscription"
283 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
285 FreeStrBuf(&UrlRoom);
290 * Enter an unsubscription request
292 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
293 struct ctdlroom qrbuf;
294 const char *EmailSender = NULL;
300 const char *RoomMailAddress;
301 OneRoomNetCfg *OneRNCfg;
302 RoomNetCfgLine *Line;
303 long RoomMailAddressLen;
305 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
306 cprintf("%d There is no list called '%s'\n",
307 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
311 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
313 "does not accept subscribe/unsubscribe requests.\n",
314 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
318 listsub_generate_token(token);
321 * Make sure there's actually a subscription there to remove
323 begin_critical_section(S_NETCONFIGS);
324 RoomMailAddress = qrbuf.QRname;
325 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
326 if (OneRNCfg!=NULL) {
327 found_sub = CountThisSubscriber(OneRNCfg, *email);
328 if (StrLength(OneRNCfg->Sender) > 0)
329 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
332 if (found_sub == 0) {
333 cprintf("%d '%s' is not subscribed to '%s'.\n",
334 ERROR + NO_SUCH_USER,
338 end_critical_section(S_NETCONFIGS);
343 * Ok, now enter the unsubscribe-pending entry.
345 RoomMailAddressLen = strlen(RoomMailAddress);
346 listsub_generate_token(token);
347 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
348 memset(Line, 0, sizeof(RoomNetCfgLine));
350 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4);
352 Line->Value[0] = NewStrBufDup(*email);
353 Line->Value[1] = NewStrBufPlain(token, -1);
354 Line->Value[2] = NewStrBufPlain(NULL, 10);
355 StrBufPrintf(Line->Value[2], "%ld", time(NULL));
356 Line->Value[3] = *webpage; *webpage = NULL;
359 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
361 /* Generate and send the confirmation request */
362 UrlRoom = NewStrBuf();
363 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
365 cf_req = NewStrBufPlain(NULL, 2048);
367 StrBufAppendBufPlain(
369 HKEY("MIME-Version: 1.0\n"
370 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
372 "This is a multipart message in MIME format.\n"
374 "--__ctdlmultipart__\n"
375 "Content-type: text/plain\n"
377 "Someone (probably you) has submitted a request to unsubscribe\n"
379 StrBufAppendBuf(cf_req, Line->Value[0], 0);
382 StrBufAppendBufPlain(
384 HKEY("> from the '"), 0);
385 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
387 StrBufAppendBufPlain(
389 HKEY("' mailing list.\n"
391 "Please go here to confirm this request:\n "), 0);
392 StrBufAppendBuf(cf_req, Line->Value[3], 0);
393 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
394 StrBufAppendBuf(cf_req, UrlRoom, 0);
395 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
396 StrBufAppendBuf(cf_req, Line->Value[1], 0);
398 StrBufAppendBufPlain(
400 HKEY("&cmd=confirm \n"
402 "If this request has been submitted in error and you do not\n"
403 "wish to unsubscribe from the '"), 0);
405 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
407 StrBufAppendBufPlain(
409 HKEY("' mailing list, simply do nothing,\n"
410 "and the request will not be processed.\n"
412 "--__ctdlmultipart__\n"
413 "Content-type: text/html\n"
416 "Someone (probably you) has submitted a request to unsubscribe\n"
418 StrBufAppendBuf(cf_req, Line->Value[0], 0);
420 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
421 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
423 StrBufAppendBufPlain(
425 HKEY("</B> mailing list.<BR><BR>\n"
426 "Please click here to confirm this request:<BR>\n"
428 StrBufAppendBuf(cf_req, Line->Value[3], 0);
430 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
431 StrBufAppendBuf(cf_req, UrlRoom, 0);
433 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
434 StrBufAppendBuf(cf_req, Line->Value[1], 0);
436 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
437 StrBufAppendBuf(cf_req, Line->Value[3], 0);
439 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
440 StrBufAppendBuf(cf_req, UrlRoom, 0);
442 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
443 StrBufAppendBuf(cf_req, Line->Value[1], 0);
446 StrBufAppendBufPlain(
448 HKEY("&cmd=confirm</A><BR><BR>\n"
449 "If this request has been submitted in error and you do not\n"
450 "wish to unsubscribe from the '"), 0);
451 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
453 StrBufAppendBufPlain(
455 HKEY("' mailing list, simply do nothing,\n"
456 "and the request will not be processed.\n"
459 "--__ctdlmultipart__--\n"), 0);
461 end_critical_section(S_NETCONFIGS);
463 pcf_req = SmashStrBuf(&cf_req);
464 quickie_message( /* This delivers the message */
471 "Please confirm your unsubscribe request"
475 FreeStrBuf(&UrlRoom);
476 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
480 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
483 * Confirm a subscribe/unsubscribe request.
485 void do_confirm(StrBuf **room, StrBuf **token) {
486 struct ctdlroom qrbuf;
487 OneRoomNetCfg *OneRNCfg;
488 RoomNetCfgLine *Line;
489 RoomNetCfgLine *ConfirmLine = NULL;
490 RoomNetCfgLine *RemoveLine = NULL;
491 RoomNetCfgLine **PrevLine;
493 RoomNetCfg ConfirmType;
494 const char *errmsg = "";
497 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
498 cprintf("%d There is no list called '%s'\n",
499 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
503 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
505 "does not accept subscribe/unsubscribe requests.\n",
506 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
511 if (StrLength(*token) == 0) {
512 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
516 * Now start scanning this room's netconfig file for the
519 begin_critical_section(S_NETCONFIGS);
520 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
522 ConfirmType = maxRoomNetCfg;
525 errmsg = "no networking config found";
527 else for (i = 0; i < 2; i++)
531 if (ConfirmSubscribers[i] == subpending)
535 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
539 if (!strcasecmp(ChrPtr(*token),
540 ChrPtr(Line->Value[offset])))
543 *PrevLine = Line->next; /* Remove it from the list */
544 ConfirmType = ConfirmSubscribers[i];
545 ConfirmLine->next = NULL;
551 PrevLine = &(*PrevLine)->next;
554 if (ConfirmType == maxRoomNetCfg)
556 errmsg = "No active un/subscribe request found";
560 if (ConfirmType == subpending)
562 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
564 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
567 ConfirmType = digestrecp;
571 ConfirmType = listrecp;
575 "Mailing list: %s subscribed to %s with token %s\n",
576 ChrPtr(ConfirmLine->Value[0]),
580 FreeStrBuf(&ConfirmLine->Value[1]);
581 FreeStrBuf(&ConfirmLine->Value[2]);
582 FreeStrBuf(&ConfirmLine->Value[3]);
583 FreeStrBuf(&ConfirmLine->Value[4]);
584 ConfirmLine->nValues = 1;
586 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
591 /* whipe duplicate subscribe entry... */
592 OneRNCfg->changed = 1;
593 SaveChangedConfigs();
594 errmsg = "already subscribed";
597 else if (ConfirmType == unsubpending)
600 for (i = 0; i < 2; i++)
602 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
606 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
607 ChrPtr(Line->Value[0])))
611 *PrevLine = Line->next; /* Remove it from the list */
612 RemoveLine->next = NULL;
613 if (RemoveLine != NULL)
614 DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine);
618 PrevLine = &(*PrevLine)->next;
626 "Mailing list: %s unsubscribed to %s with token %s\n",
627 ChrPtr(ConfirmLine->Value[0]),
633 errmsg = "no subscriber found for this unsubscription request";
635 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
636 OneRNCfg->changed = 1;
637 SaveChangedConfigs();
640 end_critical_section(S_NETCONFIGS);
643 * Did we do anything useful today?
646 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
649 syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s",
651 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
659 * process subscribe/unsubscribe requests and confirmations
661 void cmd_subs(char *cmdbuf)
663 const char *Pos = NULL;
664 StrBuf *Segments[20];
667 memset(Segments, 0, sizeof(StrBuf*) * 20);
668 Segments[0] = NewStrBufPlain(cmdbuf, -1);
669 while ((Pos != StrBufNOTNULL) && (i < 20))
671 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
672 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
676 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
677 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
678 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
679 cprintf("%d Invalid subscription type '%s'\n",
680 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
683 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
686 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
687 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
689 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
690 do_confirm(&Segments[2], &Segments[3]);
693 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
698 FreeStrBuf(&Segments[i]);
706 CTDL_MODULE_INIT(listsub)
710 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
713 /* return our module name for the log */