citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

Loading...
Searching...
No Matches
serv_listsub.c
Go to the documentation of this file.
1// This module handles self-service subscription/unsubscription to mail lists.
2//
3// Copyright (c) 2002-2022 by the citadel.org team
4//
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
8// License version 3.
9//
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.
14
15#include "../../sysdep.h"
16#include <stdlib.h>
17#include <unistd.h>
18#include <stdio.h>
19#include <fcntl.h>
20#include <ctype.h>
21#include <signal.h>
22#include <pwd.h>
23#include <errno.h>
24#include <sys/types.h>
25#include <dirent.h>
26#include <time.h>
27#include <sys/wait.h>
28#include <string.h>
29#include <limits.h>
30#include <crypt.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"
43
44
45enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
48};
49
50
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
53// confirmation link.
54void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
55 char string_to_hash[1024];
56 struct crypt_data cd;
57 char *ptr;
58
59 snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
60 memset(&cd, 0, sizeof cd);
61
62 strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
63
64 for (ptr=token_buf; *ptr; ++ptr) {
65 if (!isalnum((char)*ptr)) *ptr='X';
66 }
67}
68
69
70// This generates an email with a link the user clicks to confirm a list subscription.
71void 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);
75
76 char from_address[1024];
77 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
78
79 char emailtext[SIZ];
80 snprintf(emailtext, sizeof emailtext,
81 "MIME-Version: 1.0\n"
82 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
83 "\n"
84 "This is a multipart message in MIME format.\n"
85 "\n"
86 "--__ctdlmultipart__\n"
87 "Content-type: text/plain\n"
88 "\n"
89 "Someone (probably you) has submitted a request to subscribe\n"
90 "<%s> to the <%s> mailing list.\n"
91 "\n"
92 "Please go here to confirm this request:\n"
93 "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
94 "\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"
98 "\n"
99 "--__ctdlmultipart__\n"
100 "Content-type: text/html\n"
101 "\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>"
110 "</body></html>\n"
111 "\n"
112 "--__ctdlmultipart__--\n"
113 ,
114 emailaddr, roomname,
115 url, emailaddr, urlroom, confirmation_token,
116 roomname
117 ,
118 emailaddr, roomname,
119 url, emailaddr, urlroom, confirmation_token,
120 url, emailaddr, urlroom, confirmation_token,
121 roomname
122 );
123
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);
126}
127
128
129// This generates an email with a link the user clicks to confirm a list unsubscription.
130void 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);
134
135 char from_address[1024];
136 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
137
138 char emailtext[SIZ];
139 snprintf(emailtext, sizeof emailtext,
140 "MIME-Version: 1.0\n"
141 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
142 "\n"
143 "This is a multipart message in MIME format.\n"
144 "\n"
145 "--__ctdlmultipart__\n"
146 "Content-type: text/plain\n"
147 "\n"
148 "Someone (probably you) has submitted a request to unsubscribe\n"
149 "<%s> from the <%s> mailing list.\n"
150 "\n"
151 "Please go here to confirm this request:\n"
152 "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
153 "\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"
157 "\n"
158 "--__ctdlmultipart__\n"
159 "Content-type: text/html\n"
160 "\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>"
169 "</body></html>\n"
170 "\n"
171 "--__ctdlmultipart__--\n"
172 ,
173 emailaddr, roomname,
174 url, emailaddr, urlroom, confirmation_token,
175 roomname
176 ,
177 emailaddr, roomname,
178 url, emailaddr, urlroom, confirmation_token,
179 url, emailaddr, urlroom, confirmation_token,
180 roomname
181 );
182
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);
185}
186
187
188// Confirm a list subscription or unsubscription
189void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
190 int i;
191 char buf[1024];
192 int config_lines = 0;
193 char *oldnetconfig, *newnetconfig;
194
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);
199 return;
200 }
201
202 // If the generated token matches the supplied token, the request is authentic. Do what it says.
203
204 // Load the room's network configuration...
205 oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
206 if (!oldnetconfig) {
207 oldnetconfig = strdup("");
208 }
209
210 // The new netconfig begins with an empty buffer...
212 newnetconfig = malloc(strlen(oldnetconfig) + 1024);
213 newnetconfig[0] = 0;
214
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) {
218 char buf_email[256];
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);
223 }
224 }
225
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);
231 }
232
233 // write it back to disk
234 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
236 free(oldnetconfig);
237 free(newnetconfig);
238 cprintf("%d The pending request was confirmed.\n", CIT_OK);
239}
240
241
242// process subscribe/unsubscribe requests and confirmations
243void cmd_lsub(char *cmdbuf) {
244 char cmd[20];
245 char roomname[ROOMNAMELEN];
246 char emailaddr[1024];
247 char url[1024];
248 char generated_token[128];
249 char supplied_token[128];
250
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
256
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);
260 return;
261 }
262
263 if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
264 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
265 return;
266 }
267
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);
270
271 // Now parse the command.
272 if (!strcasecmp(cmd, "subscribe")) {
273 send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
274 }
275
276 else if (!strcasecmp(cmd, "unsubscribe")) {
277 send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
278 }
279
280 else if (!strcasecmp(cmd, "confirm_subscribe")) {
281 do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
282 }
283
284 else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
285 do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
286 }
287
288 else { // sorry man, I can't deal with that
289 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
290 }
291}
292
293
294// Initialization function, called from modules_init.c
296 if (!threading) {
297 CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
298 }
299
300 /* return our module name for the log */
301 return "listsub";
302}
#define ROOMNAMELEN
Definition: citadel.h:51
char * CtdlGetConfigStr(char *key)
Definition: config.c:363
#define CC
Definition: context.h:140
void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig)
Definition: netconfig.c:36
int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:309
char * LoadRoomNetConfigFile(long roomnum)
Definition: netconfig.c:61
int threading
Definition: modules_init.c:24
void CtdlRegisterProtoHook(void(*handler)(char *), char *cmd, char *desc)
#define CIT_OK
Definition: ipcdef.h:6
#define ILLEGAL_VALUE
Definition: ipcdef.h:16
#define ROOM_NOT_FOUND
Definition: ipcdef.h:31
#define ERROR
Definition: ipcdef.h:9
#define PASSWORD_REQUIRED
Definition: ipcdef.h:20
#define QR2_SELFLIST
Definition: ipcdef.h:56
long quickie_message(char *from, char *fromaddr, char *to, char *room, char *text, int format_type, char *subject)
Definition: msgbase.c:2906
void * malloc(unsigned)
void free(void *)
void cmd_lsub(char *cmdbuf)
Definition: serv_listsub.c:243
void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token)
Definition: serv_listsub.c:130
@ UNSUBSCRIBE
Definition: serv_listsub.c:46
@ SUBSCRIBE
Definition: serv_listsub.c:47
void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token)
Definition: serv_listsub.c:71
void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token)
Definition: serv_listsub.c:189
char * ctdl_module_init_listsub(void)
Definition: serv_listsub.c:295
void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr)
Definition: serv_listsub.c:54
#define FMT_RFC822
Definition: server.h:178
@ S_NETCONFIGS
Definition: server.h:145
#define SIZ
Definition: sysconfig.h:33
void cprintf(const char *format,...)
Definition: sysdep.c:369
void begin_critical_section(int which_one)
Definition: threads.c:62
void end_critical_section(int which_one)
Definition: threads.c:80