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