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)  

serv_listsub.c
Go to the documentation of this file.
1//
2// This module handles self-service subscription/unsubscription to mail lists.
3//
4// Copyright (c) 2002-2021 by the citadel.org team
5//
6// This program is open source software. It runs great on the
7// Linux operating system (and probably elsewhere). You can use,
8// copy, and run it under the terms of the GNU General Public
9// License version 3.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16#include "sysdep.h"
17#include <stdlib.h>
18#include <unistd.h>
19#include <stdio.h>
20#include <fcntl.h>
21#include <ctype.h>
22#include <signal.h>
23#include <pwd.h>
24#include <errno.h>
25#include <sys/types.h>
26#include <dirent.h>
27#include <time.h>
28#include <sys/wait.h>
29#include <string.h>
30#include <limits.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/*
52 * This generates an email with a link the user clicks to confirm a list subscription.
53 */
54void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
55 // We need a URL-safe representation of the room name
56 char urlroom[ROOMNAMELEN+10];
57 urlesc(urlroom, sizeof(urlroom), roomname);
58
59 char from_address[1024];
60 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
61
62 char emailtext[SIZ];
63 snprintf(emailtext, sizeof emailtext,
64 "MIME-Version: 1.0\n"
65 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
66 "\n"
67 "This is a multipart message in MIME format.\n"
68 "\n"
69 "--__ctdlmultipart__\n"
70 "Content-type: text/plain\n"
71 "\n"
72 "Someone (probably you) has submitted a request to subscribe\n"
73 "<%s> to the <%s> mailing list.\n"
74 "\n"
75 "Please go here to confirm this request:\n"
76 "%s?room=%s&token=%s&cmd=confirm\n"
77 "\n"
78 "If this request has been submitted in error and you do not\n"
79 "wish to receive the <%s> mailing list, simply do nothing,\n"
80 "and you will not receive any further mailings.\n"
81 "\n"
82 "--__ctdlmultipart__\n"
83 "Content-type: text/html\n"
84 "\n"
85 "<html><body><p>Someone (probably you) has submitted a request to subscribe "
86 "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
87 "<p>Please go here to confirm this request:</p>"
88 "<p><a href=\"%s?room=%s&token=%s&cmd=confirm\">"
89 "%s?room=%s&token=%s&cmd=confirm</a></p>"
90 "<p>If this request has been submitted in error and you do not "
91 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
92 "and you will not receive any further mailings.</p>"
93 "</body></html>\n"
94 "\n"
95 "--__ctdlmultipart__--\n"
96 ,
97 emailaddr, roomname,
98 url, urlroom, confirmation_token,
99 roomname
100 ,
101 emailaddr, roomname,
102 url, urlroom, confirmation_token,
103 url, urlroom, confirmation_token,
104 roomname
105 );
106
107 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
108}
109
110
111/*
112 * This generates an email with a link the user clicks to confirm a list unsubscription.
113 */
114void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
115 // We need a URL-safe representation of the room name
116 char urlroom[ROOMNAMELEN+10];
117 urlesc(urlroom, sizeof(urlroom), roomname);
118
119 char from_address[1024];
120 snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
121
122 char emailtext[SIZ];
123 snprintf(emailtext, sizeof emailtext,
124 "MIME-Version: 1.0\n"
125 "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
126 "\n"
127 "This is a multipart message in MIME format.\n"
128 "\n"
129 "--__ctdlmultipart__\n"
130 "Content-type: text/plain\n"
131 "\n"
132 "Someone (probably you) has submitted a request to unsubscribe\n"
133 "<%s> from the <%s> mailing list.\n"
134 "\n"
135 "Please go here to confirm this request:\n"
136 "%s?room=%s&token=%s&cmd=confirm\n"
137 "\n"
138 "If this request has been submitted in error and you still\n"
139 "wish to receive the <%s> mailing list, simply do nothing,\n"
140 "and you will remain subscribed.\n"
141 "\n"
142 "--__ctdlmultipart__\n"
143 "Content-type: text/html\n"
144 "\n"
145 "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
146 "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
147 "<p>Please go here to confirm this request:</p>"
148 "<p><a href=\"%s?room=%s&token=%s&cmd=confirm\">"
149 "%s?room=%s&token=%s&cmd=confirm</a></p>"
150 "<p>If this request has been submitted in error and you still "
151 "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
152 "and you will remain subscribed.</p>"
153 "</body></html>\n"
154 "\n"
155 "--__ctdlmultipart__--\n"
156 ,
157 emailaddr, roomname,
158 url, urlroom, confirmation_token,
159 roomname
160 ,
161 emailaddr, roomname,
162 url, urlroom, confirmation_token,
163 url, urlroom, confirmation_token,
164 roomname
165 );
166
167 quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
168}
169
170
171/*
172 * "Subscribe" and "Unsubscribe" operations are so similar that they share a function.
173 * The actual subscription doesn't take place here -- we just send out the confirmation request
174 * and record the address and confirmation token.
175 */
176void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url) {
177
178 int i;
179 char buf[1024];
180 char confirmation_token[40];
181
182 // Update this room's netconfig with the updated lastsent
184 char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
185 if (!oldnetconfig) {
186 oldnetconfig = strdup("");
187 }
188
189 // The new netconfig begins with an empty buffer...
190 char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
191 newnetconfig[0] = 0;
192
193 // And then we...
194 int is_already_subscribed = 0;
195 int config_lines = num_tokens(oldnetconfig, '\n');
196 for (i=0; i<config_lines; ++i) {
197 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
198 int keep_this_line =1; // set to nonzero if we are discarding a line
199
200 if (IsEmptyStr(buf)) {
201 keep_this_line = 0;
202 }
203
204 char buf_directive[1024];
205 char buf_email[1024];
206 extract_token(buf_directive, buf, 0, '|', sizeof buf_directive);
207 extract_token(buf_email, buf, 1, '|', sizeof buf_email);
208
209 if ( ( (!strcasecmp(buf_directive, "listrecp")) || (!strcasecmp(buf_directive, "digestrecp")) )
210 && (!strcasecmp(buf_email, emailaddr))
211 ) {
212 is_already_subscribed = 1;
213 }
214
215 if ( (!strcasecmp(buf_directive, "subpending")) || (!strcasecmp(buf_directive, "unsubpending")) ) {
216 time_t pendingtime = extract_long(buf, 3);
217 if ((time(NULL) - pendingtime) > 259200) {
218 syslog(LOG_DEBUG, "%s %s is %ld seconds old - deleting it", buf_email, buf_directive, time(NULL) - pendingtime);
219 keep_this_line = 0;
220 }
221 }
222
223 if (keep_this_line) {
224 sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
225 }
226 }
227
228 // Do we need to send out a confirmation email?
229 if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
230 generate_uuid(confirmation_token);
231 sprintf(&newnetconfig[strlen(newnetconfig)], "subpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
232 send_subscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token);
233 }
234 if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
235 generate_uuid(confirmation_token);
236 sprintf(&newnetconfig[strlen(newnetconfig)], "unsubpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
237 send_unsubscribe_confirmation_email(CC->room.QRname, emailaddr, url, confirmation_token);
238 }
239
240 // Write the new netconfig back to disk
241 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
243 free(newnetconfig); // this was the new netconfig, free it because we're done with it
244 free(oldnetconfig); // this was the old netconfig, free it even if we didn't do anything
245
246 // Tell the client what happened.
247 if ((action == SUBSCRIBE) && (is_already_subscribed)) {
248 cprintf("%d This email address is already subscribed.\n", ERROR + ALREADY_EXISTS);
249 }
250 else if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
251 cprintf("%d Subscription was requested, and a confirmation email was sent.\n", CIT_OK);
252 }
253 else if ((action == UNSUBSCRIBE) && (!is_already_subscribed)) {
254 cprintf("%d This email address is not subscribed.\n", ERROR + NO_SUCH_USER);
255 }
256 else if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
257 cprintf("%d Unsubscription was requested, and a confirmation email was sent.\n", CIT_OK);
258 }
259 else {
260 cprintf("%d Nothing happens.\n", ERROR);
261 }
262}
263
264
265/*
266 * Confirm a list subscription or unsubscription
267 */
268void do_confirm(char *token) {
269 int yes_subscribe = 0; // Set to 1 if the confirmation to subscribe is validated.
270 int yes_unsubscribe = 0; // Set to 1 if the confirmation to unsubscribe is validated.
271 int i;
272 char buf[1024];
273 int config_lines = 0;
274 char pending_directive[128];
275 char pending_email[256];
276 char pending_token[128];
277
278 // We will have to do this in two passes. The first pass checks to see if we have a confirmation request matching the token.
279 char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
280 if (!oldnetconfig) {
281 cprintf("%d There are no pending requests.\n", ERROR + NO_SUCH_USER);
282 return;
283 }
284
285 config_lines = num_tokens(oldnetconfig, '\n');
286 for (i=0; i<config_lines; ++i) {
287 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
288 extract_token(pending_directive, buf, 0, '|', sizeof pending_directive);
289 extract_token(pending_email, buf, 1, '|', sizeof pending_email);
290 extract_token(pending_token, buf, 2, '|', sizeof pending_token);
291
292 if (!strcasecmp(pending_token, token)) {
293 if (!strcasecmp(pending_directive, "subpending")) {
294 yes_subscribe = 1;
295 }
296 else if (!strcasecmp(pending_directive, "unsubpending")) {
297 yes_unsubscribe = 1;
298 }
299 }
300 }
301 free(oldnetconfig);
302
303 // We didn't find a pending subscribe or unsubscribe request with the supplied token.
304 if ((!yes_subscribe) && (!yes_unsubscribe)) {
305 cprintf("%d The request you are trying to confirm was not found.\n", ERROR + NO_SUCH_USER);
306 return;
307 }
308
309 // The second pass performs the now confirmed operation.
310 // We will have to do this in two passes. The first pass checks to see if we have a confirmation request matching the token.
311 oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
312 if (!oldnetconfig) {
313 oldnetconfig = strdup("");
314 }
315
316 // The new netconfig begins with an empty buffer...
318 char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
319 newnetconfig[0] = 0;
320
321 config_lines = num_tokens(oldnetconfig, '\n');
322 for (i=0; i<config_lines; ++i) {
323 char buf_email[256];
324 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
325 extract_token(buf_email, buf, 1, '|', sizeof pending_email);
326 if (strcasecmp(buf_email, pending_email)) {
327 sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf); // only keep lines that do not reference this subscriber
328 }
329 }
330
331 // We have now removed all lines containing the subscriber's email address. This deletes any pending requests.
332 // If this was an unsubscribe operation, they're now gone from the list.
333 // But if this was a subscribe operation, we now need to add them.
334 if (yes_subscribe) {
335 sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", pending_email);
336 }
337
338 // FIXME write it back to disk
339 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
341 free(oldnetconfig);
342 free(newnetconfig);
343 cprintf("%d The pending request was confirmed.\n", CIT_OK);
344}
345
346
347/*
348 * process subscribe/unsubscribe requests and confirmations
349 */
350void cmd_lsub(char *cmdbuf) {
351 char cmd[20];
352 char roomname[ROOMNAMELEN];
353 char emailaddr[1024];
354 char url[1024];
355 char token[128];
356
357 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
358 extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
359
360 // First confirm that the caller is referencing a room that actually exists.
361 if (CtdlGetRoom(&CC->room, roomname) != 0) {
362 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
363 return;
364 }
365
366 if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
367 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
368 return;
369 }
370
371 // Room confirmed, now parse the command.
372
373 if (!strcasecmp(cmd, "subscribe")) {
374 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
375 extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
376 do_subscribe_or_unsubscribe(SUBSCRIBE, emailaddr, url);
377 }
378
379 else if (!strcasecmp(cmd, "unsubscribe")) {
380 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
381 extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
383 }
384
385 else if (!strcasecmp(cmd, "confirm")) {
386 extract_token(token, cmdbuf, 2, '|', sizeof token); // token 2 is the confirmation token
387 do_confirm(token);
388 }
389
390 else { // sorry man, I can't deal with that
391 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
392 }
393}
394
395
396/*
397 * Module entry point
398 */
400{
401 if (!threading)
402 {
403 CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
404 }
405
406 /* return our module name for the log */
407 return "listsub";
408}
#define ROOMNAMELEN
Definition: citadel.h:56
char * CtdlGetConfigStr(char *key)
Definition: config.c:384
#define CC
Definition: context.h:140
void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig)
Definition: netconfig.c:50
int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:342
char * LoadRoomNetConfigFile(long roomnum)
Definition: netconfig.c:77
void CtdlRegisterProtoHook(void(*handler)(char *), char *cmd, char *desc)
#define CTDL_MODULE_INIT(module_name)
Definition: ctdl_module.h:50
#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 ALREADY_EXISTS
Definition: ipcdef.h:33
#define NO_SUCH_USER
Definition: ipcdef.h:29
#define QR2_SELFLIST
Definition: ipcdef.h:56
long quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject)
Definition: msgbase.c:2921
void * malloc(size_t)
void free(void *)
void cmd_lsub(char *cmdbuf)
Definition: serv_listsub.c:350
void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token)
Definition: serv_listsub.c:114
@ 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:54
void do_confirm(char *token)
Definition: serv_listsub.c:268
void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url)
Definition: serv_listsub.c:176
#define FMT_RFC822
Definition: server.h:178
@ S_NETCONFIGS
Definition: server.h:146
int snprintf(char *buf, size_t max, const char *fmt,...)
Definition: snprintf.c:69
#define SIZ
Definition: sysconfig.h:33
void cprintf(const char *format,...)
Definition: sysdep.c:381
void begin_critical_section(int which_one)
Definition: threads.c:67
void end_critical_section(int which_one)
Definition: threads.c:85