1 // This module handles self-service subscription/unsubscription to mail lists.
3 // Copyright (c) 2002-2022 by the citadel.org team
5 // This program is open source software. It runs great on the
6 // Linux operating system (and probably elsewhere). You can use,
7 // copy, and run it under the terms of the GNU General Public
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 #include "../../sysdep.h"
24 #include <sys/types.h>
31 #include <libcitadel.h>
32 #include "../../citadel.h"
33 #include "../../server.h"
34 #include "../../citserver.h"
35 #include "../../support.h"
36 #include "../../config.h"
37 #include "../../user_ops.h"
38 #include "../../database.h"
39 #include "../../msgbase.h"
40 #include "../../internet_addressing.h"
41 #include "../../clientsocket.h"
42 #include "../../ctdl_module.h"
45 enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
51 // The confirmation token will be generated by combining the room name and email address with the host key,
52 // and then generating an encrypted hash of that string. The encrypted hash is included as part of the
54 void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
55 char string_to_hash[1024];
59 snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
60 memset(&cd, 0, sizeof cd);
62 strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
64 for (ptr=token_buf; *ptr; ++ptr) {
65 if (!isalnum((char)*ptr)) *ptr='X';
70 // This generates an email with a link the user clicks to confirm a list subscription.
71 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
72 // We need a URL-safe representation of the room name
73 char urlroom[ROOMNAMELEN+10];
74 urlesc(urlroom, sizeof(urlroom), roomname);
76 char from_address[1024];
77 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
80 snprintf(emailtext, sizeof emailtext,
82 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
84 "This is a multipart message in MIME format.\n"
86 "--__ctdlmultipart__\n"
87 "Content-type: text/plain\n"
89 "Someone (probably you) has submitted a request to subscribe\n"
90 "<%s> to the <%s> mailing list.\n"
92 "Please go here to confirm this request:\n"
93 "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
95 "If this request has been submitted in error and you do not\n"
96 "wish to receive the <%s> mailing list, simply do nothing,\n"
97 "and you will not receive any further mailings.\n"
99 "--__ctdlmultipart__\n"
100 "Content-type: text/html\n"
102 "<html><body><p>Someone (probably you) has submitted a request to subscribe "
103 "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
104 "<p>Please go here to confirm this request:</p>"
105 "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
106 "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
107 "<p>If this request has been submitted in error and you do not "
108 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
109 "and you will not receive any further mailings.</p>"
112 "--__ctdlmultipart__--\n"
115 url, emailaddr, urlroom, confirmation_token,
119 url, emailaddr, urlroom, confirmation_token,
120 url, emailaddr, urlroom, confirmation_token,
124 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
125 cprintf("%d confirmation email sent\n", CIT_OK);
129 // This generates an email with a link the user clicks to confirm a list unsubscription.
130 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
131 // We need a URL-safe representation of the room name
132 char urlroom[ROOMNAMELEN+10];
133 urlesc(urlroom, sizeof(urlroom), roomname);
135 char from_address[1024];
136 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
139 snprintf(emailtext, sizeof emailtext,
140 "MIME-Version: 1.0\n"
141 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
143 "This is a multipart message in MIME format.\n"
145 "--__ctdlmultipart__\n"
146 "Content-type: text/plain\n"
148 "Someone (probably you) has submitted a request to unsubscribe\n"
149 "<%s> from the <%s> mailing list.\n"
151 "Please go here to confirm this request:\n"
152 "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
154 "If this request has been submitted in error and you still\n"
155 "wish to receive the <%s> mailing list, simply do nothing,\n"
156 "and you will remain subscribed.\n"
158 "--__ctdlmultipart__\n"
159 "Content-type: text/html\n"
161 "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
162 "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
163 "<p>Please go here to confirm this request:</p>"
164 "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
165 "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
166 "<p>If this request has been submitted in error and you still "
167 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
168 "and you will remain subscribed.</p>"
171 "--__ctdlmultipart__--\n"
174 url, emailaddr, urlroom, confirmation_token,
178 url, emailaddr, urlroom, confirmation_token,
179 url, emailaddr, urlroom, confirmation_token,
183 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
184 cprintf("%d confirmation email sent\n", CIT_OK);
188 // Confirm a list subscription or unsubscription
189 void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
192 int config_lines = 0;
193 char *oldnetconfig, *newnetconfig;
195 // During opt #1, the server generated a persistent confirmation token for the user+room combination.
196 // Let's see if the user has supplied the same token during opt #2.
197 if (strcmp(generated_token, supplied_token)) {
198 cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
202 // If the generated token matches the supplied token, the request is authentic. Do what it says.
204 // Load the room's network configuration...
205 oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
207 oldnetconfig = strdup("");
210 // The new netconfig begins with an empty buffer...
211 begin_critical_section(S_NETCONFIGS);
212 newnetconfig = malloc(strlen(oldnetconfig) + 1024);
215 // Load the config lines in one by one, skipping any that reference this subscriber. Also remove blank lines.
216 config_lines = num_tokens(oldnetconfig, '\n');
217 for (i=0; i<config_lines; ++i) {
219 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
220 extract_token(buf_email, buf, 1, '|', sizeof buf_email);
221 if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
222 sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
226 // We have now removed all lines containing the subscriber's email address. This deletes any pending requests.
227 // If this was an unsubscribe operation, they're now gone from the list.
228 // But if this was a subscribe operation, we now need to add them.
229 if (cmd == SUBSCRIBE) {
230 sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
233 // write it back to disk
234 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
235 end_critical_section(S_NETCONFIGS);
238 cprintf("%d The pending request was confirmed.\n", CIT_OK);
242 // process subscribe/unsubscribe requests and confirmations
243 void cmd_lsub(char *cmdbuf) {
245 char roomname[ROOMNAMELEN];
246 char emailaddr[1024];
248 char generated_token[128];
249 char supplied_token[128];
251 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
252 extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
253 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
254 extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
255 extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token); // token 4 is the token supplied by the caller
257 // First confirm that the caller is referencing a room that actually exists.
258 if (CtdlGetRoom(&CC->room, roomname) != 0) {
259 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
263 if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
264 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
268 // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
269 generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
271 // Now parse the command.
272 if (!strcasecmp(cmd, "subscribe")) {
273 send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
276 else if (!strcasecmp(cmd, "unsubscribe")) {
277 send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
280 else if (!strcasecmp(cmd, "confirm_subscribe")) {
281 do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
284 else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
285 do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
288 else { // sorry man, I can't deal with that
289 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
297 char *ctdl_module_init_listsub(void) {
299 CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
302 /* return our module name for the log */