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>
30 #include <libcitadel.h>
31 #include "../../citadel_defs.h"
32 #include "../../server.h"
33 #include "../../citserver.h"
34 #include "../../support.h"
35 #include "../../config.h"
36 #include "../../user_ops.h"
37 #include "../../database.h"
38 #include "../../msgbase.h"
39 #include "../../internet_addressing.h"
40 #include "../../clientsocket.h"
41 #include "../../ctdl_module.h"
44 enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
50 // The confirmation token will be generated by combining the room name and email address with the host key,
51 // and then generating a one-way hash of that string. The hash is included as part of the confirmation link.
52 void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
53 char string_to_hash[1024];
55 snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
56 snprintf(token_buf, token_buf_len, "%lx", FourHash(string_to_hash, strlen(string_to_hash)));
60 // This generates an email with a link the user clicks to confirm a list subscription.
61 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
62 // We need a URL-safe representation of the room name
63 char urlroom[ROOMNAMELEN+10];
64 urlesc(urlroom, sizeof(urlroom), roomname);
66 char from_address[1024];
67 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
70 snprintf(emailtext, sizeof emailtext,
72 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
74 "This is a multipart message in MIME format.\n"
76 "--__ctdlmultipart__\n"
77 "Content-type: text/plain\n"
79 "Someone (probably you) has submitted a request to subscribe\n"
80 "<%s> to the <%s> mailing list.\n"
82 "Please go here to confirm this request:\n"
83 "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
85 "If this request has been submitted in error and you do not\n"
86 "wish to receive the <%s> mailing list, simply do nothing,\n"
87 "and you will not receive any further mailings.\n"
89 "--__ctdlmultipart__\n"
90 "Content-type: text/html\n"
92 "<html><body><p>Someone (probably you) has submitted a request to subscribe "
93 "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
94 "<p>Please go here to confirm this request:</p>"
95 "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
96 "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
97 "<p>If this request has been submitted in error and you do not "
98 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
99 "and you will not receive any further mailings.</p>"
102 "--__ctdlmultipart__--\n"
105 url, emailaddr, urlroom, confirmation_token,
109 url, emailaddr, urlroom, confirmation_token,
110 url, emailaddr, urlroom, confirmation_token,
114 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
115 cprintf("%d confirmation email sent\n", CIT_OK);
119 // This generates an email with a link the user clicks to confirm a list unsubscription.
120 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
121 // We need a URL-safe representation of the room name
122 char urlroom[ROOMNAMELEN+10];
123 urlesc(urlroom, sizeof(urlroom), roomname);
125 char from_address[1024];
126 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
129 snprintf(emailtext, sizeof emailtext,
130 "MIME-Version: 1.0\n"
131 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
133 "This is a multipart message in MIME format.\n"
135 "--__ctdlmultipart__\n"
136 "Content-type: text/plain\n"
138 "Someone (probably you) has submitted a request to unsubscribe\n"
139 "<%s> from the <%s> mailing list.\n"
141 "Please go here to confirm this request:\n"
142 "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
144 "If this request has been submitted in error and you still\n"
145 "wish to receive the <%s> mailing list, simply do nothing,\n"
146 "and you will remain subscribed.\n"
148 "--__ctdlmultipart__\n"
149 "Content-type: text/html\n"
151 "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
152 "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
153 "<p>Please go here to confirm this request:</p>"
154 "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
155 "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
156 "<p>If this request has been submitted in error and you still "
157 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
158 "and you will remain subscribed.</p>"
161 "--__ctdlmultipart__--\n"
164 url, emailaddr, urlroom, confirmation_token,
168 url, emailaddr, urlroom, confirmation_token,
169 url, emailaddr, urlroom, confirmation_token,
173 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
174 cprintf("%d confirmation email sent\n", CIT_OK);
178 // Confirm a list subscription or unsubscription
179 void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
182 int config_lines = 0;
183 char *oldnetconfig, *newnetconfig;
185 // During opt #1, the server generated a persistent confirmation token for the user+room combination.
186 // Let's see if the user has supplied the same token during opt #2.
187 if (strcmp(generated_token, supplied_token)) {
188 cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
192 // If the generated token matches the supplied token, the request is authentic. Do what it says.
194 // Load the room's network configuration...
195 oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
197 oldnetconfig = strdup("");
200 // The new netconfig begins with an empty buffer...
201 begin_critical_section(S_NETCONFIGS);
202 newnetconfig = malloc(strlen(oldnetconfig) + 1024);
205 // Load the config lines in one by one, skipping any that reference this subscriber. Also remove blank lines.
206 config_lines = num_tokens(oldnetconfig, '\n');
207 for (i=0; i<config_lines; ++i) {
209 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
210 extract_token(buf_email, buf, 1, '|', sizeof buf_email);
211 if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
212 sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
216 // We have now removed all lines containing the subscriber's email address. This deletes any pending requests.
217 // If this was an unsubscribe operation, they're now gone from the list.
218 // But if this was a subscribe operation, we now need to add them.
219 if (cmd == SUBSCRIBE) {
220 sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
223 // write it back to disk
224 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
225 end_critical_section(S_NETCONFIGS);
228 cprintf("%d The pending request was confirmed.\n", CIT_OK);
232 // process subscribe/unsubscribe requests and confirmations
233 void cmd_lsub(char *cmdbuf) {
235 char roomname[ROOMNAMELEN];
236 char emailaddr[1024];
238 char generated_token[128];
239 char supplied_token[128];
241 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
242 extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
243 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
244 extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
245 extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token); // token 4 is the token supplied by the caller
247 // First confirm that the caller is referencing a room that actually exists.
248 if (CtdlGetRoom(&CC->room, roomname) != 0) {
249 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
253 if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
254 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
258 // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
259 generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
261 // Now parse the command.
262 if (!strcasecmp(cmd, "subscribe")) {
263 send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
266 else if (!strcasecmp(cmd, "unsubscribe")) {
267 send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
270 else if (!strcasecmp(cmd, "confirm_subscribe")) {
271 do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
274 else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
275 do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
278 else { // sorry man, I can't deal with that
279 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
284 // Initialization function, called from modules_init.c
285 char *ctdl_module_init_listsub(void) {
287 CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
290 /* return our module name for the log */