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"
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) {
61 /* Theo, please sit down and shut up. This key doesn't have to be
62 * tinfoil-hat secure, it just needs to be reasonably unguessable
65 sprintf(sourcebuf, "%lx",
66 (long) (++seq + getpid() + time(NULL))
69 /* Convert it to base64 so it looks cool */
70 CtdlEncodeBase64(buf, sourcebuf, strlen(sourcebuf), 0);
73 const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp};
75 int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email)
81 for (i = 0; i < 2; i++)
83 Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]];
86 if (!strcmp(ChrPtr(email),
87 ChrPtr(Line->Value[0])))
99 * Enter a subscription request
101 void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) {
102 struct ctdlroom qrbuf;
108 const char *RoomMailAddress;
109 OneRoomNetCfg *OneRNCfg;
110 RoomNetCfgLine *Line;
111 const char *EmailSender = NULL;
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 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
140 if (found_sub != 0) {
141 cprintf("%d '%s' is already subscribed to '%s'.\n",
142 ERROR + ALREADY_EXISTS,
146 end_critical_section(S_NETCONFIGS);
151 * Now add it to the config
154 RoomMailAddressLen = strlen(RoomMailAddress);
155 listsub_generate_token(token);
156 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
157 memset(Line, 0, sizeof(RoomNetCfgLine));
159 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5);
161 Line->Value[0] = NewStrBufDup(*email);
162 Line->Value[1] = *subtype; *subtype = NULL;
163 Line->Value[2] = NewStrBufPlain(token, -1);
164 Line->Value[3] = NewStrBufPlain(NULL, 10);
165 StrBufPrintf(Line->Value[3], "%ld", time(NULL));
166 Line->Value[4] = *webpage; *webpage = NULL;
169 AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line);
171 /* Generate and send the confirmation request */
172 UrlRoom = NewStrBuf();
173 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
175 cf_req = NewStrBufPlain(NULL, 2048);
176 StrBufAppendBufPlain(
178 HKEY("MIME-Version: 1.0\n"
179 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
181 "This is a multipart message in MIME format.\n"
183 "--__ctdlmultipart__\n"
184 "Content-type: text/plain\n"
186 "Someone (probably you) has submitted a request to subscribe\n"
188 StrBufAppendBuf(cf_req, Line->Value[0], 0);
190 StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0);
191 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
193 StrBufAppendBufPlain(
195 HKEY("' mailing list.\n"
197 "Please go here to confirm this request:\n"
199 StrBufAppendBuf(cf_req, Line->Value[4], 0);
201 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
202 StrBufAppendBuf(cf_req, UrlRoom, 0);
204 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
205 StrBufAppendBuf(cf_req, Line->Value[2], 0);
207 StrBufAppendBufPlain(
209 HKEY("&cmd=confirm \n"
211 "If this request has been submitted in error and you do not\n"
212 "wish to receive the '"), 0);
213 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
215 StrBufAppendBufPlain(
217 HKEY("' mailing list, simply do nothing,\n"
218 "and you will not receive any further mailings.\n"
220 "--__ctdlmultipart__\n"
221 "Content-type: text/html\n"
224 "Someone (probably you) has submitted a request to subscribe\n"
226 StrBufAppendBuf(cf_req, Line->Value[0], 0);
228 StrBufAppendBufPlain(cf_req, HKEY( "> to the <B>"), 0);
230 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
232 StrBufAppendBufPlain(
234 HKEY("'</B> mailing list.<BR><BR>\n"
235 "Please click here to confirm this request:<BR>\n"
237 StrBufAppendBuf(cf_req, Line->Value[4], 0);
239 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
240 StrBufAppendBuf(cf_req, UrlRoom, 0);
242 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
243 StrBufAppendBuf(cf_req, Line->Value[2], 0);
245 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
246 StrBufAppendBuf(cf_req, Line->Value[4], 0);
248 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
249 StrBufAppendBuf(cf_req, UrlRoom, 0);
251 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
252 StrBufAppendBuf(cf_req, Line->Value[2], 0);
254 StrBufAppendBufPlain(
256 HKEY("&cmd=confirm</A><BR><BR>\n"
257 "If this request has been submitted in error and you do not\n"
258 "wish to receive the '"), 0);
259 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
261 StrBufAppendBufPlain(
263 HKEY("' mailing list, simply do nothing,\n"
264 "and you will not receive any further mailings.\n"
267 "--__ctdlmultipart__--\n"), 0);
269 end_critical_section(S_NETCONFIGS);
271 pcf_req = SmashStrBuf(&cf_req);
272 quickie_message( /* This delivers the message */
279 "Please confirm your list subscription"
282 cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK);
284 FreeStrBuf(&UrlRoom);
289 * Enter an unsubscription request
291 void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) {
292 struct ctdlroom qrbuf;
293 const char *EmailSender = NULL;
299 const char *RoomMailAddress;
300 OneRoomNetCfg *OneRNCfg;
301 RoomNetCfgLine *Line;
302 long RoomMailAddressLen;
304 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
305 cprintf("%d There is no list called '%s'\n",
306 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
310 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
312 "does not accept subscribe/unsubscribe requests.\n",
313 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
317 listsub_generate_token(token);
320 * Make sure there's actually a subscription there to remove
322 begin_critical_section(S_NETCONFIGS);
323 RoomMailAddress = qrbuf.QRname;
324 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
325 if (OneRNCfg!=NULL) {
326 found_sub = CountThisSubscriber(OneRNCfg, *email);
327 if (StrLength(OneRNCfg->Sender) > 0)
328 EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender);
331 if (found_sub == 0) {
332 cprintf("%d '%s' is not subscribed to '%s'.\n",
333 ERROR + NO_SUCH_USER,
337 end_critical_section(S_NETCONFIGS);
342 * Ok, now enter the unsubscribe-pending entry.
344 RoomMailAddressLen = strlen(RoomMailAddress);
345 listsub_generate_token(token);
346 Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine));
347 memset(Line, 0, sizeof(RoomNetCfgLine));
349 Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4);
351 Line->Value[0] = NewStrBufDup(*email);
352 Line->Value[1] = NewStrBufPlain(token, -1);
353 Line->Value[2] = NewStrBufPlain(NULL, 10);
354 StrBufPrintf(Line->Value[2], "%ld", time(NULL));
355 Line->Value[3] = *webpage; *webpage = NULL;
358 AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line);
360 /* Generate and send the confirmation request */
361 UrlRoom = NewStrBuf();
362 StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname);
364 cf_req = NewStrBufPlain(NULL, 2048);
366 StrBufAppendBufPlain(
368 HKEY("MIME-Version: 1.0\n"
369 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
371 "This is a multipart message in MIME format.\n"
373 "--__ctdlmultipart__\n"
374 "Content-type: text/plain\n"
376 "Someone (probably you) has submitted a request to unsubscribe\n"
378 StrBufAppendBuf(cf_req, Line->Value[0], 0);
381 StrBufAppendBufPlain(
383 HKEY("> from the '"), 0);
384 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
386 StrBufAppendBufPlain(
388 HKEY("' mailing list.\n"
390 "Please go here to confirm this request:\n "), 0);
391 StrBufAppendBuf(cf_req, Line->Value[3], 0);
392 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
393 StrBufAppendBuf(cf_req, UrlRoom, 0);
394 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
395 StrBufAppendBuf(cf_req, Line->Value[1], 0);
397 StrBufAppendBufPlain(
399 HKEY("&cmd=confirm \n"
401 "If this request has been submitted in error and you do not\n"
402 "wish to unsubscribe from the '"), 0);
404 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
406 StrBufAppendBufPlain(
408 HKEY("' mailing list, simply do nothing,\n"
409 "and the request will not be processed.\n"
411 "--__ctdlmultipart__\n"
412 "Content-type: text/html\n"
415 "Someone (probably you) has submitted a request to unsubscribe\n"
417 StrBufAppendBuf(cf_req, Line->Value[0], 0);
419 StrBufAppendBufPlain(cf_req, HKEY("> from the <B>"), 0);
420 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
422 StrBufAppendBufPlain(
424 HKEY("</B> mailing list.<BR><BR>\n"
425 "Please click here to confirm this request:<BR>\n"
427 StrBufAppendBuf(cf_req, Line->Value[3], 0);
429 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
430 StrBufAppendBuf(cf_req, UrlRoom, 0);
432 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
433 StrBufAppendBuf(cf_req, Line->Value[1], 0);
435 StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0);
436 StrBufAppendBuf(cf_req, Line->Value[3], 0);
438 StrBufAppendBufPlain(cf_req, HKEY("?room="), 0);
439 StrBufAppendBuf(cf_req, UrlRoom, 0);
441 StrBufAppendBufPlain(cf_req, HKEY("&token="), 0);
442 StrBufAppendBuf(cf_req, Line->Value[1], 0);
445 StrBufAppendBufPlain(
447 HKEY("&cmd=confirm</A><BR><BR>\n"
448 "If this request has been submitted in error and you do not\n"
449 "wish to unsubscribe from the '"), 0);
450 StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0);
452 StrBufAppendBufPlain(
454 HKEY("' mailing list, simply do nothing,\n"
455 "and the request will not be processed.\n"
458 "--__ctdlmultipart__--\n"), 0);
460 end_critical_section(S_NETCONFIGS);
462 pcf_req = SmashStrBuf(&cf_req);
463 quickie_message( /* This delivers the message */
470 "Please confirm your unsubscribe request"
474 FreeStrBuf(&UrlRoom);
475 cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK);
479 const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending};
482 * Confirm a subscribe/unsubscribe request.
484 void do_confirm(StrBuf **room, StrBuf **token) {
485 struct ctdlroom qrbuf;
486 OneRoomNetCfg *OneRNCfg;
487 RoomNetCfgLine *Line;
488 RoomNetCfgLine *ConfirmLine = NULL;
489 RoomNetCfgLine *RemoveLine = NULL;
490 RoomNetCfgLine **PrevLine;
492 RoomNetCfg ConfirmType;
493 const char *errmsg = "";
496 if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) {
497 cprintf("%d There is no list called '%s'\n",
498 ERROR + ROOM_NOT_FOUND, ChrPtr(*room));
502 if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) {
504 "does not accept subscribe/unsubscribe requests.\n",
505 ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname);
510 if (StrLength(*token) == 0) {
511 cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE);
515 * Now start scanning this room's netconfig file for the
518 begin_critical_section(S_NETCONFIGS);
519 OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber);
521 ConfirmType = maxRoomNetCfg;
524 errmsg = "no networking config found";
526 else for (i = 0; i < 2; i++)
530 if (ConfirmSubscribers[i] == subpending)
534 PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]];
538 if (!strcasecmp(ChrPtr(*token),
539 ChrPtr(Line->Value[offset])))
542 *PrevLine = Line->next; /* Remove it from the list */
543 ConfirmType = ConfirmSubscribers[i];
544 ConfirmLine->next = NULL;
550 PrevLine = &(*PrevLine)->next;
553 if (ConfirmType == maxRoomNetCfg)
555 errmsg = "No active un/subscribe request found";
559 if (ConfirmType == subpending)
561 if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0)
563 if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]),
566 ConfirmType = digestrecp;
570 ConfirmType = listrecp;
574 "Mailing list: %s subscribed to %s with token %s\n",
575 ChrPtr(ConfirmLine->Value[0]),
579 FreeStrBuf(&ConfirmLine->Value[1]);
580 FreeStrBuf(&ConfirmLine->Value[2]);
581 FreeStrBuf(&ConfirmLine->Value[3]);
582 FreeStrBuf(&ConfirmLine->Value[4]);
583 ConfirmLine->nValues = 1;
585 AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine);
590 /* whipe duplicate subscribe entry... */
591 OneRNCfg->changed = 1;
592 SaveChangedConfigs();
593 errmsg = "already subscribed";
596 else if (ConfirmType == unsubpending)
599 for (i = 0; i < 2; i++)
601 PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]];
605 if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]),
606 ChrPtr(Line->Value[0])))
610 *PrevLine = Line->next; /* Remove it from the list */
611 RemoveLine->next = NULL;
612 if (RemoveLine != NULL)
613 DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine);
617 PrevLine = &(*PrevLine)->next;
625 "Mailing list: %s unsubscribed to %s with token %s\n",
626 ChrPtr(ConfirmLine->Value[0]),
632 errmsg = "no subscriber found for this unsubscription request";
634 DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine);
635 OneRNCfg->changed = 1;
636 SaveChangedConfigs();
639 end_critical_section(S_NETCONFIGS);
642 * Did we do anything useful today?
645 cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success);
648 syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s",
650 cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE);
658 * process subscribe/unsubscribe requests and confirmations
660 void cmd_subs(char *cmdbuf)
662 const char *Pos = NULL;
663 StrBuf *Segments[20];
666 memset(Segments, 0, sizeof(StrBuf*) * 20);
667 Segments[0] = NewStrBufPlain(cmdbuf, -1);
668 while ((Pos != StrBufNOTNULL) && (i < 20))
670 Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0]));
671 StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|');
675 if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) {
676 if ( (strcasecmp(ChrPtr(Segments[4]), "list"))
677 && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) {
678 cprintf("%d Invalid subscription type '%s'\n",
679 ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4]));
682 do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]);
685 else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) {
686 do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]);
688 else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) {
689 do_confirm(&Segments[2], &Segments[3]);
692 cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE);
697 FreeStrBuf(&Segments[i]);
705 CTDL_MODULE_INIT(listsub)
709 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
712 /* return our module name for the log */