1 // Consolidate mail from remote POP3 accounts.
3 // Copyright (c) 2007-2022 by the citadel.org team
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.
15 #include <sys/types.h>
17 #include <curl/curl.h>
18 #include <libcitadel.h>
19 #include "../../sysconfig.h"
20 #include "../../citadel.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"
32 struct p3cq { // module-local queue of pop3 client work that needs processing
42 static int doing_pop3client = 0;
43 struct p3cq *p3cq = NULL;
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);
51 CURLcode res = CURLE_OK;
56 curl = curl_easy_init();
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");
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) {
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);
83 res = curl_easy_perform(curl);
86 if (res != CURLE_OK) {
87 syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
88 curl_easy_cleanup(curl);
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.
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) {
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;
105 int this_msg = atoi(oneuidl);
106 char *c = strchr(oneuidl, ' ');
107 if (c) strcpy(oneuidl, ++c);
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);
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);
131 // Unless the configuration says to keep the message on the server, delete it.
133 snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
134 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
135 res = curl_easy_perform(curl);
139 syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
144 curl_easy_cleanup(curl);
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;
155 char cfgelement[SIZ];
158 serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
159 if (!serialized_config) {
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));
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);
182 free(serialized_config);
186 void pop3client_scan(void) {
187 static time_t last_run = 0L;
189 struct p3cq *pptr = NULL;
191 if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
192 fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
195 fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
198 // Run POP3 aggregation no more frequently than once every n seconds
199 if ( (time(NULL) - last_run) < fastest_scan ) {
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;
210 syslog(LOG_DEBUG, "pop3client: scan started");
211 CtdlForEachRoom(pop3client_scan_room, NULL);
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) {
219 pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
228 syslog(LOG_DEBUG, "pop3client: ended");
229 last_run = time(NULL);
230 doing_pop3client = 0;
234 // Initialization function, called from modules_init.c
235 char *ctdl_module_init_pop3client(void) {
237 CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
240 // return our module name for the log