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 < sizeof (ActiveSubscribers); i++)
84 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
87 if (!strcmp(ChrPtr(email),
88 ChrPtr(Line->Value[0])))
112 * Enter a subscription request
114 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
115 struct ctdlroom qrbuf;
121 const char *RoomMailAddress;
122 OneRoomNetCfg *OneRNCfg;
123 RoomNetCfgLine *Line;
124 long RoomMailAddressLen;
126 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
127 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
131 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
133 "does not accept subscribe/unsubscribe requests.\n",
134 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
139 * Make sure the requested address isn't already subscribed
141 begin_critical_section(S_NETCONFIGS);
143 RoomMailAddress = qrbuf.QRname;
144 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
145 if (OneRNCfg!=NULL) {
146 found_sub = CountThisSubscriber(OneRNCfg, *email);
147 if (StrLength(OneRNCfg->Sender) > 0)
148 RoomMailAddress = ChrPtr(OneRNCfg->Sender);
151 if (found_sub != 0) {
152 cprintf("%d '%s' is already subscribed to '%s'.\n",
153 ERROR + ALREADY_EXISTS,
157 end_critical_section(S_NETCONFIGS);
162 * Now add it to the config
165 RoomMailAddressLen = strlen(RoomMailAddress);
166 listsub_generate_token(token);
167 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
168 memset(Line, 0, sizeof(RoomNetCfgLine));
170 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
172 Line->Value[0] = *email; *email = NULL;
173 Line->Value[1] = *subtype; *subtype = NULL;
174 Line->Value[2] = NewStrBufPlain(token, -1);
175 Line->Value[3] = NewStrBufPlain(NULL, 10);
176 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
177 Line->Value[4] = *webpage; *webpage = NULL;
180 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
182 /* Generate and send the confirmation request */
183 UrlRoom = NewStrBuf();
184 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
186 cf_req = NewStrBufPlain(NULL, 2048);
187 StrBufAppendBufPlain(
189 HKEY("MIME-Version: 1.0\n"
190 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
192 "This is a multipart message in MIME format.\n"
194 "--__ctdlmultipart__\n"
195 "Content-type: text/plain\n"
197 "Someone (probably you) has submitted a request to subscribe\n"
199 StrBufAppendBuf(cf_req, Line->Value[0], 0);
201 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
202 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
204 StrBufAppendBufPlain(
206 HKEY("' mailing list.\n"
208 "Please go here to confirm this request:\n"
210 StrBufAppendBuf(cf_req, Line->Value[4], 0);
212 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
213 StrBufAppendBuf(cf_req, UrlRoom, 0);
215 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
216 StrBufAppendBuf(cf_req, Line->Value[2], 0);
218 StrBufAppendBufPlain(
220 HKEY("&cmd=confirm \n"
222 "If this request has been submitted in error and you do not\n"
223 "wish to receive the '"), 0);
224 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
226 StrBufAppendBufPlain(
228 HKEY("' mailing list, simply do nothing,\n"
229 "and you will not receive any further mailings.\n"
231 "--__ctdlmultipart__\n"
232 "Content-type: text/html\n"
235 "Someone (probably you) has submitted a request to subscribe\n"
237 StrBufAppendBuf(cf_req, Line->Value[0], 0);
239 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
241 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
243 StrBufAppendBufPlain(
245 HKEY("'</B> mailing list.<BR><BR>\n"
246 "Please click here to confirm this request:<BR>\n"
248 StrBufAppendBuf(cf_req, Line->Value[4], 0);
250 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
251 StrBufAppendBuf(cf_req, UrlRoom, 0);
253 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
254 StrBufAppendBuf(cf_req, Line->Value[2], 0);
256 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
257 StrBufAppendBuf(cf_req, Line->Value[4], 0);
259 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
260 StrBufAppendBuf(cf_req, UrlRoom, 0);
262 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
263 StrBufAppendBuf(cf_req, Line->Value[2], 0);
265 StrBufAppendBufPlain(
267 HKEY("&cmd=confirm</A><BR><BR>\n"
268 "If this request has been submitted in error and you do not\n"
269 "wish to receive the '"), 0);
270 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
272 StrBufAppendBufPlain(
274 HKEY("' mailing list, simply do nothing,\n"
275 "and you will not receive any further mailings.\n"
278 "--__ctdlmultipart__--\n"), 0);
280 end_critical_section(S_NETCONFIGS);
282 pcf_req = SmashStrBuf(&cf_req);
283 quickie_message( /* This delivers the message */
290 "Please confirm your list subscription"
293 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
295 FreeStrBuf(&UrlRoom);
300 * Enter an unsubscription request
302 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
303 struct ctdlroom qrbuf;
309 const char *RoomMailAddress;
310 OneRoomNetCfg *OneRNCfg;
311 RoomNetCfgLine *Line;
312 long RoomMailAddressLen;
314 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
315 cprintf("%d There is no list called '%s'\n",
316 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
320 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
322 "does not accept subscribe/unsubscribe requests.\n",
323 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
327 listsub_generate_token(token);
330 * Make sure there's actually a subscription there to remove
332 begin_critical_section(S_NETCONFIGS);
333 RoomMailAddress = qrbuf.QRname;
334 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
335 if (OneRNCfg!=NULL) {
336 found_sub = CountThisSubscriber(OneRNCfg, *email);
337 if (StrLength(OneRNCfg->Sender) > 0)
338 RoomMailAddress = ChrPtr(OneRNCfg->Sender);
341 if (found_sub == 0) {
342 cprintf("%d '%s' is not subscribed to '%s'.\n",
343 ERROR + NO_SUCH_USER,
347 end_critical_section(S_NETCONFIGS);
352 * Ok, now enter the unsubscribe-pending entry.
354 RoomMailAddressLen = strlen(RoomMailAddress);
355 listsub_generate_token(token);
356 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
357 memset(Line, 0, sizeof(RoomNetCfgLine));
359 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
361 Line->Value[0] = *email; *email = NULL;
362 Line->Value[2] = NewStrBufPlain(token, -1);
363 Line->Value[3] = NewStrBufPlain(NULL, 10);
364 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
365 Line->Value[4] = *webpage; *webpage = NULL;
368 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
370 /* Generate and send the confirmation request */
371 UrlRoom = NewStrBuf();
372 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
374 cf_req = NewStrBufPlain(NULL, 2048);
376 StrBufAppendBufPlain(
378 HKEY("MIME-Version: 1.0\n"
379 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
381 "This is a multipart message in MIME format.\n"
383 "--__ctdlmultipart__\n"
384 "Content-type: text/plain\n"
386 "Someone (probably you) has submitted a request to unsubscribe\n"
388 StrBufAppendBuf(cf_req, Line->Value[0], 0);
391 StrBufAppendBufPlain(
393 HKEY("> from the '"), 0);
394 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
396 StrBufAppendBufPlain(
398 HKEY("' mailing list.\n"
400 "Please go here to confirm this request:\n "), 0);
401 StrBufAppendBuf(cf_req, Line->Value[4], 0);
402 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
403 StrBufAppendBuf(cf_req, UrlRoom, 0);
404 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
405 StrBufAppendBuf(cf_req, Line->Value[2], 0);
407 StrBufAppendBufPlain(
409 HKEY("&cmd=confirm \n"
411 "If this request has been submitted in error and you do not\n"
412 "wish to unsubscribe from the '"), 0);
414 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
416 StrBufAppendBufPlain(
418 HKEY("' mailing list, simply do nothing,\n"
419 "and the request will not be processed.\n"
421 "--__ctdlmultipart__\n"
422 "Content-type: text/html\n"
425 "Someone (probably you) has submitted a request to unsubscribe\n"
427 StrBufAppendBuf(cf_req, Line->Value[0], 0);
429 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
430 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
432 StrBufAppendBufPlain(
434 HKEY("</B> mailing list.<BR><BR>\n"
435 "Please click here to confirm this request:<BR>\n"
437 StrBufAppendBuf(cf_req, Line->Value[4], 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[2], 0);
445 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
446 StrBufAppendBuf(cf_req, Line->Value[4], 0);
448 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
449 StrBufAppendBuf(cf_req, UrlRoom, 0);
451 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
452 StrBufAppendBuf(cf_req, Line->Value[2], 0);
455 StrBufAppendBufPlain(
457 HKEY("&cmd=confirm</A><BR><BR>\n"
458 "If this request has been submitted in error and you do not\n"
459 "wish to unsubscribe from the '"), 0);
460 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
462 StrBufAppendBufPlain(
464 HKEY("' mailing list, simply do nothing,\n"
465 "and the request will not be processed.\n"
468 "--__ctdlmultipart__--\n"), 0);
470 end_critical_section(S_NETCONFIGS);
472 pcf_req = SmashStrBuf(&cf_req);
473 quickie_message( /* This delivers the message */
480 "Please confirm your unsubscribe request"
483 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
487 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
490 * Confirm a subscribe/unsubscribe request.
492 void do_confirm(StrBuf **room, StrBuf **token) {
493 struct ctdlroom qrbuf;
494 OneRoomNetCfg *OneRNCfg;
495 RoomNetCfgLine *Line;
496 RoomNetCfgLine *ConfirmLine;
497 RoomNetCfgLine **PrevLine;
499 RoomNetCfg ConfirmType;
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 * Now start scanning this room's netconfig file for the
519 begin_critical_section(S_NETCONFIGS);
520 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
521 if (OneRNCfg==NULL) {
526 ConfirmType = maxRoomNetCfg;
527 for (i = 0; i < sizeof (ConfirmSubscribers); i++)
529 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
533 if (!strcasecmp(ChrPtr(*token),
534 ChrPtr(Line->Value[2])))
536 *PrevLine = Line->next; /* Remove it from the list */
538 ConfirmType = ConfirmSubscribers[i];
547 if (ConfirmType == subpending) {
548 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
551 ConfirmType = digestrecp;
555 ConfirmType = listrecp;
559 "Mailing list: %s subscribed to %s with token %s\n",
560 ChrPtr(ConfirmLine->Value[0]),
564 FreeStrBuf(&ConfirmLine->Value[1]);
565 FreeStrBuf(&ConfirmLine->Value[2]);
566 FreeStrBuf(&ConfirmLine->Value[3]);
567 FreeStrBuf(&ConfirmLine->Value[4]);
568 ConfirmLine->nValues = 5;
570 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
573 else if (ConfirmType == unsubpending) {
574 for (i = 0; i < sizeof (ActiveSubscribers); i++)
576 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
580 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
581 ChrPtr(Line->Value[2])))
583 *PrevLine = Line->next; /* Remove it from the list */
594 "Mailing list: %s unsubscribed to %s with token %s\n",
595 ChrPtr(ConfirmLine->Value[0]),
600 if (Line != NULL) DeleteGenericCfgLine(NULL/*TODO*/, &Line);
601 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
602 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, NULL);
606 end_critical_section(S_NETCONFIGS);
609 * Did we do anything useful today?
612 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
615 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
623 * process subscribe/unsubscribe requests and confirmations
625 void cmd_subs(char *cmdbuf)
627 const char *Pos = NULL;
628 StrBuf *Segments[20];
631 memset(Segments, 0, sizeof(StrBuf*) * 20);
632 Segments[0] = NewStrBufPlain(cmdbuf, -1);
633 while ((Pos != StrBufNOTNULL) && (i < 20))
635 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
636 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
639 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
640 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
641 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
642 cprintf("%d Invalid subscription type '%s'\n",
643 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
646 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
649 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
650 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
652 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
653 do_confirm(&Segments[2], &Segments[3]);
656 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
664 CTDL_MODULE_INIT(listsub)
668 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
671 /* return our module name for the log */