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