Aggregation of remote POP3 accounts is now working.
[citadel.git] / citadel / modules / pop3client / serv_pop3client.c
1 /*
2  * Aggregate remote POP3 accounts
3  */
4
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <stdio.h>
8
9 #if TIME_WITH_SYS_TIME
10 # include <sys/time.h>
11 # include <time.h>
12 #else
13 # if HAVE_SYS_TIME_H
14 #  include <sys/time.h>
15 # else
16 #  include <time.h>
17 # endif
18 #endif
19
20 #include <ctype.h>
21 #include <string.h>
22 #include <errno.h>
23 #include "citadel.h"
24 #include "server.h"
25 #include "citserver.h"
26 #include "support.h"
27 #include "config.h"
28 #include "tools.h"
29 #include "room_ops.h"
30 #include "ctdl_module.h"
31 #include "clientsocket.h"
32 #include "msgbase.h"
33 #include "internet_addressing.h"
34
35 struct pop3aggr {
36         struct pop3aggr *next;
37         char roomname[ROOMNAMELEN];
38         char pop3host[128];
39         char pop3user[128];
40         char pop3pass[128];
41 };
42
43 struct pop3aggr *palist = NULL;
44
45 #ifdef POP3_AGGREGATION
46
47
48 void pop3_do_fetching(char *roomname, char *pop3host, char *pop3user, char *pop3pass)
49 {
50         int sock;
51         char buf[SIZ];
52         int msg_to_fetch = 0;
53         int *msglist = NULL;
54         int num_msgs = 0;
55         int alloc_msgs = 0;
56         int i;
57         char *body = NULL;
58         struct CtdlMessage *msg = NULL;
59         long msgnum = 0;
60
61         lprintf(CTDL_DEBUG, "POP3: %s %s %s %s\n", roomname, pop3host, pop3user, pop3pass);
62         lprintf(CTDL_NOTICE, "Connecting to <%s>\n", pop3host);
63         sock = sock_connect(pop3host, "110", "tcp");
64         if (sock < 0) {
65                 lprintf(CTDL_ERR, "Could not connect: %s\n", strerror(errno));
66                 return;
67         }
68         
69         lprintf(CTDL_DEBUG, "Connected!\n");
70
71         /* Read the server greeting */
72         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
73         lprintf(CTDL_DEBUG, ">%s\n", buf);
74         if (strncasecmp(buf, "+OK", 3)) goto bail;
75
76         /* Identify ourselves */
77         snprintf(buf, sizeof buf, "USER %s", pop3user);
78         lprintf(CTDL_DEBUG, "<%s\n", buf);
79         if (sock_puts(sock, buf) <0) goto bail;
80         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
81         lprintf(CTDL_DEBUG, ">%s\n", buf);
82         if (strncasecmp(buf, "+OK", 3)) goto bail;
83
84         /* Password */
85         snprintf(buf, sizeof buf, "PASS %s", pop3pass);
86         lprintf(CTDL_DEBUG, "<%s\n", buf);
87         if (sock_puts(sock, buf) <0) goto bail;
88         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
89         lprintf(CTDL_DEBUG, ">%s\n", buf);
90         if (strncasecmp(buf, "+OK", 3)) goto bail;
91
92         /* Get the list of messages */
93         snprintf(buf, sizeof buf, "LIST");
94         lprintf(CTDL_DEBUG, "<%s\n", buf);
95         if (sock_puts(sock, buf) <0) goto bail;
96         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
97         lprintf(CTDL_DEBUG, ">%s\n", buf);
98         if (strncasecmp(buf, "+OK", 3)) goto bail;
99
100         do {
101                 if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
102                 lprintf(CTDL_DEBUG, ">%s\n", buf);
103                 msg_to_fetch = atoi(buf);
104                 if (msg_to_fetch > 0) {
105                         if (alloc_msgs == 0) {
106                                 alloc_msgs = 100;
107                                 msglist = malloc((alloc_msgs * (sizeof(int))));
108                         }
109                         else if (num_msgs >= alloc_msgs) {
110                                 alloc_msgs = alloc_msgs * 2;
111                                 msglist = realloc(msglist, (alloc_msgs * sizeof(int)));
112                         }
113                         if (msglist == NULL) goto bail;
114                         msglist[num_msgs++] = msg_to_fetch;
115                 }
116         } while (buf[0] != '.');
117
118         if (num_msgs) for (i=0; i<num_msgs; ++i) {
119
120                 /* Tell the server to fetch the message */
121                 snprintf(buf, sizeof buf, "RETR %d", msglist[i]);
122                 lprintf(CTDL_DEBUG, "<%s\n", buf);
123                 if (sock_puts(sock, buf) <0) goto bail;
124                 if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
125                 lprintf(CTDL_DEBUG, ">%s\n", buf);
126                 if (strncasecmp(buf, "+OK", 3)) goto bail;
127
128                 /* If we get to this point, the message is on its way.  Read it. */
129                 body = CtdlReadMessageBody(".", config.c_maxmsglen, NULL, 1, sock);
130                 if (body == NULL) goto bail;
131
132                 lprintf(CTDL_DEBUG, "Converting message...\n");
133                 msg = convert_internet_message(body);
134                 body = NULL;    /* yes, this should be dereferenced, NOT freed */
135
136                 /* Do Something With It (tm) */
137                 msgnum = CtdlSubmitMsg(msg, NULL, roomname);
138                 if (msgnum > 0L) {
139                         /* Message has been committed to the store, so delete it from the remote server */
140                         snprintf(buf, sizeof buf, "DELE %d", msglist[i]);
141                         lprintf(CTDL_DEBUG, "<%s\n", buf);
142                         if (sock_puts(sock, buf) <0) goto bail;
143                         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
144                         lprintf(CTDL_DEBUG, ">%s\n", buf);
145                         if (strncasecmp(buf, "+OK", 3)) goto bail;
146                 }
147                 CtdlFreeMessage(msg);
148         }
149
150         /* Log out */
151         snprintf(buf, sizeof buf, "QUIT");
152         lprintf(CTDL_DEBUG, "<%s\n", buf);
153         if (sock_puts(sock, buf) <0) goto bail;
154         if (sock_getln(sock, buf, sizeof buf) < 0) goto bail;
155         lprintf(CTDL_DEBUG, ">%s\n", buf);
156 bail:   sock_close(sock);
157         if (msglist) free(msglist);
158 }
159
160
161 /*
162  * Scan a room's netconfig to determine whether it requires POP3 aggregation
163  */
164 void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
165 {
166         char filename[PATH_MAX];
167         char buf[1024];
168         char instr[32];
169         FILE *fp;
170         struct pop3aggr *pptr;
171
172         assoc_file_name(filename, sizeof filename, qrbuf, ctdl_netcfg_dir);
173
174         /* Only do net processing for rooms that have netconfigs */
175         fp = fopen(filename, "r");
176         if (fp == NULL) {
177                 return;
178         }
179
180         while (fgets(buf, sizeof buf, fp) != NULL) {
181                 buf[strlen(buf)-1] = 0;
182
183                 extract_token(instr, buf, 0, '|', sizeof instr);
184                 if (!strcasecmp(instr, "pop3client")) {
185                         pptr = (struct pop3aggr *) malloc(sizeof(struct pop3aggr));
186                         if (pptr != NULL) {
187                                 safestrncpy(pptr->roomname, qrbuf->QRname, sizeof pptr->roomname);
188                                 extract_token(pptr->pop3host, buf, 1, '|', sizeof pptr->pop3host);
189                                 extract_token(pptr->pop3user, buf, 2, '|', sizeof pptr->pop3user);
190                                 extract_token(pptr->pop3pass, buf, 3, '|', sizeof pptr->pop3pass);
191                                 pptr->next = palist;
192                                 palist = pptr;
193                         }
194                 }
195
196         }
197
198         fclose(fp);
199
200 }
201
202
203 void pop3client_scan(void) {
204         static time_t last_run = 0L;
205         static int doing_pop3client = 0;
206         struct pop3aggr *pptr;
207
208         /*
209          * Run POP3 aggregation no more frequently than once every n seconds
210          */
211         if ( (time(NULL) - last_run) < config.c_net_freq ) {
212                 return;
213         }
214
215         /*
216          * This is a simple concurrency check to make sure only one pop3client run
217          * is done at a time.  We could do this with a mutex, but since we
218          * don't really require extremely fine granularity here, we'll do it
219          * with a static variable instead.
220          */
221         if (doing_pop3client) return;
222         doing_pop3client = 1;
223
224         lprintf(CTDL_DEBUG, "pop3client started\n");
225         ForEachRoom(pop3client_scan_room, NULL);
226
227         while (palist != NULL) {
228                 pop3_do_fetching(palist->roomname, palist->pop3host, palist->pop3user, palist->pop3pass);
229                 pptr = palist;
230                 palist = palist->next;
231                 free(pptr);
232         }
233
234         lprintf(CTDL_DEBUG, "pop3client ended\n");
235         last_run = time(NULL);
236         doing_pop3client = 0;
237 }
238
239 #endif
240
241 CTDL_MODULE_INIT(pop3client)
242 {
243 #ifdef POP3_AGGREGATION
244         CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER);
245 #endif
246
247         /* return our Subversion id for the Log */
248         return "$Id:  $";
249 }