e5ba9bf3e1a30a330296f54596493454dd53be99
[citadel.git] / citadel / modules / pop3client / serv_pop3client.c
1 /*
2  * Consolidate mail from remote POP3 accounts.
3  *
4  * Copyright (c) 2007-2011 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
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  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  */
20
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <stdio.h>
24
25 #if TIME_WITH_SYS_TIME
26 # include <sys/time.h>
27 # include <time.h>
28 #else
29 # if HAVE_SYS_TIME_H
30 #  include <sys/time.h>
31 # else
32 #  include <time.h>
33 # endif
34 #endif
35
36 #include <ctype.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <libcitadel.h>
42 #include "citadel.h"
43 #include "server.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "ctdl_module.h"
48 #include "clientsocket.h"
49 #include "msgbase.h"
50 #include "internet_addressing.h"
51 #include "database.h"
52 #include "citadel_dirs.h"
53 #include "event_client.h"
54
55
56 #define POP3C_OK (strncasecmp(ChrPtr(RecvMsg->IO.IOBuf), "+OK", 3) == 0)
57 int Pop3ClientID = 0;
58 int POP3ClientDebugEnabled = 0;
59
60 #define N ((pop3aggr*)IO->Data)->n
61
62 #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (POP3ClientDebugEnabled != 0))
63
64 #define EVP3C_syslog(LEVEL, FORMAT, ...)                                \
65         DBGLOG(LEVEL) syslog(LEVEL,                                     \
66                              "IO[%ld]CC[%d][%ld]" FORMAT,               \
67                              IO->ID, CCID, N, __VA_ARGS__)
68
69 #define EVP3CM_syslog(LEVEL, FORMAT)                                    \
70         DBGLOG(LEVEL) syslog(LEVEL,                                     \
71                              "IO[%ld]CC[%d][%ld]" FORMAT,               \
72                              IO->ID, CCID, N)
73
74 #define EVP3CQ_syslog(LEVEL, FORMAT, ...)                               \
75         DBGLOG(LEVEL) syslog(LEVEL,                                     \
76                              "P3Q:" FORMAT,                             \
77                              __VA_ARGS__)
78
79 #define EVP3CQM_syslog(LEVEL, FORMAT)                                   \
80         DBGLOG(LEVEL) syslog(LEVEL,                                     \
81                              "P3Q" FORMAT                               \
82                 )
83
84 #define EVP3CCS_syslog(LEVEL, FORMAT, ...)                              \
85         DBGLOG(LEVEL) syslog(LEVEL, "IO[%ld][%ld]" FORMAT,              \
86                              IO->ID, N, __VA_ARGS__)
87
88 #define EVP3CCSM_syslog(LEVEL, FORMAT)                          \
89         DBGLOG(LEVEL) syslog(LEVEL, "IO[%ld][%ld]" FORMAT,      \
90                              IO->ID, N)
91
92 #define POP3C_DBG_SEND()                                                \
93         EVP3C_syslog(LOG_DEBUG,                                         \
94                      "POP3: > %s\n",                                    \
95                      ChrPtr(RecvMsg->IO.SendBuf.Buf))
96
97 #define POP3C_DBG_READ()                                \
98         EVP3C_syslog(LOG_DEBUG,                         \
99                      "POP3: < %s\n",                    \
100                      ChrPtr(RecvMsg->IO.IOBuf))
101
102
103 struct CitContext pop3_client_CC;
104
105 pthread_mutex_t POP3QueueMutex; /* locks the access to the following vars: */
106 HashList *POP3QueueRooms = NULL;
107 HashList *POP3FetchUrls = NULL;
108
109 typedef struct pop3aggr pop3aggr;
110 typedef eNextState(*Pop3ClientHandler)(pop3aggr* RecvMsg);
111
112 eNextState POP3_C_Shutdown(AsyncIO *IO);
113 eNextState POP3_C_Timeout(AsyncIO *IO);
114 eNextState POP3_C_ConnFail(AsyncIO *IO);
115 eNextState POP3_C_DNSFail(AsyncIO *IO);
116 eNextState POP3_C_DispatchReadDone(AsyncIO *IO);
117 eNextState POP3_C_DispatchWriteDone(AsyncIO *IO);
118 eNextState POP3_C_Terminate(AsyncIO *IO);
119 eReadState POP3_C_ReadServerStatus(AsyncIO *IO);
120 eNextState POP3_C_ReAttachToFetchMessages(AsyncIO *IO);
121
122 typedef struct __pop3_room_counter {
123         int count;
124         long QRnumber;
125 }pop3_room_counter;
126
127 typedef enum ePOP3_C_States {
128         ReadGreeting,
129         GetUserState,
130         GetPassState,
131         GetListCommandState,
132         GetListOneLine,
133         GetOneMessageIDState,
134         ReadMessageBodyFollowing,
135         ReadMessageBody,
136         GetDeleteState,
137         ReadQuitState,
138         POP3C_MaxRead
139 }ePOP3_C_States;
140
141
142 typedef struct _FetchItem {
143         long MSGID;
144         long MSGSize;
145         StrBuf *MsgUIDL;
146         StrBuf *MsgUID;
147         int NeedFetch;
148         struct CtdlMessage *Msg;
149 } FetchItem;
150
151 void HfreeFetchItem(void *vItem)
152 {
153         FetchItem *Item = (FetchItem*) vItem;
154         FreeStrBuf(&Item->MsgUIDL);
155         FreeStrBuf(&Item->MsgUID);
156         free(Item);
157 }
158
159 struct pop3aggr {
160         AsyncIO  IO;
161
162         long n;
163         double IOStart;
164         long count;
165         long RefCount;
166         DNSQueryParts HostLookup;
167
168         long             QRnumber;
169         HashList        *OtherQRnumbers;
170
171         StrBuf          *Url;
172         StrBuf *pop3user;
173         StrBuf *pop3pass;
174         StrBuf *Host;
175         StrBuf *RoomName; // TODO: fill me
176         int keep;
177         time_t interval;
178         ePOP3_C_States State;
179         HashList *MsgNumbers;
180         HashPos *Pos;
181         FetchItem *CurrMsg;
182 };
183
184 void DeletePOP3Aggregator(void *vptr)
185 {
186         pop3aggr *ptr = vptr;
187         DeleteHashPos(&ptr->Pos);
188         DeleteHash(&ptr->MsgNumbers);
189 //      FreeStrBuf(&ptr->rooms);
190         FreeStrBuf(&ptr->pop3user);
191         FreeStrBuf(&ptr->pop3pass);
192         FreeStrBuf(&ptr->Host);
193         FreeStrBuf(&ptr->RoomName);
194         FreeURL(&ptr->IO.ConnectMe);
195         FreeStrBuf(&ptr->Url);
196         FreeStrBuf(&ptr->IO.IOBuf);
197         FreeStrBuf(&ptr->IO.SendBuf.Buf);
198         FreeStrBuf(&ptr->IO.RecvBuf.Buf);
199         DeleteAsyncMsg(&ptr->IO.ReadMsg);
200         ((struct CitContext*)ptr->IO.CitContext)->state = CON_IDLE;
201         ((struct CitContext*)ptr->IO.CitContext)->kill_me = 1;
202         FreeAsyncIOContents(&ptr->IO);
203         free(ptr);
204 }
205
206 eNextState FinalizePOP3AggrRun(AsyncIO *IO)
207 {
208         HashPos  *It;
209         pop3aggr *cpptr = (pop3aggr *)IO->Data;
210
211         EVP3C_syslog(LOG_INFO,
212                      "%s@%s: fetched %ld new of %d messages in %fs. bye.",
213                      ChrPtr(cpptr->pop3user),
214                      ChrPtr(cpptr->Host),
215                      cpptr->count,
216                      GetCount(cpptr->MsgNumbers), 
217                      IO->Now - cpptr->IOStart 
218                 );
219
220         It = GetNewHashPos(POP3FetchUrls, 0);
221         pthread_mutex_lock(&POP3QueueMutex);
222         {
223                 if (GetHashPosFromKey(POP3FetchUrls, SKEY(cpptr->Url), It))
224                         DeleteEntryFromHash(POP3FetchUrls, It);
225         }
226         pthread_mutex_unlock(&POP3QueueMutex);
227         DeleteHashPos(&It);
228         return eAbort;
229 }
230
231 eNextState FailAggregationRun(AsyncIO *IO)
232 {
233         return eAbort;
234 }
235
236 eNextState POP3C_ReadGreeting(pop3aggr *RecvMsg)
237 {
238         AsyncIO *IO = &RecvMsg->IO;
239         POP3C_DBG_READ();
240         /* Read the server greeting */
241         if (!POP3C_OK) return eTerminateConnection;
242         else return eSendReply;
243 }
244
245 eNextState POP3C_SendUser(pop3aggr *RecvMsg)
246 {
247         AsyncIO *IO = &RecvMsg->IO;
248         /* Identify ourselves.  NOTE: we have to append a CR to each command.
249          *  The LF will automatically be appended by sock_puts().  Believe it
250          * or not, leaving out the CR will cause problems if the server happens
251          * to be Exchange, which is so b0rken it actually barfs on
252          * LF-terminated newlines.
253          */
254         StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
255                      "USER %s\r\n", ChrPtr(RecvMsg->pop3user));
256         POP3C_DBG_SEND();
257         return eReadMessage;
258 }
259
260 eNextState POP3C_GetUserState(pop3aggr *RecvMsg)
261 {
262         AsyncIO *IO = &RecvMsg->IO;
263         POP3C_DBG_READ();
264         if (!POP3C_OK) return eTerminateConnection;
265         else return eSendReply;
266 }
267
268 eNextState POP3C_SendPassword(pop3aggr *RecvMsg)
269 {
270         AsyncIO *IO = &RecvMsg->IO;
271         /* Password */
272         StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
273                      "PASS %s\r\n", ChrPtr(RecvMsg->pop3pass));
274         EVP3CM_syslog(LOG_DEBUG, "<PASS <password>\n");
275 //      POP3C_DBG_SEND(); No, we won't write the passvoid to syslog...
276         return eReadMessage;
277 }
278
279 eNextState POP3C_GetPassState(pop3aggr *RecvMsg)
280 {
281         AsyncIO *IO = &RecvMsg->IO;
282         POP3C_DBG_READ();
283         if (!POP3C_OK) return eTerminateConnection;
284         else return eSendReply;
285 }
286
287 eNextState POP3C_SendListCommand(pop3aggr *RecvMsg)
288 {
289         AsyncIO *IO = &RecvMsg->IO;
290         /* Get the list of messages */
291         StrBufPlain(RecvMsg->IO.SendBuf.Buf, HKEY("LIST\r\n"));
292         POP3C_DBG_SEND();
293         return eReadMessage;
294 }
295
296 eNextState POP3C_GetListCommandState(pop3aggr *RecvMsg)
297 {
298         AsyncIO *IO = &RecvMsg->IO;
299         POP3C_DBG_READ();
300         if (!POP3C_OK) return eTerminateConnection;
301         RecvMsg->MsgNumbers = NewHash(1, NULL);
302         RecvMsg->State++;
303         return eReadMore;
304 }
305
306
307 eNextState POP3C_GetListOneLine(pop3aggr *RecvMsg)
308 {
309         AsyncIO *IO = &RecvMsg->IO;
310 #if 0
311         int rc;
312 #endif
313         const char *pch;
314         FetchItem *OneMsg = NULL;
315         POP3C_DBG_READ();
316
317         if ((StrLength(RecvMsg->IO.IOBuf) == 1) &&
318             (ChrPtr(RecvMsg->IO.IOBuf)[0] == '.'))
319         {
320                 if (GetCount(RecvMsg->MsgNumbers) == 0)
321                 {
322                         ////    RecvMsg->Sate = ReadQuitState;
323                 }
324                 else
325                 {
326                         RecvMsg->Pos = GetNewHashPos(RecvMsg->MsgNumbers, 0);
327                 }
328                 return eSendReply;
329
330         }
331         OneMsg = (FetchItem*) malloc(sizeof(FetchItem));
332         memset(OneMsg, 0, sizeof(FetchItem));
333         OneMsg->MSGID = atol(ChrPtr(RecvMsg->IO.IOBuf));
334
335         pch = strchr(ChrPtr(RecvMsg->IO.IOBuf), ' ');
336         if (pch != NULL)
337         {
338                 OneMsg->MSGSize = atol(pch + 1);
339         }
340 #if 0
341         rc = TestValidateHash(RecvMsg->MsgNumbers);
342         if (rc != 0)
343                 EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
344 #endif
345
346         Put(RecvMsg->MsgNumbers, LKEY(OneMsg->MSGID), OneMsg, HfreeFetchItem);
347 #if 0
348         rc = TestValidateHash(RecvMsg->MsgNumbers);
349         if (rc != 0)
350                 EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
351 #endif
352         //RecvMsg->State --; /* read next Line */
353         return eReadMore;
354 }
355
356 eNextState POP3_FetchNetworkUsetableEntry(AsyncIO *IO)
357 {
358         long HKLen;
359         const char *HKey;
360         void *vData;
361         struct cdbdata *cdbut;
362         pop3aggr *RecvMsg = (pop3aggr *) IO->Data;
363
364         if((RecvMsg->Pos != NULL) &&
365            GetNextHashPos(RecvMsg->MsgNumbers,
366                           RecvMsg->Pos,
367                           &HKLen,
368                           &HKey,
369                           &vData))
370         {
371                 struct UseTable ut;
372                 if (server_shutting_down)
373                         return eAbort;
374
375                 RecvMsg->CurrMsg = (FetchItem*) vData;
376                 EVP3CCS_syslog(LOG_DEBUG,
377                                "CHECKING: whether %s has already been seen: ",
378                                ChrPtr(RecvMsg->CurrMsg->MsgUID));
379
380                 /* Find out if we've already seen this item */
381                 safestrncpy(ut.ut_msgid,
382                             ChrPtr(RecvMsg->CurrMsg->MsgUID),
383                             sizeof(ut.ut_msgid));
384                 ut.ut_timestamp = time(NULL);/// TODO: libev timestamp!
385
386                 cdbut = cdb_fetch(CDB_USETABLE, SKEY(RecvMsg->CurrMsg->MsgUID));
387                 if (cdbut != NULL) {
388                         /* Item has already been seen */
389                         EVP3CCSM_syslog(LOG_DEBUG, "YES\n");
390                         cdb_free(cdbut);
391
392                         /* rewrite the record anyway, to update the timestamp */
393                         cdb_store(CDB_USETABLE,
394                                   SKEY(RecvMsg->CurrMsg->MsgUID),
395                                   &ut, sizeof(struct UseTable) );
396                         RecvMsg->CurrMsg->NeedFetch = 0; ////TODO0;
397                 }
398                 else
399                 {
400                         EVP3CCSM_syslog(LOG_DEBUG, "NO\n");
401                         RecvMsg->CurrMsg->NeedFetch = 1;
402                 }
403                 return NextDBOperation(&RecvMsg->IO,
404                                        POP3_FetchNetworkUsetableEntry);
405         }
406         else
407         {
408                 /* ok, now we know them all,
409                  * continue with reading the actual messages. */
410                 DeleteHashPos(&RecvMsg->Pos);
411                 StopDBWatchers(IO);
412                 return QueueEventContext(IO, POP3_C_ReAttachToFetchMessages);
413         }
414 }
415
416 eNextState POP3C_GetOneMessagID(pop3aggr *RecvMsg)
417 {
418         AsyncIO *IO = &RecvMsg->IO;
419         long HKLen;
420         const char *HKey;
421         void *vData;
422
423 #if 0
424         int rc;
425         rc = TestValidateHash(RecvMsg->MsgNumbers);
426         if (rc != 0)
427                 EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
428 #endif
429         if((RecvMsg->Pos != NULL) &&
430            GetNextHashPos(RecvMsg->MsgNumbers,
431                           RecvMsg->Pos,
432                           &HKLen, &HKey,
433                           &vData))
434         {
435                 RecvMsg->CurrMsg = (FetchItem*) vData;
436                 /* Find out the UIDL of the message,
437                  * to determine whether we've already downloaded it */
438                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
439                              "UIDL %ld\r\n", RecvMsg->CurrMsg->MSGID);
440                 POP3C_DBG_SEND();
441         }
442         else
443         {
444                 RecvMsg->State++;
445                 DeleteHashPos(&RecvMsg->Pos);
446                 /// done receiving uidls.. start looking them up now.
447                 RecvMsg->Pos = GetNewHashPos(RecvMsg->MsgNumbers, 0);
448                 return QueueDBOperation(&RecvMsg->IO,
449                                         POP3_FetchNetworkUsetableEntry);
450         }
451         return eReadMore; /* TODO */
452 }
453
454 eNextState POP3C_GetOneMessageIDState(pop3aggr *RecvMsg)
455 {
456         AsyncIO *IO = &RecvMsg->IO;
457 #if 0
458         int rc;
459         rc = TestValidateHash(RecvMsg->MsgNumbers);
460         if (rc != 0)
461                 EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
462 #endif
463
464         POP3C_DBG_READ();
465         if (!POP3C_OK) return eTerminateConnection;
466         RecvMsg->CurrMsg->MsgUIDL =
467                 NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf));
468         RecvMsg->CurrMsg->MsgUID =
469                 NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf) * 2);
470
471         StrBufExtract_token(RecvMsg->CurrMsg->MsgUIDL,
472                             RecvMsg->IO.IOBuf, 2, ' ');
473
474         StrBufPrintf(RecvMsg->CurrMsg->MsgUID,
475                      "pop3/%s/%s:%s@%s",
476                      ChrPtr(RecvMsg->RoomName),
477                      ChrPtr(RecvMsg->CurrMsg->MsgUIDL),
478                      RecvMsg->IO.ConnectMe->User,
479                      RecvMsg->IO.ConnectMe->Host);
480         RecvMsg->State --;
481         return eSendReply;
482 }
483
484
485 eNextState POP3C_SendGetOneMsg(pop3aggr *RecvMsg)
486 {
487         AsyncIO *IO = &RecvMsg->IO;
488         long HKLen;
489         const char *HKey;
490         void *vData;
491
492         RecvMsg->CurrMsg = NULL;
493         while ((RecvMsg->Pos != NULL) && 
494                GetNextHashPos(RecvMsg->MsgNumbers,
495                               RecvMsg->Pos,
496                               &HKLen, &HKey,
497                               &vData) &&
498                (RecvMsg->CurrMsg = (FetchItem*) vData,
499                 RecvMsg->CurrMsg->NeedFetch == 0))
500         {}
501
502         if ((RecvMsg->CurrMsg != NULL ) && (RecvMsg->CurrMsg->NeedFetch == 1))
503         {
504                 /* Message has not been seen.
505                  * Tell the server to fetch the message... */
506                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
507                              "RETR %ld\r\n", RecvMsg->CurrMsg->MSGID);
508                 POP3C_DBG_SEND();
509                 return eReadMessage;
510         }
511         else {
512                 RecvMsg->State = ReadQuitState;
513                 return POP3_C_DispatchWriteDone(&RecvMsg->IO);
514         }
515 }
516
517
518 eNextState POP3C_ReadMessageBodyFollowing(pop3aggr *RecvMsg)
519 {
520         AsyncIO *IO = &RecvMsg->IO;
521         POP3C_DBG_READ();
522         if (!POP3C_OK) return eTerminateConnection;
523         RecvMsg->IO.ReadMsg = NewAsyncMsg(HKEY("."),
524                                           RecvMsg->CurrMsg->MSGSize,
525                                           config.c_maxmsglen,
526                                           NULL, -1,
527                                           1);
528
529         return eReadPayload;
530 }
531
532
533 eNextState POP3C_StoreMsgRead(AsyncIO *IO)
534 {
535         pop3aggr *RecvMsg = (pop3aggr *) IO->Data;
536         struct UseTable ut;
537
538         EVP3CCS_syslog(LOG_DEBUG,
539                        "MARKING: %s as seen: ",
540                        ChrPtr(RecvMsg->CurrMsg->MsgUID));
541
542         safestrncpy(ut.ut_msgid,
543                     ChrPtr(RecvMsg->CurrMsg->MsgUID),
544                     sizeof(ut.ut_msgid));
545         ut.ut_timestamp = time(NULL); /* TODO: use libev time */
546         cdb_store(CDB_USETABLE,
547                   ChrPtr(RecvMsg->CurrMsg->MsgUID),
548                   StrLength(RecvMsg->CurrMsg->MsgUID),
549                   &ut,
550                   sizeof(struct UseTable) );
551         StopDBWatchers(IO);
552         return QueueEventContext(&RecvMsg->IO, POP3_C_ReAttachToFetchMessages);
553 }
554 eNextState POP3C_SaveMsg(AsyncIO *IO)
555 {
556         long msgnum;
557         pop3aggr *RecvMsg = (pop3aggr *) IO->Data;
558
559         /* Do Something With It (tm) */
560         msgnum = CtdlSubmitMsg(RecvMsg->CurrMsg->Msg,
561                                NULL,
562                                ChrPtr(RecvMsg->RoomName),
563                                0);
564         if (msgnum > 0L)
565         {
566                 /* Message has been committed to the store
567                  * write the uidl to the use table
568                  * so we don't fetch this message again
569                  */
570         }
571         CtdlFreeMessage(RecvMsg->CurrMsg->Msg);
572
573         RecvMsg->count ++;
574         return NextDBOperation(&RecvMsg->IO, POP3C_StoreMsgRead);
575 }
576
577 eNextState POP3C_ReadMessageBody(pop3aggr *RecvMsg)
578 {
579         AsyncIO *IO = &RecvMsg->IO;
580         EVP3CM_syslog(LOG_DEBUG, "Converting message...");
581         RecvMsg->CurrMsg->Msg =
582                 convert_internet_message_buf(&RecvMsg->IO.ReadMsg->MsgBuf);
583         StopClientWatchers(IO, 0);
584         return QueueDBOperation(&RecvMsg->IO, POP3C_SaveMsg);
585 }
586
587 eNextState POP3C_SendDelete(pop3aggr *RecvMsg)
588 {
589         AsyncIO *IO = &RecvMsg->IO;
590         if (!RecvMsg->keep) {
591                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
592                              "DELE %ld\r\n", RecvMsg->CurrMsg->MSGID);
593                 POP3C_DBG_SEND();
594                 return eReadMessage;
595         }
596         else {
597                 RecvMsg->State = ReadMessageBodyFollowing;
598                 return POP3_C_DispatchWriteDone(&RecvMsg->IO);
599         }
600 }
601 eNextState POP3C_ReadDeleteState(pop3aggr *RecvMsg)
602 {
603         AsyncIO *IO = &RecvMsg->IO;
604         POP3C_DBG_READ();
605         RecvMsg->State = GetOneMessageIDState;
606         return eReadMessage;
607 }
608
609 eNextState POP3C_SendQuit(pop3aggr *RecvMsg)
610 {
611         AsyncIO *IO = &RecvMsg->IO;
612         /* Log out */
613         StrBufPlain(RecvMsg->IO.SendBuf.Buf,
614                     HKEY("QUIT\r\n3)"));
615         POP3C_DBG_SEND();
616         return eReadMessage;
617 }
618
619
620 eNextState POP3C_ReadQuitState(pop3aggr *RecvMsg)
621 {
622         AsyncIO *IO = &RecvMsg->IO;
623         POP3C_DBG_READ();
624         return eTerminateConnection;
625 }
626
627 const long POP3_C_ConnTimeout = 1000;
628 const long DefaultPOP3Port = 110;
629
630 Pop3ClientHandler POP3C_ReadHandlers[] = {
631         POP3C_ReadGreeting,
632         POP3C_GetUserState,
633         POP3C_GetPassState,
634         POP3C_GetListCommandState,
635         POP3C_GetListOneLine,
636         POP3C_GetOneMessageIDState,
637         POP3C_ReadMessageBodyFollowing,
638         POP3C_ReadMessageBody,
639         POP3C_ReadDeleteState,
640         POP3C_ReadQuitState,
641 };
642
643 const long POP3_C_SendTimeouts[POP3C_MaxRead] = {
644         100,
645         100,
646         100,
647         100,
648         100,
649         100,
650         100,
651         100
652 };
653 const ConstStr POP3C_ReadErrors[POP3C_MaxRead] = {
654         {HKEY("Connection broken during ")},
655         {HKEY("Connection broken during ")},
656         {HKEY("Connection broken during ")},
657         {HKEY("Connection broken during ")},
658         {HKEY("Connection broken during ")},
659         {HKEY("Connection broken during ")},
660         {HKEY("Connection broken during ")},
661         {HKEY("Connection broken during ")}
662 };
663
664 Pop3ClientHandler POP3C_SendHandlers[] = {
665         NULL, /* we don't send a greeting */
666         POP3C_SendUser,
667         POP3C_SendPassword,
668         POP3C_SendListCommand,
669         NULL,
670         POP3C_GetOneMessagID,
671         POP3C_SendGetOneMsg,
672         NULL,
673         POP3C_SendDelete,
674         POP3C_SendQuit
675 };
676
677 const long POP3_C_ReadTimeouts[] = {
678         100,
679         100,
680         100,
681         100,
682         100,
683         100,
684         100,
685         100,
686         100,
687         100
688 };
689 /*****************************************************************************/
690 /*                     POP3 CLIENT DISPATCHER                                */
691 /*****************************************************************************/
692
693 void POP3SetTimeout(eNextState NextTCPState, pop3aggr *pMsg)
694 {
695         AsyncIO *IO = &pMsg->IO;
696         double Timeout = 0.0;
697
698         EVP3C_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
699
700         switch (NextTCPState) {
701         case eSendFile:
702         case eSendReply:
703         case eSendMore:
704                 Timeout = POP3_C_SendTimeouts[pMsg->State];
705 /*
706   if (pMsg->State == eDATABody) {
707   / * if we're sending a huge message, we need more time. * /
708   Timeout += StrLength(pMsg->msgtext) / 1024;
709   }
710 */
711                 break;
712         case eReadFile:
713         case eReadMessage:
714                 Timeout = POP3_C_ReadTimeouts[pMsg->State];
715 /*
716   if (pMsg->State == eDATATerminateBody) {
717   / *
718   * some mailservers take a nap before accepting the message
719   * content inspection and such.
720   * /
721   Timeout += StrLength(pMsg->msgtext) / 1024;
722   }
723 */
724                 break;
725         case eReadPayload:
726                 Timeout = 100000;
727                 /* TODO!!! */
728                 break;
729         case eSendDNSQuery:
730         case eReadDNSReply:
731         case eConnect:
732         case eTerminateConnection:
733         case eDBQuery:
734         case eAbort:
735         case eReadMore://// TODO
736                 return;
737         }
738         SetNextTimeout(&pMsg->IO, Timeout);
739 }
740 eNextState POP3_C_DispatchReadDone(AsyncIO *IO)
741 {
742 /*      EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */
743         pop3aggr *pMsg = IO->Data;
744         eNextState rc;
745
746         rc = POP3C_ReadHandlers[pMsg->State](pMsg);
747         if (rc != eReadMore)
748             pMsg->State++;
749         POP3SetTimeout(rc, pMsg);
750         return rc;
751 }
752 eNextState POP3_C_DispatchWriteDone(AsyncIO *IO)
753 {
754         pop3aggr *pMsg = IO->Data;
755         eNextState rc;
756
757 /*      EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */
758         rc = POP3C_SendHandlers[pMsg->State](pMsg);
759         POP3SetTimeout(rc, pMsg);
760         return rc;
761 }
762
763
764 /*****************************************************************************/
765 /*                     POP3 CLIENT ERROR CATCHERS                            */
766 /*****************************************************************************/
767 eNextState POP3_C_Terminate(AsyncIO *IO)
768 {
769 ///     pop3aggr *pMsg = (pop3aggr *)IO->Data;
770
771         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
772         FinalizePOP3AggrRun(IO);
773         return eAbort;
774 }
775 eNextState POP3_C_TerminateDB(AsyncIO *IO)
776 {
777 ///     pop3aggr *pMsg = (pop3aggr *)IO->Data;
778
779         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
780         FinalizePOP3AggrRun(IO);
781         return eAbort;
782 }
783 eNextState POP3_C_Timeout(AsyncIO *IO)
784 {
785         pop3aggr *pMsg = IO->Data;
786
787         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
788         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
789         return FailAggregationRun(IO);
790 }
791 eNextState POP3_C_ConnFail(AsyncIO *IO)
792 {
793         pop3aggr *pMsg = (pop3aggr *)IO->Data;
794
795         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
796         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
797         return FailAggregationRun(IO);
798 }
799 eNextState POP3_C_DNSFail(AsyncIO *IO)
800 {
801         pop3aggr *pMsg = (pop3aggr *)IO->Data;
802
803         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
804         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
805         return FailAggregationRun(IO);
806 }
807 eNextState POP3_C_Shutdown(AsyncIO *IO)
808 {
809         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
810 ////    pop3aggr *pMsg = IO->Data;
811
812 ////pMsg->MyQEntry->Status = 3;
813 ///StrBufPlain(pMsg->MyQEntry->StatusMessage, HKEY("server shutdown during message retrieval."));
814         FinalizePOP3AggrRun(IO);
815         return eAbort;
816 }
817
818
819 /**
820  * @brief lineread Handler; understands when to read more POP3 lines,
821  *   and when this is a one-lined reply.
822  */
823 eReadState POP3_C_ReadServerStatus(AsyncIO *IO)
824 {
825         eReadState Finished = eBufferNotEmpty;
826
827         switch (IO->NextState) {
828         case eSendDNSQuery:
829         case eReadDNSReply:
830         case eDBQuery:
831         case eConnect:
832         case eTerminateConnection:
833         case eAbort:
834                 Finished = eReadFail;
835                 break;
836         case eSendFile:
837         case eSendReply:
838         case eSendMore:
839         case eReadMore:
840         case eReadMessage:
841                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
842                 break;
843         case eReadFile:
844         case eReadPayload:
845                 Finished = CtdlReadMessageBodyAsync(IO);
846                 break;
847         }
848         return Finished;
849 }
850
851 /*****************************************************************************
852  * So we connect our Server IP here.                                         *
853  *****************************************************************************/
854 eNextState POP3_C_ReAttachToFetchMessages(AsyncIO *IO)
855 {
856         pop3aggr *cpptr = IO->Data;
857
858         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
859 ////??? cpptr->State ++;
860         if (cpptr->Pos == NULL)
861                 cpptr->Pos = GetNewHashPos(cpptr->MsgNumbers, 0);
862
863         POP3_C_DispatchWriteDone(IO);
864         ReAttachIO(IO, cpptr, 0);
865         IO->NextState = eReadMessage;
866         return IO->NextState;
867 }
868
869 eNextState pop3_connect_ip(AsyncIO *IO)
870 {
871         pop3aggr *cpptr = IO->Data;
872
873         if (cpptr->IOStart == 0.0) /* whith or without DNS? */
874                 cpptr->IOStart = IO->Now;
875
876         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
877
878         return EvConnectSock(IO,
879                              POP3_C_ConnTimeout,
880                              POP3_C_ReadTimeouts[0],
881                              1);
882 }
883
884 eNextState pop3_get_one_host_ip_done(AsyncIO *IO)
885 {
886         pop3aggr *cpptr = IO->Data;
887         struct hostent *hostent;
888
889         QueryCbDone(IO);
890
891         hostent = cpptr->HostLookup.VParsedDNSReply;
892         if ((cpptr->HostLookup.DNSStatus == ARES_SUCCESS) && 
893             (hostent != NULL) ) {
894                 memset(&cpptr->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr));
895                 if (cpptr->IO.ConnectMe->IPv6) {
896                         memcpy(&cpptr->IO.ConnectMe->Addr.sin6_addr.s6_addr, 
897                                &hostent->h_addr_list[0],
898                                sizeof(struct in6_addr));
899
900                         cpptr->IO.ConnectMe->Addr.sin6_family =
901                                 hostent->h_addrtype;
902                         cpptr->IO.ConnectMe->Addr.sin6_port   =
903                                 htons(DefaultPOP3Port);
904                 }
905                 else {
906                         struct sockaddr_in *addr =
907                                 (struct sockaddr_in*)
908                                 &cpptr->IO.ConnectMe->Addr;
909
910                         memcpy(&addr->sin_addr.s_addr,
911                                hostent->h_addr_list[0],
912                                sizeof(uint32_t));
913
914                         addr->sin_family = hostent->h_addrtype;
915                         addr->sin_port   = htons(DefaultPOP3Port);
916                 }
917                 return pop3_connect_ip(IO);
918         }
919         else
920                 return eAbort;
921 }
922
923 eNextState pop3_get_one_host_ip(AsyncIO *IO)
924 {
925         pop3aggr *cpptr = IO->Data;
926
927         cpptr->IOStart = IO->Now;
928
929         EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
930
931         EVP3CCS_syslog(LOG_DEBUG, 
932                        "POP3 client[%ld]: looking up %s-Record %s : %d ...\n",
933                        cpptr->n,
934                        (cpptr->IO.ConnectMe->IPv6)? "aaaa": "a",
935                        cpptr->IO.ConnectMe->Host,
936                        cpptr->IO.ConnectMe->Port);
937
938         QueueQuery((cpptr->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a,
939                    cpptr->IO.ConnectMe->Host,
940                    &cpptr->IO,
941                    &cpptr->HostLookup,
942                    pop3_get_one_host_ip_done);
943         IO->NextState = eReadDNSReply;
944         return IO->NextState;
945 }
946
947
948
949 int pop3_do_fetching(pop3aggr *cpptr)
950 {
951         AsyncIO *IO = &cpptr->IO;
952
953         InitIOStruct(IO,
954                      cpptr,
955                      eReadMessage,
956                      POP3_C_ReadServerStatus,
957                      POP3_C_DNSFail,
958                      POP3_C_DispatchWriteDone,
959                      POP3_C_DispatchReadDone,
960                      POP3_C_Terminate,
961                      POP3_C_TerminateDB,
962                      POP3_C_ConnFail,
963                      POP3_C_Timeout,
964                      POP3_C_Shutdown);
965
966         safestrncpy(((CitContext *)cpptr->IO.CitContext)->cs_host,
967                     ChrPtr(cpptr->Url),
968                     sizeof(((CitContext *)cpptr->IO.CitContext)->cs_host));
969
970         if (cpptr->IO.ConnectMe->IsIP) {
971                 QueueEventContext(&cpptr->IO,
972                                   pop3_connect_ip);
973         }
974         else {
975                 QueueEventContext(&cpptr->IO,
976                                   pop3_get_one_host_ip);
977         }
978         return 1;
979 }
980
981 /*
982  * Scan a room's netconfig to determine whether it requires POP3 aggregation
983  */
984 void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
985 {
986         StrBuf *CfgData;
987         StrBuf *CfgType;
988         StrBuf *Line;
989
990         struct stat statbuf;
991         char filename[PATH_MAX];
992         int  fd;
993         int Done;
994         void *vptr;
995         const char *CfgPtr, *lPtr;
996         const char *Err;
997
998 //      pop3_room_counter *Count = NULL;
999 //      pop3aggr *cpptr;
1000
1001         pthread_mutex_lock(&POP3QueueMutex);
1002         if (GetHash(POP3QueueRooms, LKEY(qrbuf->QRnumber), &vptr))
1003         {
1004                 pthread_mutex_unlock(&POP3QueueMutex);
1005                 EVP3CQ_syslog(LOG_DEBUG,
1006                               "pop3client: [%ld] %s already in progress.",
1007                               qrbuf->QRnumber,
1008                               qrbuf->QRname);
1009                 return;
1010         }
1011         pthread_mutex_unlock(&POP3QueueMutex);
1012
1013         if (server_shutting_down) return;
1014
1015         assoc_file_name(filename, sizeof filename, qrbuf, ctdl_netcfg_dir);
1016
1017         if (server_shutting_down)
1018                 return;
1019
1020         /* Only do net processing for rooms that have netconfigs */
1021         fd = open(filename, 0);
1022         if (fd <= 0) {
1023                 return;
1024         }
1025         if (server_shutting_down)
1026                 return;
1027         if (fstat(fd, &statbuf) == -1) {
1028                 EVP3CQ_syslog(LOG_INFO,
1029                               "ERROR: could not stat configfile '%s' - %s",
1030                               filename,
1031                               strerror(errno));
1032                 return;
1033         }
1034         if (server_shutting_down)
1035                 return;
1036         CfgData = NewStrBufPlain(NULL, statbuf.st_size + 1);
1037         if (StrBufReadBLOB(CfgData, &fd, 1, statbuf.st_size, &Err) < 0) {
1038                 close(fd);
1039                 FreeStrBuf(&CfgData);
1040                 EVP3CQ_syslog(LOG_INFO,
1041                               "ERROR: reading config '%s' - %s",
1042                               filename, strerror(errno));
1043                 return;
1044         }
1045         close(fd);
1046         if (server_shutting_down)
1047                 return;
1048
1049         CfgPtr = NULL;
1050         CfgType = NewStrBuf();
1051         Line = NewStrBufPlain(NULL, StrLength(CfgData));
1052         Done = 0;
1053
1054         while (!Done)
1055         {
1056                 Done = StrBufSipLine(Line, CfgData, &CfgPtr) == 0;
1057                 if (StrLength(Line) > 0)
1058                 {
1059                         lPtr = NULL;
1060                         StrBufExtract_NextToken(CfgType, Line, &lPtr, '|');
1061                         if (!strcasecmp("pop3client", ChrPtr(CfgType)))
1062                         {
1063                                 pop3aggr *cptr;
1064 /*
1065                                 if (Count == NULL)
1066                                 {
1067                                 Count = malloc(sizeof(pop3_room_counter));
1068                                         Count->count = 0;
1069                                 }
1070                                 Count->count ++;
1071 */
1072                                 cptr = (pop3aggr *) malloc(sizeof(pop3aggr));
1073                                 memset(cptr, 0, sizeof(pop3aggr));
1074                                 ///TODO do we need this? cptr->roomlist_parts=1;
1075                                 cptr->RoomName =
1076                                         NewStrBufPlain(qrbuf->QRname, -1);
1077                                 cptr->pop3user =
1078                                         NewStrBufPlain(NULL, StrLength(Line));
1079                                 cptr->pop3pass =
1080                                         NewStrBufPlain(NULL, StrLength(Line));
1081                                 cptr->Url = NewStrBuf();
1082                                 cptr->Host =
1083                                         NewStrBufPlain(NULL, StrLength(Line));
1084
1085                                 StrBufExtract_NextToken(cptr->Host, Line, &lPtr, '|');
1086                                 StrBufExtract_NextToken(cptr->pop3user,
1087                                                         Line,
1088                                                         &lPtr,
1089                                                         '|');
1090
1091                                 StrBufExtract_NextToken(cptr->pop3pass,
1092                                                         Line,
1093                                                         &lPtr,
1094                                                         '|');
1095
1096                                 cptr->keep = StrBufExtractNext_long(Line,
1097                                                                     &lPtr,
1098                                                                     '|');
1099
1100                                 cptr->interval = StrBufExtractNext_long(Line,
1101                                                                         &lPtr,
1102                                                                         '|');
1103
1104                                 StrBufAppendBufPlain(cptr->Url, HKEY("pop3://"), 0);
1105                                 StrBufUrlescUPAppend(cptr->Url, cptr->pop3user, NULL);
1106                                 StrBufAppendBufPlain(cptr->Url, HKEY(":"), 0);
1107                                 StrBufUrlescUPAppend(cptr->Url, cptr->pop3pass, NULL);
1108                                 StrBufAppendBufPlain(cptr->Url, HKEY("@"), 0);
1109                                 StrBufAppendBuf(cptr->Url, cptr->Host, 0);
1110                                 StrBufAppendBufPlain(cptr->Url, HKEY("/"), 0);
1111                                 StrBufUrlescAppend(cptr->Url, cptr->RoomName, NULL);
1112
1113                                 ParseURL(&cptr->IO.ConnectMe, cptr->Url, 110);
1114
1115
1116 #if 0
1117 /* todo: we need to reunite the url to be shure. */
1118
1119                                 pthread_mutex_lock(&POP3ueueMutex);
1120                                 GetHash(POP3FetchUrls, SKEY(ptr->Url), &vptr);
1121                                 use_this_cptr = (pop3aggr *)vptr;
1122
1123                                 if (use_this_rncptr != NULL)
1124                                 {
1125                                         /* mustn't attach to an active session */
1126                                         if (use_this_cptr->RefCount > 0)
1127                                         {
1128                                                 DeletePOP3Cfg(cptr);
1129 ///                                             Count->count--;
1130                                         }
1131                                         else
1132                                         {
1133                                                 long *QRnumber;
1134                                                 StrBufAppendBufPlain(
1135                                                         use_this_cptr->rooms,
1136                                                         qrbuf->QRname,
1137                                                         -1, 0);
1138                                                 if (use_this_cptr->roomlist_parts == 1)
1139                                                 {
1140                                                         use_this_cptr->OtherQRnumbers
1141                                                                 = NewHash(1, lFlathash);
1142                                                 }
1143                                                 QRnumber = (long*)malloc(sizeof(long));
1144                                                 *QRnumber = qrbuf->QRnumber;
1145                                                 Put(use_this_cptr->OtherQRnumbers,
1146                                                     LKEY(qrbuf->QRnumber),
1147                                                     QRnumber,
1148                                                     NULL);
1149
1150                                                 use_this_cptr->roomlist_parts++;
1151                                         }
1152                                         pthread_mutex_unlock(&POP3QueueMutex);
1153                                         continue;
1154                                 }
1155                                 pthread_mutex_unlock(&RSSQueueMutex);
1156 #endif
1157                                 cptr->n = Pop3ClientID++;
1158                                 pthread_mutex_lock(&POP3QueueMutex);
1159                                 Put(POP3FetchUrls,
1160                                     SKEY(cptr->Url),
1161                                     cptr,
1162                                     DeletePOP3Aggregator);
1163
1164                                 pthread_mutex_unlock(&POP3QueueMutex);
1165
1166                         }
1167
1168                 }
1169
1170                 ///fclose(fp);
1171
1172         }
1173         FreeStrBuf(&Line);
1174         FreeStrBuf(&CfgType);
1175         FreeStrBuf(&CfgData);
1176 }
1177
1178 static int doing_pop3client = 0;
1179
1180 void pop3client_scan(void) {
1181         static time_t last_run = 0L;
1182         time_t fastest_scan;
1183         HashPos *it;
1184         long len;
1185         const char *Key;
1186         void *vrptr;
1187         pop3aggr *cptr;
1188
1189         become_session(&pop3_client_CC);
1190
1191         if (config.c_pop3_fastest < config.c_pop3_fetch)
1192                 fastest_scan = config.c_pop3_fastest;
1193         else
1194                 fastest_scan = config.c_pop3_fetch;
1195
1196         /*
1197          * Run POP3 aggregation no more frequently than once every n seconds
1198          */
1199         if ( (time(NULL) - last_run) < fastest_scan ) {
1200                 return;
1201         }
1202
1203         /*
1204          * This is a simple concurrency check to make sure only one pop3client
1205          * run is done at a time.  We could do this with a mutex, but since we
1206          * don't really require extremely fine granularity here, we'll do it
1207          * with a static variable instead.
1208          */
1209         if (doing_pop3client) return;
1210         doing_pop3client = 1;
1211
1212         EVP3CQM_syslog(LOG_DEBUG, "pop3client started");
1213         CtdlForEachRoom(pop3client_scan_room, NULL);
1214
1215         pthread_mutex_lock(&POP3QueueMutex);
1216         it = GetNewHashPos(POP3FetchUrls, 0);
1217         while (!server_shutting_down &&
1218                GetNextHashPos(POP3FetchUrls, it, &len, &Key, &vrptr) &&
1219                (vrptr != NULL)) {
1220                 cptr = (pop3aggr *)vrptr;
1221                 if (cptr->RefCount == 0)
1222                         if (!pop3_do_fetching(cptr))
1223                                 DeletePOP3Aggregator(cptr);////TODO
1224
1225 /*
1226         if ((palist->interval && time(NULL) > (last_run + palist->interval))
1227                         || (time(NULL) > last_run + config.c_pop3_fetch))
1228                         pop3_do_fetching(palist->roomname, palist->pop3host,
1229                         palist->pop3user, palist->pop3pass, palist->keep);
1230                 pptr = palist;
1231                 palist = palist->next;
1232                 free(pptr);
1233 */
1234         }
1235         DeleteHashPos(&it);
1236         pthread_mutex_unlock(&POP3QueueMutex);
1237
1238         EVP3CQM_syslog(LOG_DEBUG, "pop3client ended");
1239         last_run = time(NULL);
1240         doing_pop3client = 0;
1241 }
1242
1243
1244 void pop3_cleanup(void)
1245 {
1246         /* citthread_mutex_destroy(&POP3QueueMutex); TODO */
1247         while (doing_pop3client != 0) ;
1248         DeleteHash(&POP3FetchUrls);
1249         DeleteHash(&POP3QueueRooms);
1250 }
1251
1252
1253
1254 void LogDebugEnablePOP3Client(const int n)
1255 {
1256         POP3ClientDebugEnabled = n;
1257 }
1258
1259 CTDL_MODULE_INIT(pop3client)
1260 {
1261         if (!threading)
1262         {
1263                 CtdlFillSystemContext(&pop3_client_CC, "POP3aggr");
1264                 pthread_mutex_init(&POP3QueueMutex, NULL);
1265                 POP3QueueRooms = NewHash(1, lFlathash);
1266                 POP3FetchUrls = NewHash(1, NULL);
1267                 CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
1268                 CtdlRegisterEVCleanupHook(pop3_cleanup);
1269                 CtdlRegisterDebugFlagHook(HKEY("pop3client"), LogDebugEnablePOP3Client, &POP3ClientDebugEnabled);
1270         }
1271
1272         /* return our module id for the log */
1273         return "pop3client";
1274 }