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