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