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