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 long RoomMailAddressLen;
114 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
115 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
119 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
121 "does not accept subscribe/unsubscribe requests.\n",
122 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
127 * Make sure the requested address isn't already subscribed
129 begin_critical_section(S_NETCONFIGS);
131 RoomMailAddress = qrbuf.QRname;
132 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
133 if (OneRNCfg!=NULL) {
134 found_sub = CountThisSubscriber(OneRNCfg, *email);
135 if (StrLength(OneRNCfg->Sender) > 0)
136 RoomMailAddress = ChrPtr(OneRNCfg->Sender);
139 if (found_sub != 0) {
140 cprintf("%d '%s' is already subscribed to '%s'.\n",
141 ERROR + ALREADY_EXISTS,
145 end_critical_section(S_NETCONFIGS);
150 * Now add it to the config
153 RoomMailAddressLen = strlen(RoomMailAddress);
154 listsub_generate_token(token);
155 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
156 memset(Line, 0, sizeof(RoomNetCfgLine));
158 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
160 Line->Value[0] = *email; *email = NULL;
161 Line->Value[1] = *subtype; *subtype = NULL;
162 Line->Value[2] = NewStrBufPlain(token, -1);
163 Line->Value[3] = NewStrBufPlain(NULL, 10);
164 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
165 Line->Value[4] = *webpage; *webpage = NULL;
168 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
170 /* Generate and send the confirmation request */
171 UrlRoom = NewStrBuf();
172 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
174 cf_req = NewStrBufPlain(NULL, 2048);
175 StrBufAppendBufPlain(
177 HKEY("MIME-Version: 1.0\n"
178 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
180 "This is a multipart message in MIME format.\n"
182 "--__ctdlmultipart__\n"
183 "Content-type: text/plain\n"
185 "Someone (probably you) has submitted a request to subscribe\n"
187 StrBufAppendBuf(cf_req, Line->Value[0], 0);
189 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
190 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
192 StrBufAppendBufPlain(
194 HKEY("' mailing list.\n"
196 "Please go here to confirm this request:\n"
198 StrBufAppendBuf(cf_req, Line->Value[4], 0);
200 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
201 StrBufAppendBuf(cf_req, UrlRoom, 0);
203 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
204 StrBufAppendBuf(cf_req, Line->Value[2], 0);
206 StrBufAppendBufPlain(
208 HKEY("&cmd=confirm \n"
210 "If this request has been submitted in error and you do not\n"
211 "wish to receive the '"), 0);
212 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
214 StrBufAppendBufPlain(
216 HKEY("' mailing list, simply do nothing,\n"
217 "and you will not receive any further mailings.\n"
219 "--__ctdlmultipart__\n"
220 "Content-type: text/html\n"
223 "Someone (probably you) has submitted a request to subscribe\n"
225 StrBufAppendBuf(cf_req, Line->Value[0], 0);
227 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
229 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
231 StrBufAppendBufPlain(
233 HKEY("'</B> mailing list.<BR><BR>\n"
234 "Please click here to confirm this request:<BR>\n"
236 StrBufAppendBuf(cf_req, Line->Value[4], 0);
238 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
239 StrBufAppendBuf(cf_req, UrlRoom, 0);
241 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
242 StrBufAppendBuf(cf_req, Line->Value[2], 0);
244 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
245 StrBufAppendBuf(cf_req, Line->Value[4], 0);
247 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
248 StrBufAppendBuf(cf_req, UrlRoom, 0);
250 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
251 StrBufAppendBuf(cf_req, Line->Value[2], 0);
253 StrBufAppendBufPlain(
255 HKEY("&cmd=confirm</A><BR><BR>\n"
256 "If this request has been submitted in error and you do not\n"
257 "wish to receive the '"), 0);
258 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
260 StrBufAppendBufPlain(
262 HKEY("' mailing list, simply do nothing,\n"
263 "and you will not receive any further mailings.\n"
266 "--__ctdlmultipart__--\n"), 0);
268 end_critical_section(S_NETCONFIGS);
270 pcf_req = SmashStrBuf(&cf_req);
271 quickie_message( /* This delivers the message */
278 "Please confirm your list subscription"
281 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
283 FreeStrBuf(&UrlRoom);
288 * Enter an unsubscription request
290 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
291 struct ctdlroom qrbuf;
297 const char *RoomMailAddress;
298 OneRoomNetCfg *OneRNCfg;
299 RoomNetCfgLine *Line;
300 long RoomMailAddressLen;
302 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
303 cprintf("%d There is no list called '%s'\n",
304 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
308 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
310 "does not accept subscribe/unsubscribe requests.\n",
311 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
315 listsub_generate_token(token);
318 * Make sure there's actually a subscription there to remove
320 begin_critical_section(S_NETCONFIGS);
321 RoomMailAddress = qrbuf.QRname;
322 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
323 if (OneRNCfg!=NULL) {
324 found_sub = CountThisSubscriber(OneRNCfg, *email);
325 if (StrLength(OneRNCfg->Sender) > 0)
326 RoomMailAddress = ChrPtr(OneRNCfg->Sender);
329 if (found_sub == 0) {
330 cprintf("%d '%s' is not subscribed to '%s'.\n",
331 ERROR + NO_SUCH_USER,
335 end_critical_section(S_NETCONFIGS);
340 * Ok, now enter the unsubscribe-pending entry.
342 RoomMailAddressLen = strlen(RoomMailAddress);
343 listsub_generate_token(token);
344 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
345 memset(Line, 0, sizeof(RoomNetCfgLine));
347 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
349 Line->Value[0] = *email; *email = NULL;
350 Line->Value[2] = NewStrBufPlain(token, -1);
351 Line->Value[3] = NewStrBufPlain(NULL, 10);
352 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
353 Line->Value[4] = *webpage; *webpage = NULL;
356 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
358 /* Generate and send the confirmation request */
359 UrlRoom = NewStrBuf();
360 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
362 cf_req = NewStrBufPlain(NULL, 2048);
364 StrBufAppendBufPlain(
366 HKEY("MIME-Version: 1.0\n"
367 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
369 "This is a multipart message in MIME format.\n"
371 "--__ctdlmultipart__\n"
372 "Content-type: text/plain\n"
374 "Someone (probably you) has submitted a request to unsubscribe\n"
376 StrBufAppendBuf(cf_req, Line->Value[0], 0);
379 StrBufAppendBufPlain(
381 HKEY("> from the '"), 0);
382 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
384 StrBufAppendBufPlain(
386 HKEY("' mailing list.\n"
388 "Please go here to confirm this request:\n "), 0);
389 StrBufAppendBuf(cf_req, Line->Value[4], 0);
390 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
391 StrBufAppendBuf(cf_req, UrlRoom, 0);
392 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
393 StrBufAppendBuf(cf_req, Line->Value[2], 0);
395 StrBufAppendBufPlain(
397 HKEY("&cmd=confirm \n"
399 "If this request has been submitted in error and you do not\n"
400 "wish to unsubscribe from the '"), 0);
402 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
404 StrBufAppendBufPlain(
406 HKEY("' mailing list, simply do nothing,\n"
407 "and the request will not be processed.\n"
409 "--__ctdlmultipart__\n"
410 "Content-type: text/html\n"
413 "Someone (probably you) has submitted a request to unsubscribe\n"
415 StrBufAppendBuf(cf_req, Line->Value[0], 0);
417 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
418 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
420 StrBufAppendBufPlain(
422 HKEY("</B> mailing list.<BR><BR>\n"
423 "Please click here to confirm this request:<BR>\n"
425 StrBufAppendBuf(cf_req, Line->Value[4], 0);
427 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
428 StrBufAppendBuf(cf_req, UrlRoom, 0);
430 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
431 StrBufAppendBuf(cf_req, Line->Value[2], 0);
433 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
434 StrBufAppendBuf(cf_req, Line->Value[4], 0);
436 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
437 StrBufAppendBuf(cf_req, UrlRoom, 0);
439 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
440 StrBufAppendBuf(cf_req, Line->Value[2], 0);
443 StrBufAppendBufPlain(
445 HKEY("&cmd=confirm</A><BR><BR>\n"
446 "If this request has been submitted in error and you do not\n"
447 "wish to unsubscribe from the '"), 0);
448 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
450 StrBufAppendBufPlain(
452 HKEY("' mailing list, simply do nothing,\n"
453 "and the request will not be processed.\n"
456 "--__ctdlmultipart__--\n"), 0);
458 end_critical_section(S_NETCONFIGS);
460 pcf_req = SmashStrBuf(&cf_req);
461 quickie_message( /* This delivers the message */
468 "Please confirm your unsubscribe request"
471 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
475 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
478 * Confirm a subscribe/unsubscribe request.
480 void do_confirm(StrBuf **room, StrBuf **token) {
481 struct ctdlroom qrbuf;
482 OneRoomNetCfg *OneRNCfg;
483 RoomNetCfgLine *Line;
484 RoomNetCfgLine *ConfirmLine;
485 RoomNetCfgLine **PrevLine;
487 RoomNetCfg ConfirmType;
490 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
491 cprintf("%d There is no list called '%s'\n",
492 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
496 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
498 "does not accept subscribe/unsubscribe requests.\n",
499 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
504 * Now start scanning this room's netconfig file for the
507 begin_critical_section(S_NETCONFIGS);
508 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
509 if (OneRNCfg==NULL) {
514 ConfirmType = maxRoomNetCfg;
515 for (i = 0; i < 2; i++)
517 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
521 if (!strcasecmp(ChrPtr(*token),
522 ChrPtr(Line->Value[2])))
524 *PrevLine = Line->next; /* Remove it from the list */
526 ConfirmType = ConfirmSubscribers[i];
535 if (ConfirmType == subpending) {
536 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
539 ConfirmType = digestrecp;
543 ConfirmType = listrecp;
547 "Mailing list: %s subscribed to %s with token %s\n",
548 ChrPtr(ConfirmLine->Value[0]),
552 FreeStrBuf(&ConfirmLine->Value[1]);
553 FreeStrBuf(&ConfirmLine->Value[2]);
554 FreeStrBuf(&ConfirmLine->Value[3]);
555 FreeStrBuf(&ConfirmLine->Value[4]);
556 ConfirmLine->nValues = 5;
558 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
561 else if (ConfirmType == unsubpending) {
562 for (i = 0; i < 2; i++)
564 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
568 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
569 ChrPtr(Line->Value[2])))
571 *PrevLine = Line->next; /* Remove it from the list */
582 "Mailing list: %s unsubscribed to %s with token %s\n",
583 ChrPtr(ConfirmLine->Value[0]),
588 if (Line != NULL) DeleteGenericCfgLine(NULL/*TODO*/, &Line);
589 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
590 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, NULL);
594 end_critical_section(S_NETCONFIGS);
597 * Did we do anything useful today?
600 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
603 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
611 * process subscribe/unsubscribe requests and confirmations
613 void cmd_subs(char *cmdbuf)
615 const char *Pos = NULL;
616 StrBuf *Segments[20];
619 memset(Segments, 0, sizeof(StrBuf*) * 20);
620 Segments[0] = NewStrBufPlain(cmdbuf, -1);
621 while ((Pos != StrBufNOTNULL) && (i < 20))
623 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
624 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
628 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
629 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
630 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
631 cprintf("%d Invalid subscription type '%s'\n",
632 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
635 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
638 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
639 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
641 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
642 do_confirm(&Segments[2], &Segments[3]);
645 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
650 FreeStrBuf(&Segments[i]);
658 CTDL_MODULE_INIT(listsub)
662 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
665 /* return our module name for the log */