2 // This module handles self-service subscription/unsubscription to mail lists.
4 // Copyright (c) 2002-2021 by the citadel.org team
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. Richard Stallman is an asshole communist.
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.
25 #include <sys/types.h>
31 #include <libcitadel.h>
34 #include "citserver.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
52 * "Subscribe" and "Unsubscribe" operations are so similar that they share a function.
53 * The actual subscription doesn't take place here -- we just send out the confirmation request
54 * and record the address and confirmation token.
56 void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url) {
60 char confirmation_token[40];
62 // We need a URL-safe representation of the room name
63 char urlroom[ROOMNAMELEN+10];
64 urlesc(urlroom, sizeof(urlroom), CC->room.QRname);
66 // Update this room's netconfig with the updated lastsent
67 begin_critical_section(S_NETCONFIGS);
68 char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
70 oldnetconfig = strdup("");
73 // The new netconfig begins with an empty buffer...
74 char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
78 int is_already_subscribed = 0;
79 int config_lines = num_tokens(oldnetconfig, '\n');
80 for (i=0; i<config_lines; ++i) {
81 extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
82 int keep_this_line =1; // set to nonzero if we are discarding a line
84 if (IsEmptyStr(buf)) {
90 extract_token(buf_token, buf, 0, '|', sizeof buf_token);
91 extract_token(buf_email, buf, 1, '|', sizeof buf_email);
93 if ( ( (!strcasecmp(buf_token, "listrecp")) || (!strcasecmp(buf_token, "digestrecp")) )
94 && (!strcasecmp(buf_email, emailaddr))
96 is_already_subscribed = 1;
99 if ( (!strcasecmp(buf_token, "subpending")) || (!strcasecmp(buf_token, "unsubpending")) ) {
100 time_t pendingtime = extract_long(buf, 3);
101 if ((time(NULL) - pendingtime) > 259200) {
102 syslog(LOG_DEBUG, "%s %s is %ld seconds old - deleting it", buf_email, buf_token, time(NULL) - pendingtime);
107 if (keep_this_line) {
108 sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
112 // Do we need to send out a confirmation email?
113 if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
114 generate_uuid(confirmation_token);
115 sprintf(&newnetconfig[strlen(newnetconfig)], "subpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
117 // FIXME now generate the confirmation email
118 syslog(LOG_DEBUG, "%s?room=%s&token=%s&cmd=confirm", url, urlroom, confirmation_token);
120 if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
121 generate_uuid(confirmation_token);
122 sprintf(&newnetconfig[strlen(newnetconfig)], "unsubpending|%s|%s|%ld|%s", emailaddr, confirmation_token, time(NULL), url);
124 // FIXME now generate the confirmation email
125 syslog(LOG_DEBUG, "%s?room=%s&token=%s&cmd=confirm", url, urlroom, confirmation_token);
128 // Write the new netconfig back to disk
129 syslog(LOG_DEBUG, "old: <\033[31m%s\033[0m>", oldnetconfig);
130 syslog(LOG_DEBUG, "new: <\033[32m%s\033[0m>", newnetconfig);
131 SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
132 end_critical_section(S_NETCONFIGS);
133 free(newnetconfig); // this was the new netconfig, free it because we're done with it
134 free(oldnetconfig); // this was the old netconfig, free it even if we didn't do anything
136 // Tell the client what happened.
137 if ((action == SUBSCRIBE) && (is_already_subscribed)) {
138 cprintf("%d This email is already subscribed.\n", ERROR + ALREADY_EXISTS);
140 else if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
141 cprintf("%d Confirmation email sent.\n", CIT_OK);
143 else if ((action == UNSUBSCRIBE) && (!is_already_subscribed)) {
144 cprintf("%d This email is not subscribed.\n", ERROR + NO_SUCH_USER);
146 else if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
147 cprintf("%d Confirmation email sent.\n", CIT_OK);
150 cprintf("%d FIXME tell the client what we did\n", ERROR);
156 * process subscribe/unsubscribe requests and confirmations
158 void cmd_subs(char *cmdbuf) {
160 char roomname[ROOMNAMELEN];
161 char emailaddr[1024];
166 extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
167 extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
169 // First confirm that the caller is referencing a room that actually exists.
170 if (CtdlGetRoom(&CC->room, roomname) != 0) {
171 cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
175 if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
176 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
180 // Room confirmed, now parse the command.
182 if (!strcasecmp(cmd, "subscribe")) {
183 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
184 extract_token(options, cmdbuf, 3, '|', sizeof options); // there are no options ... ignore this token
185 extract_token(url, cmdbuf, 4, '|', sizeof url); // token 3 is the URL at which we subscribed
186 do_subscribe_or_unsubscribe(SUBSCRIBE, emailaddr, url);
189 else if (!strcasecmp(cmd, "unsubscribe")) {
190 extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
191 extract_token(options, cmdbuf, 3, '|', sizeof options); // there are no options ... ignore this token
192 extract_token(url, cmdbuf, 4, '|', sizeof url); // token 3 is the URL at which we subscribed
193 do_subscribe_or_unsubscribe(UNSUBSCRIBE, emailaddr, url);
196 else if (!strcasecmp(cmd, "confirm")) {
197 extract_token(token, cmdbuf, 2, '|', sizeof token); // token 2 is the confirmation token
198 cprintf("%d not implemented\n", ERROR);
201 else { // sorry man, I can't deal with that
202 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
210 CTDL_MODULE_INIT(listsub)
214 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
217 /* return our module name for the log */