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