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