citadel.h is now citadel_defs.h
[citadel.git] / citadel / server / modules / pop3client / serv_pop3client.c
1 // Consolidate mail from remote POP3 accounts.
2 //
3 // Copyright (c) 2007-2022 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <time.h>
12 #include <ctype.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <curl/curl.h>
18 #include <libcitadel.h>
19 #include "../../sysconfig.h"
20 #include "../../citadel_defs.h"
21 #include "../../server.h"
22 #include "../../citserver.h"
23 #include "../../support.h"
24 #include "../../config.h"
25 #include "../../ctdl_module.h"
26 #include "../../clientsocket.h"
27 #include "../../msgbase.h"
28 #include "../../internet_addressing.h"
29 #include "../../database.h"
30 #include "../../citadel_dirs.h"
31
32 struct p3cq {                           // module-local queue of pop3 client work that needs processing
33         struct p3cq *next;
34         char *room;
35         char *host;
36         char *user;
37         char *pass;
38         int keep;
39         long interval;
40 };
41
42 static int doing_pop3client = 0;
43 struct p3cq *p3cq = NULL;
44
45 // Process one mailbox.
46 void pop3client_one_mailbox(char *room, const char *host, const char *user, const char *pass, int keep, long interval) {
47         syslog(LOG_DEBUG, "pop3client: room=<%s> host=<%s> user=<%s> keep=<%d> interval=<%ld>", room, host, user, keep, interval);
48
49         char url[SIZ];
50         CURL *curl;
51         CURLcode res = CURLE_OK;
52         StrBuf *Uidls = NULL;
53         int i;
54         char cmd[1024];
55
56         curl = curl_easy_init();
57         if (!curl) {
58                 return;
59         }
60
61         Uidls = NewStrBuf();
62
63         curl_easy_setopt(curl, CURLOPT_USERNAME, user);
64         curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
65         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
66         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
67         curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
68         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
69         curl_easy_setopt(curl, CURLOPT_WRITEDATA, Uidls);                       // Give it our StrBuf to work with
70         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "UIDL");
71
72         // Try POP3S (SSL encrypted) first
73         snprintf(url, sizeof url, "pop3s://%s", host);
74         curl_easy_setopt(curl, CURLOPT_URL, url);
75         res = curl_easy_perform(curl);
76         if (res == CURLE_OK) {
77         }
78         else {
79                 syslog(LOG_DEBUG, "pop3client: POP3S connection failed: %s , trying POP3 next", curl_easy_strerror(res));
80                 snprintf(url, sizeof url, "pop3://%s", host);                   // try unencrypted next
81                 curl_easy_setopt(curl, CURLOPT_URL, url);
82                 FlushStrBuf(Uidls);
83                 res = curl_easy_perform(curl);
84         }
85
86         if (res != CURLE_OK) {
87                 syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
88                 curl_easy_cleanup(curl);
89                 FreeStrBuf(&Uidls);
90                 return;
91         }
92
93         // If we got this far, a connection was established, we know whether it's pop3s or pop3, and UIDL is supported.
94         // Now go through the UIDL list and look for messages.
95
96         int num_msgs = num_tokens(ChrPtr(Uidls), '\n');
97         syslog(LOG_DEBUG, "pop3client: there are %d messages", num_msgs);
98         for (i=0; i<num_msgs; ++i) {
99                 char oneuidl[1024];
100                 extract_token(oneuidl, ChrPtr(Uidls), i, '\n', sizeof oneuidl);
101                 if (strlen(oneuidl) > 2) {
102                         if (oneuidl[strlen(oneuidl)-1] == '\r') {
103                                 oneuidl[strlen(oneuidl)-1] = 0;
104                         }
105                         int this_msg = atoi(oneuidl);
106                         char *c = strchr(oneuidl, ' ');
107                         if (c) strcpy(oneuidl, ++c);
108
109                         // Make up the Use Table record so we can check if we've already seen this message.
110                         StrBuf *UT = NewStrBuf();
111                         StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
112                         int already_seen = CheckIfAlreadySeen(UT);
113                         FreeStrBuf(&UT);
114
115                         // Only fetch the message if we haven't seen it before.
116                         if (already_seen == 0) {
117                                 StrBuf *TheMsg = NewStrBuf();
118                                 snprintf(cmd, sizeof cmd, "RETR %d", this_msg);
119                                 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
120                                 curl_easy_setopt(curl, CURLOPT_WRITEDATA, TheMsg);
121                                 res = curl_easy_perform(curl);
122                                 if (res == CURLE_OK) {
123                                         struct CtdlMessage *msg = convert_internet_message_buf(&TheMsg);
124                                         CtdlSubmitMsg(msg, NULL, room);
125                                         CM_Free(msg);
126                                 }
127                                 else {
128                                         FreeStrBuf(&TheMsg);
129                                 }
130
131                                 // Unless the configuration says to keep the message on the server, delete it.
132                                 if (keep == 0) {
133                                         snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
134                                         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
135                                         res = curl_easy_perform(curl);
136                                 }
137                         }
138                         else {
139                                 syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
140                         }
141                 }
142         }
143
144         curl_easy_cleanup(curl);
145         FreeStrBuf(&Uidls);
146         return;
147 }
148
149
150 // Scan a room's netconfig looking for RSS feed parsing requests
151 void pop3client_scan_room(struct ctdlroom *qrbuf, void *data) {
152         char *serialized_config = NULL;
153         int num_configs = 0;
154         char cfgline[SIZ];
155         char cfgelement[SIZ];
156         int i = 0;
157
158         serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
159         if (!serialized_config) {
160                 return;
161         }
162
163         num_configs = num_tokens(serialized_config, '\n');
164         for (i=0; i<num_configs; ++i) {
165                 extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
166                 if (!strncasecmp(cfgline, HKEY("pop3client|"))) {
167                         struct p3cq *pptr = malloc(sizeof(struct p3cq));
168                         pptr->next = p3cq;
169                         p3cq = pptr;
170                         p3cq->room = strdup(qrbuf->QRname);
171                         extract_token(cfgelement, cfgline, 1, '|', sizeof cfgelement);
172                         p3cq->host = strdup(cfgelement);
173                         extract_token(cfgelement, cfgline, 2, '|', sizeof cfgelement);
174                         p3cq->user = strdup(cfgelement);
175                         extract_token(cfgelement, cfgline, 3, '|', sizeof cfgelement);
176                         p3cq->pass = strdup(cfgelement);
177                         p3cq->keep = extract_int(cfgline, 4);
178                         p3cq->interval = extract_long(cfgline, 5);
179                 }
180         }
181
182         free(serialized_config);
183 }
184
185
186 void pop3client_scan(void) {
187         static time_t last_run = 0L;
188         time_t fastest_scan;
189         struct p3cq *pptr = NULL;
190
191         if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
192                 fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
193         }
194         else {
195                 fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
196         }
197
198         // Run POP3 aggregation no more frequently than once every n seconds
199         if ( (time(NULL) - last_run) < fastest_scan ) {
200                 return;
201         }
202
203         // This is a simple concurrency check to make sure only one pop3client
204         // run is done at a time.  We could do this with a mutex, but since we
205         // don't really require extremely fine granularity here, we'll do it
206         // with a static variable instead.
207         if (doing_pop3client) return;
208         doing_pop3client = 1;
209
210         syslog(LOG_DEBUG, "pop3client: scan started");
211         CtdlForEachRoom(pop3client_scan_room, NULL);
212
213         // We have to queue and process in separate phases, otherwise we leave a cursor open
214         syslog(LOG_DEBUG, "pop3client: processing started");
215         while (p3cq != NULL) {
216                 pptr = p3cq;
217                 p3cq = p3cq->next;
218
219                 pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
220
221                 free(pptr->room);
222                 free(pptr->host);
223                 free(pptr->user);
224                 free(pptr->pass);
225                 free(pptr);
226         }
227
228         syslog(LOG_DEBUG, "pop3client: ended");
229         last_run = time(NULL);
230         doing_pop3client = 0;
231 }
232
233
234 // Initialization function, called from modules_init.c
235 char *ctdl_module_init_pop3client(void) {
236         if (!threading) {
237                 CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
238         }
239
240         // return our module name for the log
241         return "pop3client";
242 }