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