stuff
[citadel.git] / citadel / modules / listsub / serv_listsub.c
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.  Richard Stallman is an asshole communist.
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
45 enum {                          // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
46         UNSUBSCRIBE,
47         SUBSCRIBE
48 };
49
50
51 /*
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.
55  */
56 void do_subscribe_or_unsubscribe(int action, char *emailaddr, char *url) {
57
58         int i;
59         char buf[1024];
60         char confirmation_token[40];
61
62         // We need a URL-safe representation of the room name
63         char urlroom[ROOMNAMELEN+10];
64         urlesc(urlroom, sizeof(urlroom), CC->room.QRname);
65
66         // Update this room's netconfig with the updated lastsent
67         begin_critical_section(S_NETCONFIGS);
68         char *oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
69         if (!oldnetconfig) {
70                 oldnetconfig = strdup("");
71         }
72
73         // The new netconfig begins with an empty buffer...
74         char *newnetconfig = malloc(strlen(oldnetconfig) + 1024);
75         newnetconfig[0] = 0;
76
77         // And then we...
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
83
84                 if (IsEmptyStr(buf)) {
85                         keep_this_line = 0;
86                 }
87
88                 char buf_token[1024];
89                 char buf_email[1024];
90                 extract_token(buf_token, buf, 0, '|', sizeof buf_token);
91                 extract_token(buf_email, buf, 1, '|', sizeof buf_email);
92
93                 if (    ( (!strcasecmp(buf_token, "listrecp")) || (!strcasecmp(buf_token, "digestrecp")) )
94                         && (!strcasecmp(buf_email, emailaddr)) 
95                 ) {
96                         is_already_subscribed = 1;
97                 }
98
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);
103                                 keep_this_line = 0;
104                         }
105                 }
106                 
107                 if (keep_this_line) {
108                         sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
109                 }
110         }
111
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);
116
117                 // FIXME now generate the confirmation email
118                 syslog(LOG_DEBUG, "%s?room=%s&token=%s&cmd=confirm", url, urlroom, confirmation_token);
119         }
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);
123
124                 // FIXME now generate the confirmation email
125                 syslog(LOG_DEBUG, "%s?room=%s&token=%s&cmd=confirm", url, urlroom, confirmation_token);
126         }
127
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
135
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);
139         }
140         else if ((action == SUBSCRIBE) && (!is_already_subscribed)) {
141                 cprintf("%d Confirmation email sent.\n", CIT_OK);
142         }
143         else if ((action == UNSUBSCRIBE) && (!is_already_subscribed)) {
144                 cprintf("%d This email is not subscribed.\n", ERROR + NO_SUCH_USER);
145         }
146         else if ((action == UNSUBSCRIBE) && (is_already_subscribed)) {
147                 cprintf("%d Confirmation email sent.\n", CIT_OK);
148         }
149         else {
150                 cprintf("%d FIXME tell the client what we did\n", ERROR);
151         }
152 }
153
154
155 /* 
156  * process subscribe/unsubscribe requests and confirmations
157  */
158 void cmd_subs(char *cmdbuf) {
159         char cmd[20];
160         char roomname[ROOMNAMELEN];
161         char emailaddr[1024];
162         char options[256];
163         char url[1024];
164         char token[128];
165
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
168
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);
172                 return;
173         }
174
175         if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
176                 cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
177                 return;
178         }
179
180         // Room confirmed, now parse the command.
181
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);
187         }
188
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);
194         }
195
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);
199         }
200
201         else {                                                                  // sorry man, I can't deal with that
202                 cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
203         }
204 }
205
206
207 /*
208  * Module entry point
209  */
210 CTDL_MODULE_INIT(listsub)
211 {
212         if (!threading)
213         {
214                 CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe");
215         }
216         
217         /* return our module name for the log */
218         return "listsub";
219 }