Eager-zero and compress CDB_USETABLE records to save disk space.
[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(RecvMsg->CurrMsg->MsgUID,
378                         EvGetNow(IO),
379                         EvGetNow(IO) - USETABLE_ANTIEXPIRE,
380                         eCheckUpdate
381                 );
382                 if (seenstamp != 0)
383                 {
384                         /* Item has already been seen */
385                         RecvMsg->CurrMsg->NeedFetch = 0;
386                 }
387                 else
388                 {
389                         syslog(LOG_DEBUG, "NO\n");
390                         RecvMsg->CurrMsg->NeedFetch = 1;
391                 }
392                 return NextDBOperation(&RecvMsg->IO,
393                                        POP3_FetchNetworkUsetableEntry);
394         }
395         else
396         {
397                 /* ok, now we know them all,
398                  * continue with reading the actual messages. */
399                 DeleteHashPos(&RecvMsg->Pos);
400                 return DBQueueEventContext(IO, POP3_C_ReAttachToFetchMessages);
401         }
402 }
403
404 eNextState POP3C_GetOneMessagID(pop3aggr *RecvMsg)
405 {
406         AsyncIO *IO = &RecvMsg->IO;
407         long HKLen;
408         const char *HKey;
409         void *vData;
410
411         SetPOP3State(IO, eGetMsgID);
412 #if 0
413         int rc;
414         rc = TestValidateHash(RecvMsg->MsgNumbers);
415         if (rc != 0)
416                 syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
417 #endif
418         if((RecvMsg->Pos != NULL) &&
419            GetNextHashPos(RecvMsg->MsgNumbers,
420                           RecvMsg->Pos,
421                           &HKLen, &HKey,
422                           &vData))
423         {
424                 RecvMsg->CurrMsg = (FetchItem*) vData;
425                 /* Find out the UIDL of the message,
426                  * to determine whether we've already downloaded it */
427                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf,
428                              "UIDL %ld\r\n", RecvMsg->CurrMsg->MSGID);
429         }
430         else
431         {
432                 RecvMsg->State++;
433                 DeleteHashPos(&RecvMsg->Pos);
434                 /// done receiving uidls.. start looking them up now.
435                 RecvMsg->Pos = GetNewHashPos(RecvMsg->MsgNumbers, 0);
436                 return EventQueueDBOperation(&RecvMsg->IO,
437                                              POP3_FetchNetworkUsetableEntry,
438                                              0);
439         }
440         return eReadMore; /* TODO */
441 }
442
443 eNextState POP3C_GetOneMessageIDState(pop3aggr *RecvMsg)
444 {
445 #if 0
446         int rc;
447         rc = TestValidateHash(RecvMsg->MsgNumbers);
448         if (rc != 0)
449                 syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc);
450 #endif
451
452         if (!POP3C_OK) return eTerminateConnection;
453         RecvMsg->CurrMsg->MsgUIDL = NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf));
454         RecvMsg->CurrMsg->MsgUID = NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf) * 2);
455
456         StrBufExtract_token(RecvMsg->CurrMsg->MsgUIDL, RecvMsg->IO.IOBuf, 2, ' ');
457
458         StrBufPrintf(RecvMsg->CurrMsg->MsgUID,
459                      "pop3/%s/%s:%s@%s",
460                      ChrPtr(RecvMsg->RoomName),
461                      ChrPtr(RecvMsg->CurrMsg->MsgUIDL),
462                      RecvMsg->IO.ConnectMe->User,
463                      RecvMsg->IO.ConnectMe->Host
464         );
465         RecvMsg->State --;
466         return eSendReply;
467 }
468
469
470 eNextState POP3C_SendGetOneMsg(pop3aggr *RecvMsg)
471 {
472         AsyncIO *IO = &RecvMsg->IO;
473         long HKLen;
474         const char *HKey;
475         void *vData;
476
477         SetPOP3State(IO, eGetMsg);
478
479         syslog(LOG_DEBUG, "fast forwarding to the next unknown message");
480
481         RecvMsg->CurrMsg = NULL;
482         while ((RecvMsg->Pos != NULL) && 
483                GetNextHashPos(RecvMsg->MsgNumbers,
484                               RecvMsg->Pos,
485                               &HKLen, &HKey,
486                               &vData) &&
487                (RecvMsg->CurrMsg = (FetchItem*) vData,
488                 RecvMsg->CurrMsg->NeedFetch == 0))
489         {}
490
491         if ((RecvMsg->CurrMsg != NULL ) && (RecvMsg->CurrMsg->NeedFetch == 1))
492         {
493                 syslog(LOG_DEBUG, "fetching next");
494                 /* Message has not been seen.
495                  * Tell the server to fetch the message... */
496                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "RETR %ld\r\n", RecvMsg->CurrMsg->MSGID);
497                 return eReadMessage;
498         }
499         else {
500                 syslog(LOG_DEBUG, "no more messages to fetch.");
501                 RecvMsg->State = ReadQuitState;
502                 return POP3_C_DispatchWriteDone(&RecvMsg->IO);
503         }
504 }
505
506
507 eNextState POP3C_ReadMessageBodyFollowing(pop3aggr *RecvMsg)
508 {
509         if (!POP3C_OK) return eTerminateConnection;
510         RecvMsg->IO.ReadMsg = NewAsyncMsg(HKEY("."),
511                 RecvMsg->CurrMsg->MSGSize,
512                 CtdlGetConfigLong("c_maxmsglen"),
513                 NULL, -1,
514                 1
515         );
516
517         return eReadPayload;
518 }
519
520
521 eNextState POP3C_StoreMsgRead(AsyncIO *IO)
522 {
523         pop3aggr *RecvMsg = (pop3aggr *) IO->Data;
524
525         SetPOP3State(IO, eStoreMsg);
526
527         syslog(LOG_DEBUG, "MARKING: %s as seen: ", ChrPtr(RecvMsg->CurrMsg->MsgUID));
528         CheckIfAlreadySeen(RecvMsg->CurrMsg->MsgUID, EvGetNow(IO), EvGetNow(IO) - USETABLE_ANTIEXPIRE, eWrite);
529
530         return DBQueueEventContext(&RecvMsg->IO, POP3_C_ReAttachToFetchMessages);
531 }
532
533
534 eNextState POP3C_SaveMsg(AsyncIO *IO)
535 {
536         long msgnum;
537         pop3aggr *RecvMsg = (pop3aggr *) IO->Data;
538
539         /* Do Something With It (tm) */
540         msgnum = CtdlSubmitMsg(RecvMsg->CurrMsg->Msg,
541                                NULL,
542                                ChrPtr(RecvMsg->RoomName),
543                                0);
544         if (msgnum > 0L)
545         {
546                 /* Message has been committed to the store
547                  * write the uidl to the use table
548                  * so we don't fetch this message again
549                  */
550         }
551         CM_Free(RecvMsg->CurrMsg->Msg);
552
553         RecvMsg->count ++;
554         return NextDBOperation(&RecvMsg->IO, POP3C_StoreMsgRead);
555 }
556
557
558 eNextState POP3C_ReadMessageBody(pop3aggr *RecvMsg)
559 {
560         syslog(LOG_DEBUG, "Converting message...");
561         RecvMsg->CurrMsg->Msg = convert_internet_message_buf(&RecvMsg->IO.ReadMsg->MsgBuf);
562         return EventQueueDBOperation(&RecvMsg->IO, POP3C_SaveMsg, 0);
563 }
564
565
566 eNextState POP3C_SendDelete(pop3aggr *RecvMsg)
567 {
568         AsyncIO *IO = &RecvMsg->IO;
569
570         SetPOP3State(IO, eDelete);
571
572         if (!RecvMsg->keep) {
573                 StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "DELE %ld\r\n", RecvMsg->CurrMsg->MSGID);
574                 return eReadMessage;
575         }
576         else {
577                 RecvMsg->State = ReadMessageBodyFollowing;
578                 return POP3_C_DispatchWriteDone(&RecvMsg->IO);
579         }
580 }
581
582
583 eNextState POP3C_ReadDeleteState(pop3aggr *RecvMsg)
584 {
585         RecvMsg->State = GetOneMessageIDState;
586         return POP3_C_DispatchWriteDone(&RecvMsg->IO);
587 }
588
589
590 eNextState POP3C_SendQuit(pop3aggr *RecvMsg)
591 {
592         AsyncIO *IO = &RecvMsg->IO;
593         SetPOP3State(IO, eQuit);
594
595         /* Log out */
596         StrBufPlain(RecvMsg->IO.SendBuf.Buf,
597                     HKEY("QUIT\r\n3)"));
598         return eReadMessage;
599 }
600
601
602 eNextState POP3C_ReadQuitState(pop3aggr *RecvMsg)
603 {
604         return eTerminateConnection;
605 }
606
607
608 const long POP3_C_ConnTimeout = 1000;
609 const long DefaultPOP3Port = 110;
610
611 Pop3ClientHandler POP3C_ReadHandlers[] = {
612         POP3C_ReadGreeting,
613         POP3C_GetUserState,
614         POP3C_GetPassState,
615         POP3C_GetListCommandState,
616         POP3C_GetListOneLine,
617         POP3C_GetOneMessageIDState,
618         POP3C_ReadMessageBodyFollowing,
619         POP3C_ReadMessageBody,
620         POP3C_ReadDeleteState,
621         POP3C_ReadQuitState,
622 };
623
624 const long POP3_C_SendTimeouts[POP3C_MaxRead] = {
625         100,
626         100,
627         100,
628         100,
629         100,
630         100,
631         100,
632         100
633 };
634 const ConstStr POP3C_ReadErrors[POP3C_MaxRead] = {
635         {HKEY("Connection broken during ")},
636         {HKEY("Connection broken during ")},
637         {HKEY("Connection broken during ")},
638         {HKEY("Connection broken during ")},
639         {HKEY("Connection broken during ")},
640         {HKEY("Connection broken during ")},
641         {HKEY("Connection broken during ")},
642         {HKEY("Connection broken during ")}
643 };
644
645 Pop3ClientHandler POP3C_SendHandlers[] = {
646         NULL, /* we don't send a greeting */
647         POP3C_SendUser,
648         POP3C_SendPassword,
649         POP3C_SendListCommand,
650         NULL,
651         POP3C_GetOneMessagID,
652         POP3C_SendGetOneMsg,
653         NULL,
654         POP3C_SendDelete,
655         POP3C_SendQuit
656 };
657
658 const long POP3_C_ReadTimeouts[] = {
659         100,
660         100,
661         100,
662         100,
663         100,
664         100,
665         100,
666         100,
667         100,
668         100
669 };
670 /*****************************************************************************/
671 /*                     POP3 CLIENT DISPATCHER                                */
672 /*****************************************************************************/
673
674 void POP3SetTimeout(eNextState NextTCPState, pop3aggr *pMsg)
675 {
676         double Timeout = 0.0;
677
678         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
679
680         switch (NextTCPState) {
681         case eSendFile:
682         case eSendReply:
683         case eSendMore:
684                 Timeout = POP3_C_SendTimeouts[pMsg->State];
685 /*
686   if (pMsg->State == eDATABody) {
687   / * if we're sending a huge message, we need more time. * /
688   Timeout += StrLength(pMsg->msgtext) / 1024;
689   }
690 */
691                 break;
692         case eReadFile:
693         case eReadMessage:
694                 Timeout = POP3_C_ReadTimeouts[pMsg->State];
695 /*
696   if (pMsg->State == eDATATerminateBody) {
697   / *
698   * some mailservers take a nap before accepting the message
699   * content inspection and such.
700   * /
701   Timeout += StrLength(pMsg->msgtext) / 1024;
702   }
703 */
704                 break;
705         case eReadPayload:
706                 Timeout = 100000;
707                 /* TODO!!! */
708                 break;
709         case eSendDNSQuery:
710         case eReadDNSReply:
711         case eConnect:
712         case eTerminateConnection:
713         case eDBQuery:
714         case eAbort:
715         case eReadMore://// TODO
716                 return;
717         }
718         SetNextTimeout(&pMsg->IO, Timeout);
719 }
720 eNextState POP3_C_DispatchReadDone(AsyncIO *IO)
721 {
722 /*      syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */
723         pop3aggr *pMsg = IO->Data;
724         eNextState rc;
725
726         rc = POP3C_ReadHandlers[pMsg->State](pMsg);
727         if (rc != eReadMore)
728             pMsg->State++;
729         POP3SetTimeout(rc, pMsg);
730         return rc;
731 }
732 eNextState POP3_C_DispatchWriteDone(AsyncIO *IO)
733 {
734         pop3aggr *pMsg = IO->Data;
735         eNextState rc;
736
737 /*      syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */
738         rc = POP3C_SendHandlers[pMsg->State](pMsg);
739         POP3SetTimeout(rc, pMsg);
740         return rc;
741 }
742
743
744 /*****************************************************************************/
745 /*                     POP3 CLIENT ERROR CATCHERS                            */
746 /*****************************************************************************/
747 eNextState POP3_C_Terminate(AsyncIO *IO)
748 {
749         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
750         FinalizePOP3AggrRun(IO);
751         return eAbort;
752 }
753 eNextState POP3_C_TerminateDB(AsyncIO *IO)
754 {
755         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
756         FinalizePOP3AggrRun(IO);
757         return eAbort;
758 }
759 eNextState POP3_C_Timeout(AsyncIO *IO)
760 {
761         pop3aggr *pMsg = IO->Data;
762
763         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
764         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
765         return FailAggregationRun(IO);
766 }
767 eNextState POP3_C_ConnFail(AsyncIO *IO)
768 {
769         pop3aggr *pMsg = (pop3aggr *)IO->Data;
770
771         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
772         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
773         return FailAggregationRun(IO);
774 }
775 eNextState POP3_C_DNSFail(AsyncIO *IO)
776 {
777         pop3aggr *pMsg = (pop3aggr *)IO->Data;
778
779         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
780         StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State]));
781         return FailAggregationRun(IO);
782 }
783 eNextState POP3_C_Shutdown(AsyncIO *IO)
784 {
785         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
786         FinalizePOP3AggrRun(IO);
787         return eAbort;
788 }
789
790
791 /*
792  * lineread Handler; understands when to read more POP3 lines, and when this is a one-lined reply.
793  */
794 eReadState POP3_C_ReadServerStatus(AsyncIO *IO)
795 {
796         eReadState Finished = eBufferNotEmpty;
797
798         switch (IO->NextState) {
799         case eSendDNSQuery:
800         case eReadDNSReply:
801         case eDBQuery:
802         case eConnect:
803         case eTerminateConnection:
804         case eAbort:
805                 Finished = eReadFail;
806                 break;
807         case eSendFile:
808         case eSendReply:
809         case eSendMore:
810         case eReadMore:
811         case eReadMessage:
812                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
813                 break;
814         case eReadFile:
815         case eReadPayload:
816                 Finished = CtdlReadMessageBodyAsync(IO);
817                 break;
818         }
819         return Finished;
820 }
821
822 /*****************************************************************************
823  * So we connect our Server IP here.                                         *
824  *****************************************************************************/
825 eNextState POP3_C_ReAttachToFetchMessages(AsyncIO *IO)
826 {
827         pop3aggr *cpptr = IO->Data;
828
829         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
830 ////??? cpptr->State ++;
831         if (cpptr->Pos == NULL)
832                 cpptr->Pos = GetNewHashPos(cpptr->MsgNumbers, 0);
833
834         POP3_C_DispatchWriteDone(IO);
835         ReAttachIO(IO, cpptr, 0);
836         IO->NextState = eReadMessage;
837         return IO->NextState;
838 }
839
840 eNextState pop3_connect_ip(AsyncIO *IO)
841 {
842         pop3aggr *cpptr = IO->Data;
843
844         if (cpptr->IOStart == 0.0) /* whith or without DNS? */
845                 cpptr->IOStart = IO->Now;
846
847         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
848
849         return EvConnectSock(IO,
850                              POP3_C_ConnTimeout,
851                              POP3_C_ReadTimeouts[0],
852                              1);
853 }
854
855 eNextState pop3_get_one_host_ip_done(AsyncIO *IO)
856 {
857         pop3aggr *cpptr = IO->Data;
858         struct hostent *hostent;
859
860         QueryCbDone(IO);
861
862         hostent = cpptr->HostLookup.VParsedDNSReply;
863         if ((cpptr->HostLookup.DNSStatus == ARES_SUCCESS) && 
864             (hostent != NULL) ) {
865                 memset(&cpptr->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr));
866                 if (cpptr->IO.ConnectMe->IPv6) {
867                         memcpy(&cpptr->IO.ConnectMe->Addr.sin6_addr.s6_addr, 
868                                &hostent->h_addr_list[0],
869                                sizeof(struct in6_addr));
870
871                         cpptr->IO.ConnectMe->Addr.sin6_family =
872                                 hostent->h_addrtype;
873                         cpptr->IO.ConnectMe->Addr.sin6_port   =
874                                 htons(DefaultPOP3Port);
875                 }
876                 else {
877                         struct sockaddr_in *addr =
878                                 (struct sockaddr_in*)
879                                 &cpptr->IO.ConnectMe->Addr;
880
881                         memcpy(&addr->sin_addr.s_addr,
882                                hostent->h_addr_list[0],
883                                sizeof(uint32_t));
884
885                         addr->sin_family = hostent->h_addrtype;
886                         addr->sin_port   = htons(DefaultPOP3Port);
887                 }
888                 return pop3_connect_ip(IO);
889         }
890         else
891                 return eAbort;
892 }
893
894 eNextState pop3_get_one_host_ip(AsyncIO *IO)
895 {
896         pop3aggr *cpptr = IO->Data;
897
898         cpptr->IOStart = IO->Now;
899
900         syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__);
901
902         syslog(LOG_DEBUG, 
903                        "POP3 client[%ld]: looking up %s-Record %s : %d ...\n",
904                        cpptr->n,
905                        (cpptr->IO.ConnectMe->IPv6)? "aaaa": "a",
906                        cpptr->IO.ConnectMe->Host,
907                        cpptr->IO.ConnectMe->Port);
908
909         QueueQuery((cpptr->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a,
910                    cpptr->IO.ConnectMe->Host,
911                    &cpptr->IO,
912                    &cpptr->HostLookup,
913                    pop3_get_one_host_ip_done);
914         IO->NextState = eReadDNSReply;
915         return IO->NextState;
916 }
917
918
919
920 int pop3_do_fetching(pop3aggr *cpptr)
921 {
922         AsyncIO *IO = &cpptr->IO;
923
924         InitIOStruct(IO,
925                      cpptr,
926                      eReadMessage,
927                      POP3_C_ReadServerStatus,
928                      POP3_C_DNSFail,
929                      POP3_C_DispatchWriteDone,
930                      POP3_C_DispatchReadDone,
931                      POP3_C_Terminate,
932                      POP3_C_TerminateDB,
933                      POP3_C_ConnFail,
934                      POP3_C_Timeout,
935                      POP3_C_Shutdown);
936
937         safestrncpy(((CitContext *)cpptr->IO.CitContext)->cs_host,
938                     ChrPtr(cpptr->Url),
939                     sizeof(((CitContext *)cpptr->IO.CitContext)->cs_host));
940
941         if (cpptr->IO.ConnectMe->IsIP) {
942                 QueueEventContext(&cpptr->IO,
943                                   pop3_connect_ip);
944         }
945         else {
946                 QueueEventContext(&cpptr->IO,
947                                   pop3_get_one_host_ip);
948         }
949         return 1;
950 }
951
952 /*
953  * Scan a room's netconfig to determine whether it requires POP3 aggregation
954  */
955 void pop3client_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG)
956 {
957         const RoomNetCfgLine *pLine;
958         void *vptr;
959
960         pthread_mutex_lock(&POP3QueueMutex);
961         if (GetHash(POP3QueueRooms, LKEY(qrbuf->QRnumber), &vptr))
962         {
963                 pthread_mutex_unlock(&POP3QueueMutex);
964                 syslog(LOG_DEBUG,
965                               "pop3client: [%ld] %s already in progress.",
966                               qrbuf->QRnumber,
967                               qrbuf->QRname);
968                 return;
969         }
970         pthread_mutex_unlock(&POP3QueueMutex);
971
972         if (server_shutting_down) return;
973
974         pLine = OneRNCFG->NetConfigs[pop3client];
975
976         while (pLine != NULL)
977         {
978                 pop3aggr *cptr;
979
980                 cptr = (pop3aggr *) malloc(sizeof(pop3aggr));
981                 memset(cptr, 0, sizeof(pop3aggr));
982                 ///TODO do we need this? cptr->roomlist_parts=1;
983                 cptr->RoomName = NewStrBufPlain(qrbuf->QRname, -1);
984                 cptr->pop3user = NewStrBufDup(pLine->Value[1]);
985                 cptr->pop3pass = NewStrBufDup(pLine->Value[2]);
986                 cptr->Url = NewStrBuf();
987                 cptr->Host = NewStrBufDup(pLine->Value[0]);
988
989                 cptr->keep = atol(ChrPtr(pLine->Value[3]));
990                 cptr->interval = atol(ChrPtr(pLine->Value[4]));
991
992                 StrBufAppendBufPlain(cptr->Url, HKEY("pop3://"), 0);
993                 StrBufUrlescUPAppend(cptr->Url, cptr->pop3user, NULL);
994                 StrBufAppendBufPlain(cptr->Url, HKEY(":"), 0);
995                 StrBufUrlescUPAppend(cptr->Url, cptr->pop3pass, NULL);
996                 StrBufAppendBufPlain(cptr->Url, HKEY("@"), 0);
997                 StrBufAppendBuf(cptr->Url, cptr->Host, 0);
998                 StrBufAppendBufPlain(cptr->Url, HKEY("/"), 0);
999                 StrBufUrlescAppend(cptr->Url, cptr->RoomName, NULL);
1000
1001                 ParseURL(&cptr->IO.ConnectMe, cptr->Url, 110);
1002
1003                 cptr->n = Pop3ClientID++;
1004                 pthread_mutex_lock(&POP3QueueMutex);
1005                 Put(POP3FetchUrls,
1006                     SKEY(cptr->Url),
1007                     cptr,
1008                     DeletePOP3Aggregator);
1009
1010                 pthread_mutex_unlock(&POP3QueueMutex);
1011                 pLine = pLine->next;
1012
1013         }
1014 }
1015
1016 static int doing_pop3client = 0;
1017
1018 void pop3client_scan(void) {
1019         static time_t last_run = 0L;
1020         time_t fastest_scan;
1021         HashPos *it;
1022         long len;
1023         const char *Key;
1024         void *vrptr;
1025         pop3aggr *cptr;
1026
1027         become_session(&pop3_client_CC);
1028
1029         if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch"))
1030                 fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
1031         else
1032                 fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
1033
1034         /*
1035          * Run POP3 aggregation no more frequently than once every n seconds
1036          */
1037         if ( (time(NULL) - last_run) < fastest_scan ) {
1038                 return;
1039         }
1040
1041         /*
1042          * This is a simple concurrency check to make sure only one pop3client
1043          * run is done at a time.  We could do this with a mutex, but since we
1044          * don't really require extremely fine granularity here, we'll do it
1045          * with a static variable instead.
1046          */
1047         if (doing_pop3client) return;
1048         doing_pop3client = 1;
1049
1050         syslog(LOG_DEBUG, "pop3client started");
1051         CtdlForEachNetCfgRoom(pop3client_scan_room, NULL);
1052
1053         pthread_mutex_lock(&POP3QueueMutex);
1054         it = GetNewHashPos(POP3FetchUrls, 0);
1055         while (!server_shutting_down &&
1056                GetNextHashPos(POP3FetchUrls, it, &len, &Key, &vrptr) &&
1057                (vrptr != NULL)) {
1058                 cptr = (pop3aggr *)vrptr;
1059                 if (cptr->RefCount == 0) {
1060                         if (!pop3_do_fetching(cptr)) {
1061                                 DeletePOP3Aggregator(cptr);////TODO
1062                         }
1063                 }
1064         }
1065         DeleteHashPos(&it);
1066         pthread_mutex_unlock(&POP3QueueMutex);
1067
1068         syslog(LOG_DEBUG, "pop3client ended");
1069         last_run = time(NULL);
1070         doing_pop3client = 0;
1071 }
1072
1073
1074 void pop3_cleanup(void)
1075 {
1076         /* citthread_mutex_destroy(&POP3QueueMutex); TODO */
1077         while (doing_pop3client != 0) ;
1078         DeleteHash(&POP3FetchUrls);
1079         DeleteHash(&POP3QueueRooms);
1080 }
1081
1082
1083
1084 CTDL_MODULE_INIT(pop3client)
1085 {
1086         if (!threading)
1087         {
1088                 CtdlFillSystemContext(&pop3_client_CC, "POP3aggr");
1089                 CtdlREGISTERRoomCfgType(pop3client, ParseGeneric, 0, 5, SerializeGeneric, DeleteGenericCfgLine);
1090                 pthread_mutex_init(&POP3QueueMutex, NULL);
1091                 POP3QueueRooms = NewHash(1, lFlathash);
1092                 POP3FetchUrls = NewHash(1, NULL);
1093                 CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
1094                 CtdlRegisterEVCleanupHook(pop3_cleanup);
1095         }
1096
1097         /* return our module id for the log */
1098         return "pop3client";
1099 }