chiark / gitweb /
Fix up more warnings. Actually read from connections
[inn-innduct.git] / innfeed / imap_connection.c
1 /*  $Id: imap_connection.c 7103 2004-12-23 22:36:27Z rra $
2 **
3 **  Feed articles to an IMAP server via LMTP and IMAP.
4 **
5 **  Written by Tim Martin.
6 **
7 **  Instead of feeding articles via nntp to another host this feeds the
8 **  messages via lmtp to a host and the control messages (cancel's etc..) it
9 **  performs via IMAP.  This means it has 2 active connections at any given
10 **  time and 2 queues.
11 **
12 **  When an article comes in it is immediatly placed in the lmtp queue. When
13 **  an article is picked off the lmtp queue for processing first check if it's
14 **  a control message.  If so, place it in the IMAP queue.  If not, attempt to
15 **  deliver via LMTP.
16 **
17 **  This attempts to follow the exact same api as connection.c.
18 **  
19 **  TODO:
20 **  
21 **  feed to smtp 
22 **  security layers?  <--punt on for now
23 **  authname/password per connection object
24 **  untagged IMAP messages
25 */
26
27 #include "config.h"
28 #include "clibrary.h"
29 #include "portable/socket.h"
30 #include <ctype.h>
31 #include <errno.h>
32 #include <netdb.h>
33 #include <netdb.h>
34 #include <time.h>
35 #include <syslog.h>
36
37 #include "inn/messages.h"
38 #include "libinn.h"
39
40 #include "buffer.h"
41 #include "connection.h"
42 #include "endpoint.h"
43 #include "host.h"
44 #include "innfeed.h"
45 #include "article.h"
46 #include "configfile.h"
47
48 #ifdef HAVE_SASL
49 # include <sasl/sasl.h>
50 #endif
51
52 #ifndef MAXHOSTNAMELEN
53 #define MAXHOSTNAMELEN 1024
54 #endif
55
56 #define IMAP_PORT 143
57
58 #ifdef SMTPMODE
59 # define LMTP_PORT 25
60 #else
61 # define LMTP_PORT 2003
62 #endif
63
64 #define IMAP_TAGLENGTH 6
65
66 #define QUEUE_MAX_SIZE 250
67
68 #define DOSOMETHING_TIMEOUT 60
69
70
71
72 /* external */
73 extern char *deliver_username;
74 extern char *deliver_authname;
75 extern char *deliver_password;
76 extern char *deliver_realm;
77 extern char *deliver_rcpt_to;
78 extern char *deliver_to_header;
79
80
81 char hostname[MAXHOSTNAMELEN];
82 char *mailfrom_name = NULL; /* default to no return path */
83
84 #ifdef HAVE_SASL
85 static int initialized_sasl = 0; /* weather sasl_client_init() has been called */
86 #endif
87
88 /* states the imap connection may be in */
89 typedef enum {
90
91     IMAP_DISCONNECTED = 1,
92     IMAP_WAITING,
93     
94     IMAP_CONNECTED_NOTAUTH,
95
96     IMAP_READING_INTRO,
97
98     IMAP_WRITING_CAPABILITY,
99     IMAP_READING_CAPABILITY,
100
101     IMAP_WRITING_STARTAUTH,
102     IMAP_READING_STEPAUTH,
103     IMAP_WRITING_STEPAUTH,
104
105     IMAP_IDLE_AUTHED,
106     IMAP_WRITING_NOOP,
107     IMAP_READING_NOOP,
108
109     IMAP_WRITING_CREATE,
110     IMAP_READING_CREATE,
111
112     IMAP_WRITING_DELETE,
113     IMAP_READING_DELETE,
114
115     IMAP_WRITING_SELECT,
116     IMAP_READING_SELECT,
117
118     IMAP_WRITING_SEARCH,
119     IMAP_READING_SEARCH,
120
121     IMAP_WRITING_STORE,
122     IMAP_READING_STORE,
123     
124     IMAP_WRITING_CLOSE,
125     IMAP_READING_CLOSE,
126
127     IMAP_WRITING_QUIT,
128     IMAP_READING_QUIT
129    
130 } imap_state_t;
131
132 typedef enum {
133     LMTP_DISCONNECTED = 1,
134     LMTP_WAITING,
135
136     LMTP_CONNECTED_NOTAUTH,
137
138     LMTP_READING_INTRO,
139
140     LMTP_WRITING_LHLO,
141     LMTP_READING_LHLO,
142
143     LMTP_WRITING_STARTAUTH,
144     LMTP_READING_STEPAUTH,
145     LMTP_WRITING_STEPAUTH,
146
147     LMTP_AUTHED_IDLE,
148     LMTP_WRITING_NOOP,
149     LMTP_READING_NOOP,
150    
151     LMTP_READING_RSET,
152     LMTP_READING_MAILFROM,
153     LMTP_READING_RCPTTO,
154     LMTP_READING_DATA,
155     LMTP_READING_CONTENTS,
156
157     LMTP_WRITING_UPTODATA,
158     LMTP_WRITING_CONTENTS,
159
160     LMTP_WRITING_QUIT,
161     LMTP_READING_QUIT
162
163 } lmtp_state_t;
164
165 typedef struct imap_capabilities_s {
166
167     int imap4;         /* does server support imap4bis? */
168     int logindisabled; /* does the server allow the login command? */
169
170     char *saslmechs;   /* supported SASL mechanisms */
171
172 } imap_capabilities_t;
173
174 typedef struct lmtp_capabilities_s {
175
176     int Eightbitmime;
177     int EnhancedStatusCodes;
178     int pipelining;
179
180     char *saslmechs;
181
182 } lmtp_capabilities_t;
183
184 typedef enum {
185     STAT_CONT = 0,
186     STAT_NO = 1,
187     STAT_OK = 2,
188     STAT_FAIL = 3
189 } imt_stat;
190
191 /* Message types */
192 typedef enum {
193     DELIVER,
194     CREATE_FOLDER,
195     CANCEL_MSG,
196     DELETE_FOLDER
197 } control_type_t;
198
199 typedef struct control_item_s {
200
201     Article article;
202     char *folder;
203     char *msgid;                 /* only for cancel's */
204     unsigned long  uid;          /* only for cancel's */
205
206 } control_item_t;
207
208 typedef struct article_queue_s {
209
210     control_type_t type;
211
212     time_t arrived;
213     time_t nextsend; /* time we should next try to send article */
214
215     int trys;
216
217     int counts_toward_size;
218
219     union {
220         Article article;
221         control_item_t *control;
222         void *generic;
223     } data;
224
225     struct article_queue_s *next;
226
227 } article_queue_t;
228
229 typedef struct Q_s {
230
231     article_queue_t *head;
232
233     article_queue_t *tail;
234
235     int size;
236
237 } Q_t;
238
239 typedef struct connection_s {
240
241     /* common stuff */
242     char *ServerName;
243
244     char *lmtp_respBuffer;         /* buffer all responses are read into */
245     Buffer lmtp_rBuffer;           /* buffer all responses are read into */
246
247     Host myHost ;                   /* the host who owns the connection */
248     
249     time_t timeCon ;                /* the time the connect happened 
250                                        (last auth succeeded) */
251
252     int issue_quit;                 /* Three states:
253                                      *   0 - don't do anything
254                                      *   1 - after issue quit enter wait state
255                                      *   2 - after issue quit reconnect
256                                      *   3 - after issue quit delete connection
257                                      *   4 - nuke cxn
258                                      */
259
260     /* Statistics */    
261     int lmtp_succeeded;
262     int lmtp_failed;
263
264     int cancel_succeeded;
265     int cancel_failed;
266     
267     int create_succeeded;
268     int create_failed;
269     
270     int remove_succeeded;
271     int remove_failed;
272
273     
274     /* LMTP stuff */
275     int lmtp_port;
276     lmtp_state_t lmtp_state;
277 #ifdef HAVE_SASL
278     sasl_conn_t *saslconn_lmtp;
279 #endif /* HAVE_SASL */
280     int sockfd_lmtp;
281
282     time_t lmtp_timeCon ;
283
284     EndPoint lmtp_endpoint;
285     unsigned int ident ;               /* an identifier for syslogging. */
286
287     lmtp_capabilities_t *lmtp_capabilities;
288
289     int lmtp_disconnects;
290     char *lmtp_tofree_str;
291
292     article_queue_t *current_article;
293     Buffer *current_bufs;
294     int     current_rcpts_issued;
295     int     current_rcpts_okayed;
296
297     /* Timer for the max amount of time to wait for a response from the
298        remote */
299     unsigned int lmtp_readTimeout ;
300     TimeoutId lmtp_readBlockedTimerId ;
301
302     /* Timer for the max amount of time to wait for a any amount of data
303        to be written to the remote */
304     unsigned int lmtp_writeTimeout ;
305     TimeoutId lmtp_writeBlockedTimerId ;
306
307     /* Timer for the number of seconds to sleep before attempting a
308        reconnect. */
309     unsigned int lmtp_sleepTimeout ;
310     TimeoutId lmtp_sleepTimerId ;
311
312     /* Timer for max amount between queueing some articles and trying to send them */
313     unsigned int dosomethingTimeout ;
314     TimeoutId dosomethingTimerId ;
315
316     Q_t lmtp_todeliver_q;
317
318
319
320     /* IMAP stuff */
321     int imap_port;
322 #ifdef HAVE_SASL
323     sasl_conn_t *imap_saslconn;
324 #endif /* HAVE_SASL */
325
326     char *imap_respBuffer;
327     Buffer imap_rBuffer;
328     EndPoint imap_endpoint;
329
330     imap_capabilities_t *imap_capabilities;
331     
332     int imap_sockfd;
333
334     time_t imap_timeCon ;
335
336     imap_state_t imap_state;
337     int imap_disconnects;
338     char *imap_tofree_str;
339
340     char imap_currentTag[IMAP_TAGLENGTH];
341     int  imap_tag_num;
342
343     /* Timer for the max amount of time to wait for a response from the
344        remote */
345     unsigned int imap_readTimeout ;
346     TimeoutId imap_readBlockedTimerId ;
347
348     /* Timer for the max amount of time to wait for a any amount of data
349        to be written to the remote */
350     unsigned int imap_writeTimeout ;
351     TimeoutId imap_writeBlockedTimerId ;
352
353     /* Timer for the number of seconds to sleep before attempting a
354        reconnect. */
355     unsigned int imap_sleepTimeout ;
356     TimeoutId imap_sleepTimerId ;
357
358     Q_t imap_controlMsg_q;
359
360     article_queue_t *current_control;
361
362     struct connection_s *next;
363
364 } connection_t;
365
366 static Connection gCxnList = NULL ;
367 static unsigned int gCxnCount= 0 ;
368 static unsigned int max_reconnect_period = MAX_RECON_PER ;
369 static unsigned int init_reconnect_period = INIT_RECON_PER;
370
371 typedef enum {
372     RET_OK = 0,
373     RET_FAIL = 1,
374     RET_QUEUE_EMPTY,
375     RET_EXCEEDS_SIZE,
376     RET_NO_FULLLINE,
377     RET_NO,
378     RET_ARTICLE_BAD
379 } conn_ret;
380
381
382 /********** Private Function Declarations *************/
383
384 static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
385 static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
386 static void imap_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);
387 static void lmtp_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);
388
389 static conn_ret lmtp_Connect(connection_t *cxn);
390 static conn_ret imap_Connect(connection_t *cxn);
391
392 static void prepareReopenCbk (Connection cxn, int type);
393
394 static void lmtp_readTimeoutCbk (TimeoutId id, void *data);
395 static void imap_readTimeoutCbk (TimeoutId id, void *data);
396
397 static void dosomethingTimeoutCbk (TimeoutId id, void *data);
398
399 static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen);
400 static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen);
401
402 static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback, 
403                             EndPoint endp, Buffer *array);
404 static void lmtp_sendmessage(connection_t *cxn, Article justadded);
405 static void imap_ProcessQueue(connection_t *cxn);
406
407 static conn_ret FindHeader(Buffer *bufs, const char *header, char **start, char **end);
408 static conn_ret PopFromQueue(Q_t *q, article_queue_t **item);
409
410 enum failure_type {
411     MSG_SUCCESS = 0,
412     MSG_FAIL_DELIVER = 1,
413     MSG_GIVE_BACK = 2,
414     MSG_MISSING = 3
415 };
416
417 static void QueueForgetAbout(connection_t *cxn, article_queue_t *item, 
418                              enum failure_type failed);
419
420 static void delConnection (Connection cxn);
421 static void DeleteIfDisconnected(Connection cxn);
422 static void DeferAllArticles(connection_t *cxn, Q_t *q);
423
424 static void lmtp_Disconnect(connection_t *cxn);
425 static void imap_Disconnect(connection_t *cxn);
426 static conn_ret imap_listenintro(connection_t *cxn);
427
428 static void imap_writeTimeoutCbk (TimeoutId id, void *data);
429 static void lmtp_writeTimeoutCbk (TimeoutId id, void *data);
430
431 /******************** PRIVATE FUNCTIONS ***************************/
432
433 static const char *imap_stateToString(int state)
434 {
435     switch (state)
436         {
437         case IMAP_DISCONNECTED: return "disconnected";   
438         case IMAP_WAITING: return "waiting";
439         case IMAP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
440         case IMAP_READING_INTRO: return "reading intro";
441         case IMAP_WRITING_CAPABILITY: return "writing CAPABILITY";
442         case IMAP_READING_CAPABILITY: return "reading CAPABILITY";
443         case IMAP_WRITING_STARTAUTH: return "writing AUTHENTICATE";
444         case IMAP_READING_STEPAUTH: return "reading stepauth";
445         case IMAP_WRITING_STEPAUTH: return "writing stepauth";
446         case IMAP_IDLE_AUTHED: return "idle (authenticated)";
447         case IMAP_WRITING_NOOP: return "writing NOOP";
448         case IMAP_READING_NOOP: return "reading NOOP response";
449         case IMAP_WRITING_CREATE: return "writing CREATE";
450         case IMAP_READING_CREATE: return "reading CREATE response";
451         case IMAP_WRITING_DELETE: return "writing DELETE command";
452         case IMAP_READING_DELETE: return "reading DELETE response";            
453         case IMAP_WRITING_SELECT: return "writing SELECT";
454         case IMAP_READING_SELECT: return "reading SELECT response";
455         case IMAP_WRITING_SEARCH: return "writing SEARCH";
456         case IMAP_READING_SEARCH: return "reading SEARCH response";
457         case IMAP_WRITING_STORE: return "writing STORE";
458         case IMAP_READING_STORE: return "reading STORE response";           
459         case IMAP_WRITING_CLOSE: return "writing CLOSE";
460         case IMAP_READING_CLOSE: return "reading CLOSE response";
461         case IMAP_WRITING_QUIT: return "writing LOGOUT";
462         case IMAP_READING_QUIT: return "reading LOGOUT response";
463         default: return "Unknown state";
464         }
465 }
466
467 static const char *lmtp_stateToString(int state)
468 {
469     switch(state)
470         {
471         case LMTP_DISCONNECTED: return "disconnected";
472         case LMTP_WAITING: return "waiting";
473         case LMTP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
474         case LMTP_READING_INTRO: return "reading intro";
475         case LMTP_WRITING_LHLO: return "writing LHLO";
476         case LMTP_READING_LHLO: return "reading LHLO response";
477         case LMTP_WRITING_STARTAUTH: return "writing AUTH";
478         case LMTP_READING_STEPAUTH: return "reading stepauth";
479         case LMTP_WRITING_STEPAUTH: return "writing stepauth";
480         case LMTP_AUTHED_IDLE: return "idle (authenticated)";
481         case LMTP_WRITING_NOOP: return "writing NOOP";
482         case LMTP_READING_NOOP: return "reading NOOP response";
483         case LMTP_READING_RSET: return "reading RSET response";
484         case LMTP_READING_MAILFROM: return "reading MAIL FROM response";
485         case LMTP_READING_RCPTTO: return "reading RCPT TO response";
486         case LMTP_READING_DATA: return "reading DATA response";
487         case LMTP_READING_CONTENTS: return "reading contents response";
488         case LMTP_WRITING_UPTODATA: 
489             return "writing RSET, MAIL FROM, RCPT TO, DATA commands";
490         case LMTP_WRITING_CONTENTS: return "writing contents of message";
491         case LMTP_WRITING_QUIT: return "writing QUIT";
492         case LMTP_READING_QUIT: return "reading QUIT";
493         default: return "unknown state";
494         }
495 }
496
497 /******************************* Queue functions ***********************************/
498
499 /*
500  * Add a message to a generic queue
501  *
502  *  q       - the queue adding to
503  *  item    - the data to add to the queue
504  *  type    - the type of item it is (i.e. cancel,lmtp,etc..)
505  *  addsmsg - weather this should be counted toward the queue size
506  *            this is for control msg's that create multiple queue items.
507  *            For example a cancel message canceling a message in multiple
508  *            newsgroups will create >1 queue item but we only want it to count
509  *            once towards the queue
510  *  must    - wheather we must take it even though it may put us over our max size
511  */
512
513 static conn_ret AddToQueue(Q_t *q, void *item, 
514                            control_type_t type, int addsmsg, bool must)
515 {
516     article_queue_t *newentry;
517
518     if (must == false)
519     {
520         if (q->size >= QUEUE_MAX_SIZE)
521         {
522             return RET_EXCEEDS_SIZE;
523         }
524     } else {
525         if (q->size >= QUEUE_MAX_SIZE * 10)
526         {
527             d_printf(0, "Queue has grown way too much. Dropping article\n");
528             return RET_FAIL;
529         }
530     }
531
532     /* add to the end of our queue */
533     newentry = xmalloc(sizeof(article_queue_t));
534
535     newentry->type = type;
536
537     /* send as soon as possible */
538     newentry->nextsend = newentry->arrived = time(NULL);
539
540     newentry->trys = 0;
541
542     newentry->data.generic = item;
543     newentry->next = NULL;
544     newentry->counts_toward_size = addsmsg;
545
546     /* add to end of queue */
547     if (q->tail == NULL)
548     {
549         q->head = newentry;
550         q->tail = newentry;
551     } else {
552
553         q->tail->next = newentry;
554         q->tail = newentry;
555     }
556
557     q->size+=addsmsg;
558
559     return RET_OK;
560 }
561
562 /*
563  * Pop an item from the queue
564  *
565  * q    - the queue to pop from
566  * item - where the item shall be placed upon sucess
567  *
568  */
569
570 static conn_ret PopFromQueue(Q_t *q, article_queue_t **item)
571 {
572     /* if queue empty return error */
573     if ( q->head == NULL)
574     {
575         return RET_QUEUE_EMPTY;
576     }
577     
578     /* set what we return */
579     *item = q->head;
580
581     q->head = q->head->next;
582     if (q->head == NULL) q->tail = NULL;
583
584     q->size-=(*item)->counts_toward_size;
585     
586     return RET_OK;
587 }
588
589 /*
590  * ReQueue an item. Will either put it back in the queue for another try
591  * or forget about it
592  *
593  *  cxn     - our connection object (needed so forget about things)
594  *  q       - the queue to requeue to 
595  *  entry   - the item to put back
596  */
597
598 static void ReQueue(connection_t *cxn, Q_t *q, article_queue_t *entry)
599 {
600     /* look at the time it's been here */
601     entry->nextsend = time(NULL) + (entry->trys *30); /* xxx better formula? */
602
603     entry->trys++;
604     
605     /* give up after 5 tries xxx configurable??? */
606     if (entry->trys >= 5)
607     {
608         QueueForgetAbout(cxn, entry, MSG_FAIL_DELIVER);
609         return;
610     }
611
612
613     /* ok let's add back to the end of the queue */
614     entry->next = NULL;
615
616     /* add to end of queue */
617     if (q->tail == NULL)
618     {
619         q->head = entry;
620         q->tail = entry;
621     } else {
622         q->tail->next = entry;
623         q->tail = entry;
624     }
625
626     q->size+=entry->counts_toward_size;
627 }
628
629
630
631 /*
632  * Forget about an item. Tells host object if we succeeded/failed/etc with the message
633  *
634  * cxn    - connection object
635  * item   - item
636  * failed - type of failure (see below)
637  *
638  * failed:
639  *   0 - succeeded delivering message
640  *   1 - failed delivering message
641  *   2 - Try to give back to host
642  *   3 - Article missing (i.e. can't find on disk)
643  */
644 static void QueueForgetAbout(connection_t *cxn, article_queue_t *item, 
645                              enum failure_type failed)
646 {
647     Article art = NULL;
648     
649     switch (item->type)
650         {
651         case DELIVER:
652             if (failed>0)
653                 cxn->lmtp_failed++;
654             art = item->data.article;
655             break;
656
657         case CANCEL_MSG:
658             if (failed>0)
659                 cxn->cancel_failed++;
660             free(item->data.control->msgid);
661             free(item->data.control->folder);
662
663             if (item->counts_toward_size == 1)
664                 art = item->data.control->article;
665
666             free(item->data.control );
667             break;
668
669         case CREATE_FOLDER: 
670             if (failed>0)
671                 cxn->create_failed++;
672             free(item->data.control->folder);
673
674             art = item->data.control->article;
675
676             free(item->data.control );
677             break;
678  
679         case DELETE_FOLDER:
680             if (failed>0)
681                 cxn->remove_failed++;
682             free(item->data.control->folder);
683
684             art = item->data.control->article;
685
686             free(item->data.control );
687             break;
688
689         default:
690             d_printf(0, "%s:%d QueueForgetAbout(): "
691                      "Unknown type to forget about\n",
692                      hostPeerName (cxn->myHost), cxn->ident);
693             break;
694         }
695
696     if (art!=NULL) {
697         switch (failed) {
698         case MSG_SUCCESS:
699             hostArticleAccepted (cxn->myHost, cxn, art);
700             break;
701
702         case MSG_FAIL_DELIVER:
703             hostArticleRejected (cxn->myHost, cxn, art);
704             break;
705
706         case MSG_GIVE_BACK:
707             hostTakeBackArticle (cxn->myHost, cxn, art);
708             break;
709
710         case MSG_MISSING:
711             hostArticleIsMissing(cxn->myHost, cxn, art);
712             break;
713         default:
714             d_printf(0,"%s:%d QueueForgetAbout(): failure type unknown\n",
715                      hostPeerName (cxn->myHost), cxn->ident);
716             break;
717         }
718     }
719
720     free(item);
721 }
722
723 /*
724  * How much space is available in the queue
725  */
726
727 static int QueueSpace(Q_t *q)
728 {
729     int ret = QUEUE_MAX_SIZE - q->size;
730     if (ret < 0) ret = 0;
731     return ret;
732 }
733
734 /*
735  * How many items are in the queue
736  */
737
738 static int QueueItems(Q_t *q)
739 {
740     return q->size;
741 }
742
743
744 /***************************** END Queue functions ***********************************/
745
746 /***************************** Generic Parse Functions *******************************/
747
748 /* returns the end of the header */
749
750 static char *GetUntil(char *str)
751 {
752     while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
753     {
754         str++;
755     }
756
757     return str;
758 }
759
760 static char *GotoNextLine(char *str)
761 {
762     while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
763     {
764         str++;
765     }
766
767     if (*str == '\r') str++;
768     if (*str == '\n') str++;
769
770     return str;
771 }
772
773 /*
774  * Finds the given header in the message
775  *  Returns NULL if not found
776  */
777 static conn_ret FindHeader(Buffer *bufs, const char *header, char **start,
778         char **end)
779 {
780     Buffer b;
781     int size;
782     char *str_base;
783     char *str;
784     int headerlen = strlen(header);
785     
786     if (bufs==NULL)
787     {
788         if (start)
789             *start=NULL; 
790         return RET_ARTICLE_BAD; 
791     }
792     
793     b = bufs[0];
794     size = bufferSize(b);
795     str_base = bufferBase(b);
796     str = str_base;
797     
798     while ((str - str_base) < size - headerlen)
799     {
800         if (*str == header[0])
801         {
802             if ((strncasecmp(header, str, headerlen)==0) && ( *(str + headerlen)==':'))
803             {
804
805                 if (start)
806                 {
807                     *start = str+headerlen+1;
808
809                     /* get rid of leading whitespace */
810                     while ( isspace((int) **start))                     
811                         (*start)++;
812                 }
813                 
814                 if (end)
815                     *end = GetUntil(str+headerlen+1);
816                 
817                 return RET_OK;
818             }
819         } else if (*str == '\n') { 
820             /* end of headers */
821             return RET_NO;
822         }
823         str = GotoNextLine(str);
824     }
825
826     return RET_NO;
827 }
828
829 static conn_ret GetLine(char *buf, char *ret, int retmaxsize)
830 {
831     char *str_base;
832     char *str;
833
834     int size = strlen(buf);
835     str_base = buf;
836     str = str_base;
837
838     while ( (*str) != '\0')
839     {
840         if ((*str) == '\n')
841         {
842             if (str-str_base > retmaxsize)
843             {
844                 d_printf(0, "Max size exceeded! %s\n",str_base);
845                 return RET_FAIL;
846             }
847
848             /* fill in the return string */
849             memcpy(ret, str_base, str-str_base);
850             ret[ str - str_base -1] = '\0';
851
852             memcpy( str_base, str_base + (str-str_base)+1, size - (str-str_base));
853             str_base[size - (str-str_base)]='\0';
854
855             return RET_OK;
856         }
857
858         str++;
859     }
860
861     /* couldn't find a full line */
862     return RET_NO_FULLLINE;
863 }
864
865
866
867 /************************** END Generic Parse Functions *******************************/
868
869 /************************ Writing to Network functions *****************/
870
871 static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback, 
872                             EndPoint endp, Buffer *array)
873 {
874
875     if (array == NULL) return RET_FAIL;
876
877     prepareWrite (endp,
878                   array,
879                   NULL,
880                   callback,
881                   cxn);
882
883     return RET_OK;
884 }
885
886 static conn_ret WriteToWire_str(connection_t *cxn, EndpRWCB callback,
887                                 EndPoint endp, char *str, int slen)
888 {
889     conn_ret result;
890     Buffer buff;
891     Buffer *writeArr;
892
893     if (slen==-1) slen = strlen(str);
894
895     buff = newBufferByCharP(str, slen+1, slen);
896     ASSERT (buff != NULL);   
897
898     writeArr = makeBufferArray (buff, NULL) ;
899
900     result = WriteToWire(cxn, callback, endp, writeArr);
901
902     return result;
903 }
904
905 static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen)
906 {
907     /* prepare the timeouts */
908     clearTimer (cxn->imap_readBlockedTimerId) ;
909     
910     /* set up the write timer. */
911     clearTimer (cxn->imap_writeBlockedTimerId) ;
912
913     if (cxn->imap_writeTimeout > 0)
914         cxn->imap_writeBlockedTimerId = prepareSleep (imap_writeTimeoutCbk, cxn->imap_writeTimeout,
915                                                  cxn);
916     cxn->imap_tofree_str = str;
917     return WriteToWire_str(cxn, imap_writeCB, cxn->imap_endpoint, str, slen);
918 }
919
920 static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen)
921 {
922     /* prepare the timeouts */
923     clearTimer (cxn->lmtp_readBlockedTimerId) ;
924     
925     /* set up the write timer. */
926     clearTimer (cxn->lmtp_writeBlockedTimerId) ;
927
928     if (cxn->lmtp_writeTimeout > 0)
929         cxn->lmtp_writeBlockedTimerId = prepareSleep (lmtp_writeTimeoutCbk, cxn->lmtp_writeTimeout,
930                                                  cxn) ;
931
932
933
934     cxn->lmtp_tofree_str = str;
935     return WriteToWire_str(cxn, lmtp_writeCB, cxn->lmtp_endpoint, str, slen);
936 }
937
938 static conn_ret WriteArticle(connection_t *cxn, Buffer *array)
939 {
940     int array_len = bufferArrayLen (array);
941     int lup=0;
942     int result;
943
944     for (lup=0;lup<array_len;lup++)
945     {
946         int current_size;
947         Buffer current_buf;
948         char *current_start;
949
950         current_buf = array[lup];
951                 
952         current_size = bufferDataSize( current_buf );
953         
954         current_start = bufferBase( current_buf );
955
956     }
957
958     /* just call writetowire since it's easy */
959     result = WriteToWire(cxn, lmtp_writeCB, cxn->lmtp_endpoint, array);
960
961     if (result!=RET_OK)
962     {
963         return result;
964     }
965
966     cxn->lmtp_state = LMTP_WRITING_CONTENTS;
967
968     return RET_OK;
969 }
970
971 /************************ END Writing to Network functions *****************/
972
973
974
975 /*
976  * Adds a cancel item to the control queue
977  * Cancel item to delete message with <msgid> in <folder>
978  *
979  * cxn       - connection object
980  * folder    - pointer to start of folder string (this is a pointer into the actual message buffer)
981  * folderlen - length of folder string 
982  * msgid     - pointer to start of msgid string (this is a pointer into the actual message buffer)
983  * msgidlen  - length of msgid string 
984  * art       - the article for this control message (NULL if this cancel object lacks one)
985  * must      - if must be accepted into queue
986  */
987
988 static conn_ret addCancelItem(connection_t *cxn, 
989                               char *folder, int folderlen,
990                               char *msgid, int msgidlen,
991                               Article art, int must)
992 {
993     control_item_t *item;
994     conn_ret result;
995     int i;
996
997     ASSERT(folder); ASSERT(msgid); ASSERT(cxn);
998
999     /* sanity check folder, msgid */
1000     for (i = 0; i < folderlen; i++) ASSERT(!isspace((int) folder[i]));
1001     for (i = 0; i < msgidlen; i++) ASSERT(!isspace((int) msgid[i]));
1002
1003     /* create the object */
1004     item = xcalloc (1, sizeof(control_item_t));
1005
1006     item->folder = xcalloc(folderlen+1, 1);
1007     memcpy(item->folder, folder, folderlen);
1008     item->folder[folderlen] = '\0';
1009     
1010     item->msgid = xcalloc (msgidlen+1, 1);
1011     memcpy(item->msgid, msgid, msgidlen);
1012     item->msgid[msgidlen] = '\0';
1013
1014     item->article = art;
1015     
1016     /* try to add to the queue (counts if art isn't null) */
1017     result = AddToQueue(&(cxn->imap_controlMsg_q), item, CANCEL_MSG, (art != NULL), must);
1018     if (result != RET_OK) {
1019         d_printf(1,"%s:%d addCancelItem(): "
1020                  "I thought we had in space in [imap] queue "
1021                  "but apparently not\n",
1022                  hostPeerName (cxn->myHost), cxn->ident);
1023
1024         /* cleanup */
1025         free(item->folder);
1026         free(item->msgid);
1027         free(item);
1028
1029         return result;
1030     }
1031                 
1032     return RET_OK;
1033 }
1034
1035 static conn_ret AddControlMsg(connection_t *cxn, 
1036                               Article art, 
1037                               Buffer *bufs, 
1038                               char *control_header, 
1039                               char *control_header_end, 
1040                               bool must)
1041 {
1042     char *rcpt_list = NULL, *rcpt_list_end;
1043     control_item_t *item;
1044     conn_ret res = RET_OK;
1045     int t;
1046
1047     /* make sure contents ok; this also should load it into memory */
1048     if (!artContentsOk (art)) {
1049         d_printf(0, "%s:%d AddControlMsg(): "
1050                  "artContentsOk() said article was bad\n",
1051                  hostPeerName (cxn->myHost), cxn->ident);
1052         hostArticleIsMissing (cxn->myHost, cxn, art);
1053         return RET_FAIL;
1054     }
1055
1056     res = RET_OK;
1057     /* now let's look at the control to see what it is */
1058     if (!strncasecmp(control_header,"newgroup",8)) {
1059         control_header += 8;
1060         t = CREATE_FOLDER;
1061     } else if (!strncasecmp(control_header,"rmgroup",7)) {
1062         /* jump past "rmgroup" */
1063         control_header += 7;
1064         t = DELETE_FOLDER;
1065     } else if (!strncasecmp(control_header,"cancel",6)) {
1066         t = CANCEL_MSG;
1067         control_header += 6;
1068     } else {
1069         /* unrecognized type */
1070         char tmp[100];
1071         char *endstr;
1072         size_t clen;
1073
1074         endstr = strchr(control_header,'\n');
1075         clen = endstr - control_header;
1076
1077         if (clen > sizeof(tmp)-1) clen = sizeof(tmp)-1;
1078
1079         memcpy(tmp,control_header, clen);
1080         tmp[clen]='\0';
1081         
1082         d_printf(0,"%s:%d Don't understand control header [%s]\n",
1083                  hostPeerName (cxn->myHost), cxn->ident,tmp);
1084         return RET_FAIL;
1085     }
1086
1087     switch (t) {
1088     case CREATE_FOLDER:
1089     case DELETE_FOLDER:
1090     {
1091         int folderlen;
1092
1093         /* go past all white space */
1094         while ((*control_header == ' ') && 
1095                (control_header != control_header_end)) {
1096             control_header++;
1097         }
1098
1099         /* trim trailing whitespace */
1100         while (control_header_end[-1] == ' ') {
1101             control_header_end--;
1102         }
1103
1104         if (control_header >= control_header_end) {
1105             d_printf(0,"%s:%d addControlMsg(): "
1106                      "newgroup/rmgroup header has no group specified\n",
1107                      hostPeerName (cxn->myHost), cxn->ident);
1108             return RET_FAIL;
1109         }
1110
1111         folderlen = control_header_end - control_header;
1112
1113         item = xcalloc(1, sizeof(control_item_t));
1114
1115         item->folder = xcalloc(folderlen + 1, 1);
1116         memcpy(item->folder, control_header, folderlen);
1117         item->folder[folderlen] = '\0';
1118
1119         item->article = art;
1120
1121         if (AddToQueue(&(cxn->imap_controlMsg_q), item, t, 1, must) != RET_OK) {
1122             d_printf(1,"%s:%d addCancelItem(): "
1123                      "I thought we had in space in [imap] queue"
1124                      " but apparently not\n",
1125                      hostPeerName (cxn->myHost), cxn->ident);
1126             free(item->folder);
1127             free(item);
1128             return RET_FAIL;
1129         }
1130
1131         break;
1132     }
1133
1134     case CANCEL_MSG:
1135     {
1136         char *str, *laststart;
1137
1138         while (((*control_header) == ' ') && 
1139                (control_header != control_header_end))
1140         {
1141             control_header++;
1142         }
1143
1144         if (control_header == control_header_end)
1145         {
1146             d_printf(0, "%s:%d Control header contains cancel "
1147                         "with no msgid specified\n",
1148                      hostPeerName (cxn->myHost), cxn->ident);
1149             return RET_FAIL;
1150         }
1151
1152         if (FindHeader(bufs, "Newsgroups", &rcpt_list, &rcpt_list_end)!=RET_OK)
1153         {
1154             d_printf(0,"%s:%d Cancel msg contains no newsgroups header\n",
1155                      hostPeerName (cxn->myHost), cxn->ident);
1156             return RET_FAIL;
1157         }
1158
1159         str = rcpt_list;
1160         laststart = rcpt_list;
1161
1162         while (str != rcpt_list_end)
1163         {
1164             if (*str == ',') {
1165                 /* eliminate leading whitespace */
1166                 while (((*laststart) ==' ') || ((*laststart)=='\t'))
1167                 {
1168                     laststart++;
1169                 }
1170
1171                 res = addCancelItem(cxn, laststart, 
1172                                     str - laststart, 
1173                                     control_header, 
1174                                     control_header_end - control_header,
1175                                     NULL, must);
1176                 if (res!=RET_OK) return res;
1177                 
1178                 laststart = str+1;
1179             }
1180
1181             str++;
1182         }
1183         
1184         if (laststart<str)
1185         {
1186
1187             res = addCancelItem(cxn, laststart, str - laststart, 
1188                                 control_header,
1189                                 control_header_end - control_header, 
1190                                 art, must);
1191             if (res!=RET_OK) return res;
1192         }
1193         break;
1194     }
1195     default:
1196         /* huh?!? */
1197         d_printf(0, "%s:%d internal error in addControlMsg()\n",
1198                      hostPeerName (cxn->myHost), cxn->ident);
1199     }
1200     return RET_FAIL;
1201 }
1202
1203 /*
1204  * Show msg handling statistics
1205  */
1206
1207 static void show_stats(connection_t *cxn)
1208 {   
1209     d_printf(0, "%s:%d\n",hostPeerName (cxn->myHost), cxn->ident);
1210     d_printf(0, "  imap queue = %d lmtp queue = %d\n",
1211              QueueItems(&(cxn->imap_controlMsg_q)),
1212              QueueItems(&(cxn->lmtp_todeliver_q)));
1213     d_printf(0,"  imap state = %s\n", imap_stateToString(cxn->imap_state));
1214     d_printf(0,"  lmtp state = %s\n", lmtp_stateToString(cxn->lmtp_state));
1215     d_printf(0,"  delivered:  yes: %d no: %d\n",
1216              cxn->lmtp_succeeded, 
1217              cxn->lmtp_failed);
1218     d_printf(0,"  cancel:     yes: %d no: %d\n",
1219              cxn->cancel_succeeded, cxn->cancel_failed);
1220     d_printf(0,"  create:     yes: %d no: %d\n",
1221              cxn->create_succeeded, cxn->create_failed);
1222     d_printf(0,"  remove:     yes: %d no: %d\n",
1223              cxn->remove_succeeded, cxn->remove_failed);
1224 }
1225
1226 /**************************** SASL helper functions ******************************/
1227
1228 #ifdef HAVE_SASL
1229 /* callback to get userid or authid */
1230 static int getsimple(void *context __attribute__((unused)),
1231                      int id,
1232                      const char **result,
1233                      unsigned *len)
1234 {
1235   char *username;
1236   char *authid;
1237
1238   if (! result)
1239     return SASL_BADPARAM;
1240
1241
1242   switch (id) {
1243   case SASL_CB_GETREALM:
1244       *result = deliver_realm;
1245       if (len)
1246           *len = deliver_realm ? strlen(deliver_realm) : 0;
1247       break;
1248
1249   case SASL_CB_USER:
1250       *result = deliver_username;
1251       if (len)
1252           *len = deliver_username ? strlen(deliver_username) : 0;
1253     break;
1254   case SASL_CB_AUTHNAME:
1255     authid=deliver_authname;
1256     *result = authid;
1257     if (len)
1258       *len = authid ? strlen(authid) : 0;
1259       break;
1260   case SASL_CB_LANGUAGE:
1261     *result = NULL;
1262     if (len)
1263       *len = 0;
1264     break;
1265   default:
1266     return SASL_BADPARAM;
1267   }
1268   return SASL_OK;
1269 }
1270
1271 /* callback to get password */
1272 static int
1273 getsecret(sasl_conn_t *conn,
1274           void *context __attribute__((unused)),
1275           int id,
1276           sasl_secret_t **psecret)
1277 {
1278   size_t passlen;
1279
1280   if (! conn || ! psecret || id != SASL_CB_PASS)
1281     return SASL_BADPARAM;
1282
1283   if (deliver_password==NULL)
1284   {
1285       d_printf(0,"SASL requested a password but I don't have one\n");
1286       return SASL_FAIL;
1287   }
1288
1289   passlen = strlen(deliver_password);
1290   *psecret = xmalloc(sizeof(sasl_secret_t) + passlen + 1);
1291   if (! *psecret)
1292     return SASL_FAIL;
1293
1294   strlcpy((*psecret)->data, deliver_password, passlen + 1);
1295   (*psecret)->len = passlen;
1296
1297   return SASL_OK;
1298 }
1299
1300
1301 /* callbacks we support */
1302 static sasl_callback_t saslcallbacks[] = {
1303   {
1304     SASL_CB_GETREALM, &getsimple, NULL
1305   }, {
1306     SASL_CB_USER, &getsimple, NULL
1307   }, {
1308     SASL_CB_AUTHNAME, &getsimple, NULL
1309   }, {
1310     SASL_CB_PASS, &getsecret, NULL    
1311   }, {
1312     SASL_CB_LIST_END, NULL, NULL
1313   }
1314 };
1315
1316 static sasl_security_properties_t *make_secprops(int min,int max)
1317 {
1318   sasl_security_properties_t *ret=
1319     xmalloc(sizeof(sasl_security_properties_t));
1320
1321   ret->maxbufsize=1024;
1322   ret->min_ssf=min;
1323   ret->max_ssf=max;
1324
1325   ret->security_flags=0;
1326   ret->property_names=NULL;
1327   ret->property_values=NULL;
1328
1329   return ret;
1330 }
1331
1332 #ifndef NI_WITHSCOPEID
1333 #define NI_WITHSCOPEID  0
1334 #endif
1335 #ifndef NI_MAXHOST
1336 #define NI_MAXHOST      1025
1337 #endif
1338 #ifndef NI_MAXSERV
1339 #define NI_MAXSERV      32
1340 #endif
1341
1342 static int iptostring(const struct sockaddr *addr, socklen_t addrlen,
1343                      char *out, unsigned outlen) {
1344     char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
1345     
1346     if(!addr || !out) return SASL_BADPARAM;
1347
1348     getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
1349                 NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV);
1350
1351     if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
1352         return SASL_BUFOVER;
1353
1354     snprintf(out, outlen, "%s;%s", hbuf, pbuf);
1355
1356     return SASL_OK;
1357 }
1358
1359 static conn_ret SetSASLProperties(sasl_conn_t *conn, int sock, int minssf, int maxssf)
1360 {
1361   int saslresult;
1362   sasl_security_properties_t *secprops=NULL;
1363   int addrsize=sizeof(struct sockaddr_in);
1364   char localip[60], remoteip[60];
1365   struct sockaddr_in saddr_l;
1366   struct sockaddr_in saddr_r;
1367
1368   /* create a security structure and give it to sasl */
1369   secprops = make_secprops(minssf, maxssf);
1370   if (secprops != NULL)
1371   {
1372     sasl_setprop(conn, SASL_SEC_PROPS, secprops);
1373     free(secprops);
1374   }
1375
1376   if (getpeername(sock,(struct sockaddr *)&saddr_r,&addrsize)!=0)
1377     return RET_FAIL;
1378
1379   if (iptostring((struct sockaddr *)&saddr_r, sizeof(struct sockaddr_in),
1380                 remoteip, sizeof(remoteip)))
1381     return RET_FAIL;
1382
1383   if (sasl_setprop(conn, SASL_IPREMOTEPORT, remoteip)!=SASL_OK)
1384     return RET_FAIL;
1385   
1386   addrsize=sizeof(struct sockaddr_in);
1387   if (getsockname(sock,(struct sockaddr *) &saddr_l,&addrsize)!=0)
1388     return RET_FAIL;
1389
1390   if (iptostring((struct sockaddr *)&saddr_l, sizeof(struct sockaddr_in),
1391                  localip, sizeof(localip)))
1392     return RET_FAIL;
1393     
1394   if (sasl_setprop(conn, SASL_IPLOCALPORT, localip)!=SASL_OK)
1395     return RET_FAIL;
1396   
1397   return RET_OK;
1398 }
1399 #endif /* HAVE_SASL */
1400
1401 /************************** END SASL helper functions ******************************/
1402
1403 /************************* Startup functions **********************************/
1404
1405 static conn_ret Initialize(connection_t *cxn, int respTimeout)
1406 {
1407 #ifdef HAVE_SASL
1408     conn_ret saslresult;
1409 #endif /* HAVE_SASL */
1410
1411     d_printf(1,"%s:%d initializing....\n",
1412              hostPeerName (cxn->myHost), cxn->ident);
1413
1414 #ifdef HAVE_SASL
1415     /* only call sasl_client_init() once */
1416     if (initialized_sasl == 0)
1417     {
1418         /* Initialize SASL */
1419         saslresult=sasl_client_init(saslcallbacks);
1420         
1421         if (saslresult!=SASL_OK)
1422         {
1423             d_printf(0,
1424                      "%s:%d Error initializing SASL (sasl_client_init) (%s)\n",
1425                      hostPeerName (cxn->myHost), cxn->ident,
1426                      sasl_errstring(saslresult, NULL, NULL));
1427             return RET_FAIL;
1428         } else {
1429             initialized_sasl = 1;
1430         }
1431     }
1432 #endif /* HAVE_SASL */
1433
1434     cxn->lmtp_rBuffer = newBuffer(4096);
1435     if (cxn->lmtp_rBuffer == NULL)
1436     {
1437         d_printf(0, "%s:%d Failure allocating buffer for lmtp_rBuffer\n",
1438                  hostPeerName (cxn->myHost), cxn->ident);
1439         return RET_FAIL;
1440     }
1441     bufferAddNullByte(cxn->lmtp_rBuffer);
1442
1443
1444     cxn->imap_rBuffer = newBuffer(4096);
1445     if (cxn->imap_rBuffer == NULL)
1446     {
1447         d_printf(0, "%s:%d Failure allocating buffer for imap_rBuffer \n",
1448                  hostPeerName (cxn->myHost), cxn->ident);
1449         return RET_FAIL;
1450     }
1451     bufferAddNullByte(cxn->imap_rBuffer);
1452
1453     /* Initialize timeouts */
1454     cxn->lmtp_writeTimeout = respTimeout;
1455     cxn->lmtp_readTimeout = respTimeout;
1456     cxn->imap_writeTimeout = respTimeout;
1457     cxn->imap_readTimeout = respTimeout;
1458     cxn->lmtp_sleepTimerId = 0 ;
1459     cxn->lmtp_sleepTimeout = init_reconnect_period ;
1460     cxn->imap_sleepTimerId = 0 ;
1461     cxn->imap_sleepTimeout = init_reconnect_period ;
1462
1463     cxn->dosomethingTimeout = DOSOMETHING_TIMEOUT;
1464
1465     /* set up the write timer. */
1466     clearTimer (cxn->dosomethingTimerId) ;
1467
1468     if (cxn->dosomethingTimeout > 0)
1469         cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk, 
1470                                                 cxn->dosomethingTimeout, cxn);
1471
1472
1473
1474     return RET_OK;
1475 }
1476
1477
1478 /* initialize the network */
1479 static conn_ret init_net(char *serverFQDN, 
1480                          int port,
1481                          int *sock)
1482 {
1483   struct sockaddr_in addr;
1484   struct hostent *hp;
1485
1486   if ((hp = gethostbyname(serverFQDN)) == NULL) {
1487       d_printf(0, "gethostbyname(): %s\n", strerror(errno));
1488     return RET_FAIL;
1489   }
1490
1491   if (( (*sock) = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
1492       d_printf(0, "socket(): %s\n", strerror(errno));
1493       return RET_FAIL;  
1494   }
1495
1496   addr.sin_family = AF_INET;
1497   memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
1498   addr.sin_port = htons(port);
1499
1500   if (connect( (*sock), (struct sockaddr *) &addr, sizeof (addr)) < 0) {
1501       d_printf(0,"connect(): %s\n",
1502                strerror(errno));
1503       return RET_FAIL;
1504   }
1505
1506   return RET_OK;
1507 }
1508
1509
1510
1511 static conn_ret SetupLMTPConnection(connection_t *cxn,
1512                                     char *serverName,
1513                                     int port)
1514 {
1515 #ifdef HAVE_SASL
1516     int saslresult;
1517 #endif /* HAVE_SASL */
1518     conn_ret result;
1519
1520     cxn->lmtp_port = port;
1521
1522     if (serverName==NULL)
1523     {
1524         d_printf(0, "%s:%d serverName is null\n",
1525                  hostPeerName (cxn->myHost), cxn->ident);
1526         return RET_FAIL;        
1527     }
1528
1529 #ifdef HAVE_SASL
1530     /* Free the SASL connection if we already had one */
1531     if (cxn->saslconn_lmtp!=NULL)
1532     {
1533         sasl_dispose(&cxn->saslconn_lmtp);
1534     }
1535
1536     /* Start SASL */
1537     saslresult=sasl_client_new("lmtp",
1538                                serverName,
1539                                NULL,
1540                                NULL,
1541                                NULL,
1542                                0,
1543                                &cxn->saslconn_lmtp);
1544
1545     if (saslresult != SASL_OK)
1546     {
1547
1548         d_printf(0, "%s:%d:LMTP Error creating a new SASL connection (%s)\n",
1549                  hostPeerName (cxn->myHost), cxn->ident,
1550                  sasl_errstring(saslresult,NULL,NULL));
1551         return RET_FAIL;
1552     }
1553 #endif /* HAVE_SASL */
1554
1555     /* Connect the Socket */
1556     result = init_net(serverName,
1557                       LMTP_PORT, /*port,*/
1558                       &(cxn->sockfd_lmtp));
1559    
1560     if (result != RET_OK)
1561     {
1562         d_printf(0, "%s:%d unable to connect to lmtp host\n",
1563                  hostPeerName (cxn->myHost), cxn->ident);
1564         return RET_FAIL;
1565     }
1566
1567     if (cxn->lmtp_respBuffer) free(cxn->lmtp_respBuffer);
1568     cxn->lmtp_respBuffer = xmalloc (4096);
1569     cxn->lmtp_respBuffer[0]='\0';
1570
1571     /* Free if we had an existing one */
1572     if (cxn->lmtp_endpoint != NULL)
1573     {
1574         delEndPoint(cxn->lmtp_endpoint);
1575         cxn->lmtp_endpoint = NULL;
1576     }
1577
1578     cxn->lmtp_endpoint = newEndPoint(cxn->sockfd_lmtp);
1579     if (cxn->lmtp_endpoint == NULL)
1580     {
1581         d_printf(0, "%s:%d:LMTP failure creating endpoint\n",
1582                  hostPeerName (cxn->myHost), cxn->ident);
1583         return RET_FAIL;
1584     }
1585
1586 #ifdef HAVE_SASL
1587     /* Set the SASL properties */
1588     result = SetSASLProperties(cxn->saslconn_lmtp, cxn->sockfd_lmtp, 
1589                                0, 0);
1590     
1591     if (result != RET_OK)
1592     {
1593         d_printf(0,"%s:%d:LMTP error setting SASL properties\n",
1594                  hostPeerName (cxn->myHost), cxn->ident);
1595         return RET_FAIL;
1596     }
1597 #endif /* HAVE_SASL */
1598
1599
1600     return RET_OK;
1601 }
1602
1603 static conn_ret SetupIMAPConnection(connection_t *cxn,
1604                                     char *serverName,
1605                                     int port)
1606 {
1607 #ifdef HAVE_SASL
1608     int saslresult;
1609 #endif /* HAVE_SASL */
1610     conn_ret result;
1611
1612     cxn->imap_port = port;
1613
1614     if (serverName==NULL)
1615     {
1616         d_printf(0,"%s:%d:IMAP Servername is null",
1617                  hostPeerName (cxn->myHost), cxn->ident);
1618         return RET_FAIL;        
1619     }
1620
1621 #ifdef HAVE_SASL
1622     /* Free the SASL connection if we already had one */
1623     if (cxn->imap_saslconn!=NULL)
1624     {
1625         sasl_dispose(&cxn->imap_saslconn);
1626     }
1627
1628     /* Start SASL */
1629     saslresult=sasl_client_new("imap",
1630                                serverName,
1631                                NULL,
1632                                NULL,
1633                                NULL,
1634                                0,
1635                                &cxn->imap_saslconn);
1636
1637     if (saslresult != SASL_OK)
1638     {
1639         d_printf(0,"%s:%d:IMAP Error creating a new SASL connection (%s)",
1640                  hostPeerName (cxn->myHost), cxn->ident,
1641                  sasl_errstring(saslresult,NULL,NULL));
1642         return RET_FAIL;
1643     }
1644 #endif /* HAVE_SASL */
1645
1646     /* Connect the Socket */
1647     result = init_net(serverName,
1648                       port,
1649                       &(cxn->imap_sockfd));
1650    
1651     if (result != RET_OK)
1652     {
1653         d_printf(0,"%s:%d:IMAP Unable to start network connection for IMAP",
1654                  hostPeerName (cxn->myHost), cxn->ident);
1655         return RET_FAIL;
1656     }
1657
1658     if (cxn->imap_respBuffer) free(cxn->imap_respBuffer);
1659     cxn->imap_respBuffer = xmalloc (4096);
1660     cxn->imap_respBuffer[0]='\0';
1661
1662     /* Free if we had an existing one */
1663     if (cxn->imap_endpoint != NULL)
1664     {
1665         delEndPoint(cxn->imap_endpoint);
1666         cxn->imap_endpoint = NULL;
1667     }
1668
1669     cxn->imap_endpoint = newEndPoint(cxn->imap_sockfd);
1670     if (cxn->imap_endpoint == NULL)
1671     {
1672         d_printf(0,"%s:%d:IMAP Failure creating imap endpoint\n",
1673                  hostPeerName (cxn->myHost), cxn->ident);
1674         return RET_FAIL;
1675     }
1676
1677 #ifdef HAVE_SASL
1678     /* Set the SASL properties */
1679     result = SetSASLProperties(cxn->imap_saslconn, cxn->imap_sockfd, 
1680                                0, 0);    
1681     if (result != RET_OK)
1682     {
1683         d_printf(0,"%s:%d:IMAP Error setting sasl properties",
1684                  hostPeerName (cxn->myHost), cxn->ident);
1685         return result;
1686     }
1687 #endif /* HAVE_SASL */
1688
1689
1690     return RET_OK;
1691 }
1692
1693 /************************* END Startup functions **********************************/
1694
1695 /* Return the response code for this line
1696    -1 if it doesn't seem to have one
1697 */
1698 static int ask_code(char *str)
1699 {
1700     int ret = 0;
1701     
1702     if (str==NULL) return -1;
1703
1704     if (strlen(str) < 3) return -1;
1705
1706     /* check to make sure 0-2 are digits */
1707     if ((isdigit((int) str[0])==0) ||
1708         (isdigit((int) str[1])==0) ||
1709         (isdigit((int) str[2])==0))
1710     {
1711         d_printf(0,
1712                  "Parse error: response does not begin with a code [%s]\n",
1713                  str);
1714         return -1;
1715     }
1716
1717
1718     ret = ((str[0]-'0')*100)+
1719           ((str[1]-'0')*10)+
1720           (str[2]-'0');
1721     
1722     return ret;
1723 }
1724
1725 /* is this a continuation or not?
1726    220-fdfsd is        (1)
1727    220 fdsfs is not    (0)
1728  */
1729
1730 static int ask_keepgoing(char *str)
1731 {
1732     if (str==NULL) return 0;
1733     if (strlen(str) < 4) return 0;
1734
1735     if (str[3]=='-') return 1;
1736
1737     return 0;
1738 }
1739
1740
1741 static conn_ret lmtp_listenintro(connection_t *cxn)
1742 {
1743     Buffer *readBuffers;
1744
1745     /* set up to receive */
1746     readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
1747     prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);    
1748
1749     cxn->lmtp_state = LMTP_READING_INTRO;
1750
1751     return RET_OK;
1752 }
1753
1754
1755
1756 /************************** IMAP functions ***********************/
1757
1758 static conn_ret imap_Connect(connection_t *cxn)
1759 {
1760     conn_ret result;
1761
1762     ASSERT(cxn->imap_sleepTimerId == 0);
1763
1764     /* make the IMAP connection */
1765     result = SetupIMAPConnection(cxn,
1766                                  cxn->ServerName,
1767                                  IMAP_PORT); 
1768
1769     /* Listen to the intro and start the authenticating process */
1770     result = imap_listenintro(cxn);
1771
1772     return result;
1773 }
1774
1775 /*
1776  * This is called when the data write timeout for the remote
1777  * goes off. We tear down the connection and notify our host.
1778  */
1779 static void imap_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
1780 {
1781   connection_t *cxn = (Connection) data ;
1782   const char *peerName ;
1783
1784   peerName = hostPeerName (cxn->myHost) ;
1785
1786   syslog (LOG_WARNING, "timeout for %s", peerName);
1787   d_printf(0, "%s: shutting down non-responsive IMAP connection (%s)\n",
1788            hostPeerName (cxn->myHost), 
1789            imap_stateToString(cxn->imap_state));
1790
1791   cxnLogStats (cxn,true) ;
1792
1793   imap_Disconnect(cxn);
1794 }
1795
1796 /*
1797  * This is called when the timeout for the reponse from the remote
1798  * goes off. We tear down the connection and notify our host.
1799  */
1800 static void imap_readTimeoutCbk (TimeoutId id, void *data)
1801 {
1802   Connection cxn = (Connection) data ;
1803   const char *peerName ;
1804
1805   ASSERT (id == cxn->imap_readBlockedTimerId) ;
1806
1807   peerName = hostPeerName (cxn->myHost) ;
1808
1809   warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
1810   d_printf(0, "%s:%d shutting down non-responsive IMAP connection (%s)\n",
1811            hostPeerName (cxn->myHost), cxn->ident,
1812            imap_stateToString(cxn->imap_state));
1813
1814   cxnLogStats (cxn,true);
1815
1816   if (cxn->imap_state == IMAP_DISCONNECTED)
1817     {
1818       imap_Disconnect(cxn);
1819       lmtp_Disconnect(cxn);
1820       delConnection (cxn) ;
1821     }
1822   else {
1823       imap_Disconnect(cxn);
1824   }      
1825 }
1826
1827 /*
1828  * Called by the EndPoint class when the timer goes off
1829  */
1830 static void imap_reopenTimeoutCbk (TimeoutId id, void *data)
1831 {
1832   Connection cxn = (Connection) data ;
1833
1834   ASSERT (id == cxn->imap_sleepTimerId) ;
1835
1836   cxn->imap_sleepTimerId = 0 ;
1837
1838   d_printf(1,"%s:%d:IMAP Reopen timer rang. Try to make new connection now\n",
1839            hostPeerName (cxn->myHost), cxn->ident) ;
1840   
1841   if (cxn->imap_state != IMAP_DISCONNECTED)
1842     {
1843       warn ("%s:%d cxnsleep connection in bad state: %s",
1844             hostPeerName (cxn->myHost), cxn->ident,
1845             imap_stateToString (cxn->imap_state)) ;
1846     }
1847   else {
1848       if (imap_Connect(cxn) != RET_OK)
1849           prepareReopenCbk(cxn, 0);
1850   }
1851 }
1852
1853 static void imap_Disconnect(connection_t *cxn)
1854 {
1855     clearTimer (cxn->imap_sleepTimerId) ;
1856     cxn->imap_sleepTimerId = 0;
1857     clearTimer (cxn->imap_readBlockedTimerId) ;
1858     clearTimer (cxn->imap_writeBlockedTimerId) ;
1859
1860     DeferAllArticles(cxn, &(cxn->imap_controlMsg_q)) ;      /* give any articles back to Host */
1861
1862     cxn->imap_state = IMAP_DISCONNECTED;
1863
1864     cxn->imap_disconnects++;
1865
1866     cxn->imap_respBuffer[0]='\0';
1867
1868     if (cxn->issue_quit == 0)
1869         prepareReopenCbk(cxn,0);
1870
1871     DeleteIfDisconnected(cxn);
1872 }
1873
1874 /************************** END IMAP functions ***********************/
1875
1876 /************************ LMTP functions **************************/
1877
1878 /*
1879  * Create a network lmtp connection
1880  * and start listening for the intro string
1881  *
1882  */
1883
1884 static conn_ret lmtp_Connect(connection_t *cxn)
1885 {
1886     conn_ret result;
1887
1888     ASSERT(cxn->lmtp_sleepTimerId == 0);
1889
1890     /* make the LMTP connection */
1891     result = SetupLMTPConnection(cxn,
1892                                  cxn->ServerName,
1893                                  LMTP_PORT);
1894
1895     if (result!=RET_OK) return result;
1896
1897     /* Listen to the intro */
1898     result = lmtp_listenintro(cxn);
1899
1900     return result;
1901 }
1902
1903
1904
1905 static void lmtp_Disconnect(connection_t *cxn)
1906 {
1907     clearTimer (cxn->lmtp_sleepTimerId) ;
1908     cxn->lmtp_sleepTimerId = 0;
1909     clearTimer (cxn->lmtp_readBlockedTimerId) ;
1910     clearTimer (cxn->lmtp_writeBlockedTimerId) ;
1911
1912     /* give any articles back to Host */
1913     DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q)) ;      
1914
1915     cxn->lmtp_state = LMTP_DISCONNECTED;
1916
1917     cxn->lmtp_disconnects++;
1918
1919     cxn->lmtp_respBuffer[0]='\0';
1920
1921     if (cxn->issue_quit == 0)
1922         prepareReopenCbk(cxn,1);
1923
1924     DeleteIfDisconnected(cxn);
1925 }
1926
1927
1928
1929 /*
1930  * Called by the EndPoint class when the timer goes off
1931  */
1932 static void lmtp_reopenTimeoutCbk (TimeoutId id, void *data)
1933 {
1934   Connection cxn = (Connection) data ;
1935
1936   ASSERT (id == cxn->lmtp_sleepTimerId) ;
1937
1938   cxn->lmtp_sleepTimerId = 0 ;
1939
1940   d_printf(1,"%s:%d:LMTP Reopen timer rang. Try to make new connection now\n",
1941            hostPeerName (cxn->myHost), cxn->ident) ;
1942         
1943   if (cxn->lmtp_state != LMTP_DISCONNECTED)
1944     {
1945       warn ("%s:%d cxnsleep connection in bad state: %s",
1946             hostPeerName (cxn->myHost), cxn->ident,
1947             lmtp_stateToString (cxn->lmtp_state)) ;
1948     }
1949   else {
1950       if (lmtp_Connect(cxn) != RET_OK)
1951           prepareReopenCbk(cxn, 1);
1952   }
1953 }
1954
1955 /*
1956  * Set up the callback used when the Connection is sleeping (i.e. will try
1957  * to reopen the connection).
1958  *
1959  * type (0 = imap, 1 = lmtp)
1960  */
1961 static void prepareReopenCbk (Connection cxn, int type)
1962 {
1963     /* xxx check state */
1964
1965
1966
1967     if (type == 0) {
1968
1969         cxn->imap_sleepTimerId = prepareSleep (imap_reopenTimeoutCbk, 
1970                                                cxn->imap_sleepTimeout, cxn) ;
1971         d_printf (1,"%s:%d IMAP connection error\n"
1972                   "  will try to reconnect in %d seconds\n",
1973                   hostPeerName (cxn->myHost), cxn->ident, 
1974                   cxn->imap_sleepTimeout) ;
1975     } else {
1976         cxn->lmtp_sleepTimerId = prepareSleep (lmtp_reopenTimeoutCbk, 
1977                                                cxn->lmtp_sleepTimeout, cxn) ;
1978         d_printf (1,"%s:%d:LMTP connection error\n"
1979                   "will try to reconnect in %d seconds\n",
1980                   hostPeerName (cxn->myHost), cxn->ident, 
1981                   cxn->lmtp_sleepTimeout) ;
1982     }
1983
1984     /* bump the sleep timer amount each time to wait longer and longer. Gets
1985        reset in resetConnection() */
1986     if (type == 0) {
1987         cxn->imap_sleepTimeout *= 2 ;
1988         if (cxn->imap_sleepTimeout > max_reconnect_period)
1989             cxn->imap_sleepTimeout = max_reconnect_period ;
1990     } else {
1991         cxn->lmtp_sleepTimeout *= 2 ;
1992         if (cxn->lmtp_sleepTimeout > max_reconnect_period)
1993             cxn->lmtp_sleepTimeout = max_reconnect_period ;
1994     }
1995 }
1996
1997 /*
1998  * This is called when the timeout for the reponse from the remote
1999  * goes off. We tear down the connection and notify our host.
2000  */
2001 static void lmtp_readTimeoutCbk (TimeoutId id, void *data)
2002 {
2003   Connection cxn = (Connection) data ;
2004   const char *peerName ;
2005
2006   ASSERT (id == cxn->lmtp_readBlockedTimerId) ;
2007
2008   peerName = hostPeerName (cxn->myHost) ;
2009
2010   warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
2011   d_printf(0,"%s:%d shutting down non-responsive LMTP connection (%s)\n",
2012            hostPeerName (cxn->myHost), cxn->ident,
2013            lmtp_stateToString(cxn->lmtp_state));
2014
2015   cxnLogStats (cxn,true) ;
2016
2017   if (cxn->lmtp_state == LMTP_DISCONNECTED) {
2018       imap_Disconnect(cxn);
2019       lmtp_Disconnect(cxn);
2020       delConnection (cxn) ;
2021   } else {
2022       lmtp_Disconnect(cxn);
2023   }      
2024 }
2025
2026
2027
2028 /*
2029  * This is called when the data write timeout for the remote
2030  * goes off. We tear down the connection and notify our host.
2031  */
2032 static void lmtp_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
2033 {
2034     connection_t *cxn = (Connection) data ;
2035     const char *peerName ;
2036
2037     peerName = hostPeerName (cxn->myHost) ;
2038
2039     syslog (LOG_WARNING, "timeout for %s", peerName);
2040     d_printf(0, "%s:%d shutting down non-responsive LMTP connection (%s)\n",
2041              hostPeerName (cxn->myHost), cxn->ident,
2042              lmtp_stateToString(cxn->lmtp_state)) ;
2043
2044     cxnLogStats (cxn,true);
2045
2046     lmtp_Disconnect(cxn);
2047 }
2048
2049 /************************ END LMTP functions **************************/
2050
2051 /************************** LMTP write functions ********************/
2052
2053 static conn_ret lmtp_noop(connection_t *cxn)
2054 {
2055     int result;
2056     char *p;
2057
2058     p = xstrdup("NOOP\r\n");
2059     result = WriteToWire_lmtpstr(cxn, p, strlen(p));
2060     if (result!=RET_OK) return result;
2061
2062     cxn->lmtp_state = LMTP_WRITING_NOOP;
2063
2064     return RET_OK;
2065 }
2066
2067 static conn_ret lmtp_IssueQuit(connection_t *cxn)
2068 {
2069     int result;
2070     char *p;
2071
2072     p = xstrdup("QUIT\r\n");
2073     result = WriteToWire_lmtpstr(cxn, p, strlen(p));
2074     if (result!=RET_OK) return result;
2075
2076     cxn->lmtp_state = LMTP_WRITING_QUIT;
2077
2078     return RET_OK;
2079 }
2080
2081 static conn_ret lmtp_getcapabilities(connection_t *cxn)
2082 {
2083     int result;
2084     char *p;
2085
2086     if (cxn->lmtp_capabilities != NULL)
2087     {
2088         if (cxn->lmtp_capabilities->saslmechs) {
2089             free( cxn->lmtp_capabilities->saslmechs);
2090         }
2091         free( cxn->lmtp_capabilities );
2092         cxn->lmtp_capabilities = NULL;
2093     }
2094
2095     cxn->lmtp_capabilities = xcalloc (1, sizeof(lmtp_capabilities_t));
2096     cxn->lmtp_capabilities->saslmechs = NULL;
2097
2098 #ifdef SMTPMODE
2099     p = concat("EHLO ", hostname, "\r\n", (char *) 0);
2100 #else
2101     p = concat("LHLO ", hostname, "\r\n", (char *) 0);
2102 #endif /* SMTPMODE */
2103
2104     result = WriteToWire_lmtpstr(cxn, p, strlen(p));
2105     if (result!=RET_OK) return result;
2106
2107     cxn->lmtp_state = LMTP_WRITING_LHLO;
2108
2109     return RET_OK;    
2110 }
2111
2112 #ifdef HAVE_SASL
2113 static conn_ret lmtp_authenticate(connection_t *cxn)
2114 {
2115     int saslresult;
2116     
2117     const char *mechusing;
2118     const char *out;
2119     unsigned int outlen;
2120     char *inbase64;
2121     int inbase64len;
2122     int status;
2123     int result;
2124
2125     char *p;
2126
2127     sasl_interact_t *client_interact=NULL;
2128
2129
2130     saslresult=sasl_client_start(cxn->saslconn_lmtp, 
2131                                  cxn->lmtp_capabilities->saslmechs,
2132                                  &client_interact,
2133                                  &out, &outlen,
2134                                  &mechusing);
2135
2136
2137
2138     if ((saslresult != SASL_OK) && 
2139         (saslresult != SASL_CONTINUE)) {
2140
2141         d_printf(0,"%s:%d:LMTP Error calling sasl_client_start (%s)\n",
2142                  hostPeerName (cxn->myHost), cxn->ident,
2143                  sasl_errstring(saslresult, NULL, NULL));
2144         return RET_FAIL;
2145     }
2146
2147     d_printf(1,"%s:%d:LMTP Decided to try to authenticate with SASL mechanism=%s\n",           
2148              hostPeerName (cxn->myHost), cxn->ident,mechusing);
2149
2150     if (!out)
2151     {
2152         /* no initial client response */
2153         p = concat("AUTH ", mechusing, "\r\n", (char *) 0);
2154     } else if (!outlen) {
2155         /* empty initial client response */
2156         p = concat("AUTH ", mechusing, " =\r\n", (char *) 0);
2157     } else {
2158         /* initial client response - convert to base64 */
2159         inbase64 = xmalloc(outlen*2+10);
2160
2161         saslresult = sasl_encode64(out, outlen,
2162                                    inbase64, outlen*2+10,
2163                                    (unsigned *) &inbase64len);
2164         if (saslresult != SASL_OK) return RET_FAIL;
2165         p = concat("AUTH ", mechusing, " ", inbase64, "\r\n", (char *) 0);
2166         free(inbase64);
2167     }
2168
2169     result = WriteToWire_lmtpstr(cxn, p, strlen(p));
2170
2171     cxn->lmtp_state = LMTP_WRITING_STARTAUTH;
2172
2173     return RET_OK;
2174 }
2175
2176 static imt_stat lmtp_getauthline(char *str, char **line, int *linelen)
2177 {
2178   char buf[4096];
2179   int saslresult;
2180   int response_code = -1;
2181   
2182   response_code = ask_code(str);
2183
2184   if (response_code == 334) {
2185       
2186       /* continue */
2187
2188   } else if (response_code == 235) {
2189
2190       /* woohoo! authentication complete */
2191       return STAT_OK;
2192
2193   } else {
2194       /* failure of some sort */
2195       d_printf(0,"?:?:LMTP Authentication failure (%d) [%s]\n",
2196                response_code,str);
2197       return STAT_NO;
2198   }
2199
2200   str += 4; /* jump past the "334 " */
2201
2202   *line = xmalloc(strlen(str)+30);
2203   if ((*line)==NULL) {
2204       return STAT_NO;
2205   }
2206
2207   /* decode this line */      
2208   saslresult = sasl_decode64(str, strlen(str), 
2209                              *line, strlen(str)+1, (unsigned *) linelen);
2210   if (saslresult != SASL_OK) {
2211       d_printf(0,"?:?:LMTP base64 decoding error\n");
2212       return STAT_NO;
2213   }
2214
2215   return STAT_CONT;
2216 }
2217 #endif /* HAVE_SASL */
2218
2219 static void lmtp_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
2220         void *d)
2221 {
2222     connection_t *cxn = (connection_t *) d;
2223     Buffer *readBuffers;
2224
2225     clearTimer (cxn->lmtp_writeBlockedTimerId) ;
2226
2227     /* Free the string that was written */
2228     freeBufferArray (b);
2229     if (cxn->lmtp_tofree_str!=NULL)
2230     {
2231         free(cxn->lmtp_tofree_str);
2232         cxn->lmtp_tofree_str=NULL;
2233     }
2234
2235     /* set up to receive */
2236     readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
2237     prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
2238
2239    /* set up the response timer. */
2240     clearTimer (cxn->lmtp_readBlockedTimerId) ;
2241
2242     if (cxn->lmtp_readTimeout > 0)
2243         cxn->lmtp_readBlockedTimerId = prepareSleep (lmtp_readTimeoutCbk, 
2244                                                      cxn->lmtp_readTimeout, cxn) ;
2245
2246
2247     switch (cxn->lmtp_state)
2248         {
2249             
2250         case LMTP_WRITING_LHLO:
2251             cxn->lmtp_state = LMTP_READING_LHLO;
2252             break;
2253
2254         case LMTP_WRITING_STARTAUTH:
2255         case LMTP_WRITING_STEPAUTH:
2256
2257             cxn->lmtp_state = LMTP_READING_STEPAUTH;
2258
2259             break;
2260
2261         case LMTP_WRITING_UPTODATA:
2262             /* expect result to rset */
2263             cxn->lmtp_state = LMTP_READING_RSET;
2264             break;
2265
2266         case LMTP_WRITING_CONTENTS:
2267             /* so we sent the whole DATA command
2268                let's see what the server responded */
2269
2270             cxn->lmtp_state = LMTP_READING_CONTENTS;
2271
2272             break;
2273
2274         case LMTP_WRITING_NOOP:
2275             cxn->lmtp_state = LMTP_READING_NOOP;
2276             break;
2277
2278         case LMTP_WRITING_QUIT:
2279             cxn->lmtp_state = LMTP_READING_QUIT;
2280             break;
2281
2282         default:
2283
2284             d_printf(0,"%s:%d:LMTP Unknown state. Internal error\n",
2285                      hostPeerName (cxn->myHost), cxn->ident) ;
2286
2287             break;
2288         }
2289 }
2290
2291 /************************** END LMTP write functions ********************/
2292
2293 /************************** IMAP sending functions ************************/
2294
2295
2296 static void imap_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
2297         void *d)
2298 {
2299     connection_t *cxn = (connection_t *) d;
2300     Buffer *readBuffers;
2301
2302     clearTimer (cxn->imap_writeBlockedTimerId) ;
2303
2304     /* free the string we just wrote out */
2305     freeBufferArray (b);
2306     if (cxn->imap_tofree_str!=NULL)
2307     {
2308         free(cxn->imap_tofree_str);
2309         cxn->imap_tofree_str=NULL;
2310     }
2311
2312     /* set up to receive */
2313     readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
2314     prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);
2315
2316     /* set up the response timer. */
2317     clearTimer (cxn->imap_readBlockedTimerId) ;
2318
2319     if (cxn->imap_readTimeout > 0)
2320         cxn->imap_readBlockedTimerId = prepareSleep(imap_readTimeoutCbk, 
2321                                                     cxn->imap_readTimeout, 
2322                                                     cxn);
2323
2324     switch (cxn->imap_state) {
2325     case IMAP_WRITING_CAPABILITY:
2326         cxn->imap_state = IMAP_READING_CAPABILITY;
2327         break;
2328
2329     case IMAP_WRITING_STEPAUTH:
2330     case IMAP_WRITING_STARTAUTH:
2331         cxn->imap_state = IMAP_READING_STEPAUTH;
2332         break;
2333
2334     case IMAP_WRITING_CREATE:
2335         cxn->imap_state = IMAP_READING_CREATE;
2336         break;
2337
2338     case IMAP_WRITING_DELETE:
2339         cxn->imap_state = IMAP_READING_DELETE;
2340         break;
2341
2342     case IMAP_WRITING_SELECT:
2343         cxn->imap_state = IMAP_READING_SELECT;
2344         break;
2345
2346     case IMAP_WRITING_SEARCH:
2347         cxn->imap_state = IMAP_READING_SEARCH;
2348         break;
2349
2350     case IMAP_WRITING_STORE:
2351         cxn->imap_state = IMAP_READING_STORE;
2352         break;
2353
2354     case IMAP_WRITING_CLOSE:
2355         cxn->imap_state = IMAP_READING_CLOSE;
2356         break;
2357
2358     case IMAP_WRITING_NOOP:
2359         cxn->imap_state = IMAP_READING_NOOP;
2360         break;
2361
2362     case IMAP_WRITING_QUIT:
2363         cxn->imap_state = IMAP_READING_QUIT;
2364         break;
2365
2366     default:
2367         d_printf(0,"%s:%d:IMAP invalid connection state\n",
2368                  hostPeerName (cxn->myHost), cxn->ident) ;
2369         imap_Disconnect(cxn);
2370         break;
2371     }
2372 }
2373
2374 /*
2375  * Tag is already allocated
2376  */
2377
2378 static void imap_GetTag(connection_t *cxn)
2379 {
2380     sprintf(cxn->imap_currentTag,"%06d",cxn->imap_tag_num);
2381     cxn->imap_tag_num++;
2382     if (cxn->imap_tag_num >= 999999)
2383     {
2384         cxn->imap_tag_num = 0;
2385     }
2386 }
2387
2388 #ifdef HAVE_SASL
2389 static conn_ret imap_sendAuthStep(connection_t *cxn, char *str)
2390 {
2391     conn_ret result;
2392     int saslresult;
2393     char in[4096];
2394     unsigned int inlen;
2395     const char *out;
2396     unsigned int outlen;
2397     char *inbase64;
2398     unsigned int inbase64len;
2399
2400     /* base64 decode it */
2401
2402     saslresult = sasl_decode64(str, strlen(str), 
2403                                in, strlen(str)+1, &inlen);
2404     if (saslresult != SASL_OK) {
2405         d_printf(0,"%s:%d:IMAP base64 decoding error\n",
2406                  hostPeerName (cxn->myHost), cxn->ident) ;
2407         return RET_FAIL;
2408     }
2409
2410     saslresult=sasl_client_step(cxn->imap_saslconn,
2411                                 in,
2412                                 inlen,
2413                                 NULL,
2414                                 &out,
2415                                 &outlen);
2416
2417     /* check if sasl succeeded */
2418     if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {
2419
2420         d_printf(0,"%s:%d:IMAP sasl_client_step failed with %s\n",
2421                  hostPeerName (cxn->myHost), cxn->ident,
2422                  sasl_errstring(saslresult,NULL,NULL));
2423         cxn->imap_state = IMAP_CONNECTED_NOTAUTH;
2424         return RET_FAIL;
2425     }
2426
2427     inbase64 = xmalloc(outlen * 2 + 10);
2428
2429     /* convert to base64 */
2430     saslresult = sasl_encode64(out, outlen,
2431                                inbase64, outlen*2, (unsigned *) &inbase64len);
2432
2433     if (saslresult != SASL_OK) return RET_FAIL;
2434
2435     /* append endline */
2436     strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10 - inbase64len);
2437     inbase64len+=2;
2438     
2439     /* send to server */
2440     result = WriteToWire_imapstr(cxn,inbase64, inbase64len);
2441     
2442     cxn->imap_state = IMAP_WRITING_STEPAUTH;
2443
2444     return result;
2445 }
2446 #endif /* HAVE_SASL */
2447
2448 static conn_ret imap_sendAuthenticate(connection_t *cxn)
2449 {
2450     int result;
2451
2452     char *p;
2453
2454 #ifdef HAVE_SASL
2455     const char *mechusing;
2456     char *inbase64;
2457     int inbase64len;
2458     int saslresult=SASL_NOMECH;
2459
2460     sasl_interact_t *client_interact=NULL;
2461
2462     if (cxn->imap_capabilities->saslmechs) {
2463         saslresult=sasl_client_start(cxn->imap_saslconn, 
2464                                      cxn->imap_capabilities->saslmechs,
2465                                      &client_interact,
2466                                      NULL, NULL,
2467                                      &mechusing);
2468     }
2469
2470
2471
2472     /* If no mechs try "login" */
2473     if (saslresult == SASL_NOMECH)
2474     {
2475
2476 #else /* HAVE_SASL */
2477
2478      { /* always do login */
2479
2480 #endif /* HAVE_SASL */
2481         d_printf(1,"%s:%d:IMAP No mechanism found. Trying login method\n",
2482                  hostPeerName (cxn->myHost), cxn->ident) ;
2483
2484         if (cxn->imap_capabilities->logindisabled==1)
2485         {
2486             d_printf(0,"%s:%d:IMAP Login command w/o security layer not allowed on this server\n",
2487                      hostPeerName (cxn->myHost), cxn->ident);
2488             return RET_FAIL;
2489         }
2490
2491         if (deliver_authname==NULL)
2492         {
2493             d_printf(0,"%s:%d:IMAP Unable to log in because can't find a authname\n",
2494                      hostPeerName (cxn->myHost), cxn->ident) ;
2495             return RET_FAIL;
2496         }
2497
2498         if (deliver_password==NULL)
2499         {
2500             d_printf(0,"%s:%d:IMAP Unable to log in because can't find a password\n",
2501                      hostPeerName (cxn->myHost), cxn->ident) ;
2502             return RET_FAIL;
2503         }
2504
2505         imap_GetTag(cxn);
2506
2507         p = concat(cxn->imap_currentTag, " LOGIN ", deliver_authname, " \"",
2508                    deliver_password, "\"\r\n", (char *) 0);
2509         
2510         result = WriteToWire_imapstr(cxn, p, strlen(p));
2511         
2512         cxn->imap_state = IMAP_WRITING_STARTAUTH;
2513         
2514         return RET_OK;       
2515     }
2516
2517 #ifdef HAVE_SASL
2518     if ((saslresult != SASL_OK) && 
2519         (saslresult != SASL_CONTINUE)) {
2520
2521         d_printf(0,"%s:%d:IMAP Error calling sasl_client_start (%s) mechusing = %s\n",
2522                  hostPeerName (cxn->myHost), cxn->ident,
2523                  sasl_errstring(saslresult, NULL, NULL), mechusing);
2524         return RET_FAIL;
2525     }
2526
2527     d_printf(1,"%s:%d:IMAP Trying to authenticate to imap with %s mechanism\n",
2528              hostPeerName (cxn->myHost), cxn->ident,
2529              mechusing);
2530
2531     imap_GetTag(cxn);
2532
2533     p = concat(cxn->imap_currentTag, " AUTHENTICATE ", mechusing, "\r\n",
2534                (char *) 0);
2535     result = WriteToWire_imapstr(cxn, p, strlen(p));
2536
2537     cxn->imap_state = IMAP_WRITING_STARTAUTH;
2538
2539     return RET_OK;
2540 #endif /* HAVE_SASL */
2541 }
2542
2543 static conn_ret imap_CreateGroup(connection_t *cxn, char *bboard)
2544 {
2545     conn_ret result;
2546     char *tosend;
2547
2548     d_printf(1,"%s:%d:IMAP Ok creating group [%s]\n",
2549              hostPeerName (cxn->myHost), cxn->ident,
2550              bboard);
2551
2552     imap_GetTag(cxn);
2553
2554     tosend = concat(cxn->imap_currentTag, " CREATE ", bboard, "\r\n",
2555                     (char *) 0);
2556
2557     result = WriteToWire_imapstr(cxn, tosend, -1);
2558     if (result!=RET_OK) return result;
2559     
2560     cxn->imap_state = IMAP_WRITING_CREATE;
2561     
2562     return RET_OK;
2563 }
2564
2565 static conn_ret imap_DeleteGroup(connection_t *cxn, char *bboard)
2566 {
2567     conn_ret result;
2568     char *tosend;
2569
2570     d_printf(1,"%s:%d:IMAP Ok removing bboard [%s]\n",
2571                  hostPeerName (cxn->myHost), cxn->ident, bboard);
2572
2573     imap_GetTag(cxn);
2574
2575     tosend = concat(cxn->imap_currentTag, " DELETE ", bboard, "\r\n",
2576                     (char *) 0);
2577     result = WriteToWire_imapstr(cxn, tosend, -1);
2578     if (result!=RET_OK) return result;
2579
2580     cxn->imap_state = IMAP_WRITING_DELETE;
2581     
2582     return RET_OK;
2583 }
2584
2585 static conn_ret imap_CancelMsg(connection_t *cxn, char *newsgroup)
2586 {
2587     conn_ret result;
2588     char *tosend;
2589
2590     ASSERT(newsgroup);
2591
2592     imap_GetTag(cxn);
2593
2594     /* select mbox */
2595     tosend = concat(cxn->imap_currentTag, " SELECT ", newsgroup, "\r\n",
2596                     (char *) 0);
2597     result = WriteToWire_imapstr(cxn, tosend, -1);
2598     if (result != RET_OK) return result;
2599
2600     cxn->imap_state = IMAP_WRITING_SELECT;
2601
2602     hostArticleOffered (cxn->myHost, cxn);
2603
2604     return RET_OK;
2605 }
2606
2607 static conn_ret imap_sendSearch(connection_t *cxn, char *msgid)
2608 {
2609     conn_ret result;
2610     char *tosend;
2611
2612     ASSERT(msgid);
2613
2614     imap_GetTag(cxn);
2615
2616     /* preform search */
2617     tosend = concat(cxn->imap_currentTag,
2618                     " UID SEARCH header \"Message-ID\" \"", msgid, "\"\r\n",
2619                     (char *) 0);
2620     result = WriteToWire_imapstr(cxn, tosend, -1);
2621     if (result != RET_OK) return result;
2622
2623     cxn->imap_state = IMAP_WRITING_SEARCH;
2624
2625     return RET_OK;
2626 }
2627
2628 static conn_ret imap_sendKill(connection_t *cxn, unsigned uid)
2629 {
2630     conn_ret result;
2631     char *tosend;
2632     size_t length;
2633
2634     imap_GetTag(cxn);
2635
2636     length = 7 + 50 + 20;
2637     tosend = xmalloc(length);
2638     snprintf(tosend,length,"%s UID STORE %d +FLAGS.SILENT (\\Deleted)\r\n",
2639              cxn->imap_currentTag, uid);
2640
2641     result = WriteToWire_imapstr(cxn, tosend, -1);
2642     if (result != RET_OK) return result;
2643
2644     cxn->imap_state = IMAP_WRITING_STORE;
2645
2646     return RET_OK;
2647 }
2648
2649 static conn_ret imap_sendSimple(connection_t *cxn, const char *atom, int st)
2650 {
2651     char *tosend;
2652     conn_ret result;
2653
2654     imap_GetTag(cxn);
2655     tosend = concat(cxn->imap_currentTag, " ", atom, "\r\n", (char *) 0);
2656
2657     result = WriteToWire_imapstr(cxn, tosend, -1);
2658     if (result != RET_OK) return result;
2659
2660     cxn->imap_state = st;
2661
2662     return RET_OK;
2663 }
2664
2665 static conn_ret imap_sendClose(connection_t *cxn)
2666 {
2667     return imap_sendSimple(cxn, "CLOSE", IMAP_WRITING_CLOSE);
2668 }
2669
2670 static conn_ret imap_sendQuit(connection_t *cxn)
2671 {
2672     return imap_sendSimple(cxn, "LOGOUT", IMAP_WRITING_QUIT);
2673 }
2674
2675 static conn_ret imap_noop(connection_t *cxn)
2676 {
2677     return imap_sendSimple(cxn, "NOOP", IMAP_WRITING_NOOP);
2678 }
2679         
2680
2681 static conn_ret imap_sendCapability(connection_t *cxn)
2682 {
2683     return imap_sendSimple(cxn, "CAPABILITY", IMAP_WRITING_CAPABILITY);
2684 }
2685
2686 /************************** END IMAP sending functions ************************/
2687
2688 /************************** IMAP reading functions ***************************/
2689
2690 static conn_ret imap_listenintro(connection_t *cxn)
2691 {
2692     Buffer *readBuffers;
2693
2694     /* set up to receive */
2695     readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
2696     prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);    
2697
2698     cxn->imap_state = IMAP_READING_INTRO;
2699
2700     return RET_OK;
2701 }
2702
2703 static conn_ret imap_ParseCapability(char *string, imap_capabilities_t **caps)
2704 {
2705     char *str = string;
2706     char *start = str;
2707     size_t mechlen;
2708
2709     /* allocate the caps structure if it doesn't already exist */
2710     if ( (*caps) == NULL)
2711         (*caps) = xcalloc(1, sizeof(imap_capabilities_t));
2712
2713     while ( (*str) != '\0')
2714     {
2715
2716         while (((*str) != '\0') && ((*str)!=' '))
2717         {
2718             str++;
2719         }
2720         
2721         if ( (*str) != '\0')
2722         {
2723             *str = '\0';
2724             str++;
2725         }
2726
2727         if ( strcasecmp(start,"IMAP4")==0)
2728         {
2729             (*caps)->imap4 = 1;
2730         } else if (strcasecmp(start,"LOGINDISABLED")==0) {
2731             (*caps)->logindisabled = 1;
2732         } else if ( strncmp(start, "AUTH=", 5)==0) {
2733             
2734             if ( (*caps)->saslmechs == NULL)
2735             {
2736                 (*caps)->saslmechs = xstrdup (start + 5);
2737             } else {
2738                 mechlen = strlen((*caps)->saslmechs) + 1;
2739                 mechlen += strlen(start + 5) + 1;
2740                 (*caps)->saslmechs = xrealloc((*caps)->saslmechs, mechlen);
2741                 strlcat((*caps)->saslmechs, " ", mechlen);
2742                 strlcat((*caps)->saslmechs, start + 5, mechlen);
2743             }
2744         }
2745         
2746         start = str;
2747           
2748     }
2749
2750     if ((*caps)->saslmechs) {
2751         d_printf(1,"?:?:IMAP parsed capabilities: saslmechs = %s\n",
2752                  (*caps)->saslmechs);
2753     }
2754
2755     return RET_OK;
2756 }
2757
2758
2759 static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
2760 {
2761     connection_t *cxn = (connection_t *) d;
2762     Buffer *readBuffers ;
2763
2764     int okno;
2765     char *str;
2766     char strbuf[4096];
2767     char *linestart;
2768     conn_ret ret;
2769     char *p;
2770
2771     p = bufferBase(b[0]);
2772
2773     /* Add what we got to our internal read buffer */
2774     bufferAddNullByte (b[0]) ;
2775
2776     if (i != IoDone) {
2777         errno = endPointErrno(e);
2778
2779         syslog(LOG_ERR, "%s:%d IMAP i/o failed: %m",
2780                hostPeerName (cxn->myHost), cxn->ident);
2781         freeBufferArray (b);
2782         imap_Disconnect(cxn);
2783         return;
2784     }
2785
2786     if (strchr (p, '\n') == NULL) {
2787         /* partial read. expand buffer and retry */
2788         
2789         if (expandBuffer (b[0], BUFFER_EXPAND_AMOUNT)==false) {
2790             d_printf(0,"%s:%d:IMAP expanding buffer returned false\n",
2791                      hostPeerName (cxn->myHost), cxn->ident);
2792             
2793             imap_Disconnect(cxn);
2794             return;
2795         }
2796         readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
2797         
2798         if (!prepareRead (e, readBuffers, imap_readCB, cxn, 1)) {
2799             imap_Disconnect(cxn);
2800         }
2801         
2802         freeBufferArray (b);
2803         return;
2804     }
2805
2806     clearTimer (cxn->imap_readBlockedTimerId) ;
2807
2808     /* we got something. add to our buffer and free b */
2809
2810     strcat(cxn->imap_respBuffer, p);
2811
2812     bufferSetDataSize( b[0], 0);
2813
2814     freeBufferArray (b);
2815
2816
2817
2818     /* goto here to take another step */
2819  reset:
2820
2821     /* see if we have a full line */
2822     ret = GetLine( cxn->imap_respBuffer , strbuf, sizeof(strbuf));
2823     str = strbuf;
2824     linestart = str;
2825
2826     /* if we don't have a full line */
2827     if ( ret == RET_NO_FULLLINE)
2828         {
2829
2830             readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
2831
2832             if ( !prepareRead (e, readBuffers, imap_readCB, cxn, 1) )
2833                 {
2834                     imap_Disconnect(cxn);
2835                 }
2836             return;
2837
2838         } else if (ret!=RET_OK)
2839             {
2840                 return;
2841             }
2842
2843     /* if untagged */
2844     if ((str[0]=='*') && (str[1]==' '))
2845     {
2846         str+=2;
2847         
2848         /* now figure out what kind of untagged it is */
2849         if (strncasecmp(str,"CAPABILITY ",11)==0)
2850         {
2851             str+=11;
2852             
2853             imap_ParseCapability(str,&(cxn->imap_capabilities));
2854             
2855         } else if (strncasecmp(str,"SEARCH",6)==0) {
2856
2857             str+=6;
2858             
2859             if ( (*str) == ' ')
2860             {
2861                 str++;
2862                 
2863                 cxn->current_control->data.control->uid = atoi(str);
2864                 
2865                 d_printf(1,"%s:%d:IMAP i think the UID = %ld\n",
2866                          hostPeerName (cxn->myHost), cxn->ident,
2867                          cxn->current_control->data.control->uid);
2868             } else {
2869                 /* it's probably a blank uid (i.e. message doesn't exist) */
2870                 cxn->current_control->data.control->uid = (unsigned long)-1;
2871             }
2872
2873             
2874         } else if (strncasecmp(str,"OK ",3)==0) {
2875             
2876             if (cxn->imap_state==IMAP_READING_INTRO)
2877             {
2878                 imap_sendCapability(cxn); /* xxx errors */
2879                 return;
2880                 
2881             } else {
2882                 
2883             }
2884             
2885             
2886         } else {
2887             /* untagged command not understood */
2888         }
2889
2890         /* always might be more to look at */
2891         goto reset;
2892
2893     } else if ((str[0]=='+') && (str[1]==' ')) {
2894
2895         str+=2;
2896
2897         if (cxn->imap_state == IMAP_READING_STEPAUTH)
2898         {
2899 #ifdef HAVE_SASL
2900             if (imap_sendAuthStep(cxn, str)!=RET_OK)
2901             {
2902                 imap_Disconnect(cxn);
2903             }
2904 #else
2905             d_printf(0,"%s:%d:IMAP got a '+ ...' without SASL. Something's wrong\n",
2906                      hostPeerName (cxn->myHost), cxn->ident);
2907             imap_Disconnect(cxn);
2908 #endif /* HAVE_SASL */
2909
2910             return;
2911         } else {
2912             d_printf(0,"%s:%d:IMAP got a '+ ...' in state where not expected\n",
2913                      hostPeerName (cxn->myHost), cxn->ident);
2914             imap_Disconnect(cxn);
2915             return;
2916         }
2917         
2918
2919         
2920     } else if (strncmp(str, cxn->imap_currentTag, IMAP_TAGLENGTH)==0) { 
2921         /* matches our tag */
2922         str += IMAP_TAGLENGTH;
2923         
2924         if (str[0]!=' ')
2925         {
2926             d_printf(0,"%s:%d:IMAP Parse error: tag with no space afterward\n",
2927                      hostPeerName (cxn->myHost), cxn->ident);
2928             imap_Disconnect(cxn);
2929             return;         
2930         }
2931         str++;
2932
2933         /* should be OK/NO */
2934         if (strncmp(str,"OK",2)==0)
2935         {
2936             okno = 1;
2937         } else {
2938             okno = 0;
2939         }
2940         
2941         switch(cxn->imap_state)
2942             {
2943             case IMAP_READING_CAPABILITY:
2944
2945                 if (okno==1) {
2946                     if (imap_sendAuthenticate(cxn)!=RET_OK)
2947                     {
2948                         d_printf(0,"%s:%d:IMAP sendauthenticate failed\n",
2949                                  hostPeerName (cxn->myHost), cxn->ident);
2950                         imap_Disconnect(cxn);
2951                     }
2952                     return;
2953                 } else {
2954                     d_printf(0,"%s:%d:IMAP CAPABILITY gave a NO response\n",
2955                              hostPeerName (cxn->myHost), cxn->ident);
2956                     imap_Disconnect(cxn);
2957                 }
2958                 return;
2959                 
2960                 break;
2961             case IMAP_READING_STEPAUTH:
2962                 
2963                 if (okno == 1) {
2964
2965                     cxn->imap_sleepTimeout = init_reconnect_period ;
2966
2967                     cxn->imap_timeCon = theTime () ;
2968                     cxn->timeCon = theTime () ;
2969                     
2970                     d_printf(0,"%s:%d IMAP authentication succeeded\n",
2971                              hostPeerName (cxn->myHost), cxn->ident);
2972
2973                     cxn->imap_disconnects=0;
2974                     
2975                     cxn->imap_state = IMAP_IDLE_AUTHED;
2976
2977                     /* try to send a message if we have one */
2978                     
2979                     imap_ProcessQueue(cxn);
2980                 } else {
2981                     d_printf(0,"%s:%d:IMAP Authentication failed with [%s]\n",               
2982                              hostPeerName (cxn->myHost), cxn->ident,str);
2983                     imap_Disconnect(cxn);
2984                 }
2985
2986                 return;
2987
2988                 break;
2989                 
2990             case IMAP_READING_CREATE:
2991
2992                     if (okno==1) {
2993                         
2994                         d_printf(1,"%s:%d:IMAP Create of bboard successful\n",
2995                                  hostPeerName (cxn->myHost), cxn->ident);
2996                                  
2997                         cxn->create_succeeded++;
2998
2999                         /* we can delete article now */
3000                         QueueForgetAbout(cxn, cxn->current_control, 
3001                                          MSG_SUCCESS);
3002                     } else {
3003                         d_printf(1,"%s:%d:IMAP Create failed with [%s] for %s\n",
3004                                  hostPeerName (cxn->myHost), cxn->ident,str,
3005                                  cxn->current_control->data.control->folder);
3006
3007                         ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
3008                     }
3009
3010                     imap_ProcessQueue(cxn);
3011                         
3012                     break;
3013                     
3014             case IMAP_READING_DELETE:
3015
3016                     if (okno==1) {
3017                         d_printf(1,"%s:%d:IMAP Delete successful\n",
3018                                  hostPeerName (cxn->myHost), cxn->ident);
3019                         cxn->remove_succeeded++;
3020
3021                         /* we can delete article now */
3022                         QueueForgetAbout(cxn, cxn->current_control, 
3023                                          MSG_SUCCESS);
3024                     } else {
3025                         d_printf(1,"%s:%d:IMAP Delete mailbox failed with [%s] for %s\n",
3026                                  hostPeerName (cxn->myHost), cxn->ident,str,
3027                                  cxn->current_control->data.control->folder);
3028
3029                         ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
3030                         
3031                     }
3032
3033                     imap_ProcessQueue(cxn);
3034                     return;
3035                         
3036                     break;
3037
3038             case IMAP_READING_SELECT:
3039
3040                     if (okno==1) {
3041
3042                         imap_sendSearch(cxn, cxn->current_control->data.control->msgid);
3043                         return;
3044
3045                     } else {
3046                         d_printf(1,"%s:%d:IMAP Select failed with [%s] for %s\n",
3047                                  hostPeerName (cxn->myHost), cxn->ident,str,
3048                                  cxn->current_control->data.control->folder);
3049
3050                         ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
3051                         
3052                         cxn->imap_state = IMAP_IDLE_AUTHED;
3053
3054                         imap_ProcessQueue(cxn);
3055                         return;
3056                     }
3057
3058                     break;
3059
3060             case IMAP_READING_SEARCH:
3061                 /* if no message let's forget about it */
3062                 if (cxn->current_control->data.control->uid
3063                         == (unsigned long)-1) {
3064                     d_printf(2, "%s:%d:IMAP Search didn't find the message\n",
3065                              hostPeerName (cxn->myHost), cxn->ident);
3066                     QueueForgetAbout(cxn, cxn->current_control, 
3067                                      MSG_FAIL_DELIVER);
3068                     if (imap_sendClose(cxn) != RET_OK)
3069                         imap_Disconnect(cxn);
3070                     return;
3071                 }
3072
3073                 if (okno==1) {
3074                     /* we got a uid. let's delete it */
3075                     if (imap_sendKill(cxn, 
3076                                       cxn->current_control->data.control->uid)
3077                             != RET_OK)
3078                         imap_Disconnect(cxn);
3079                     return;
3080                 } else {
3081                     d_printf(0, "%s:%d IMAP Received NO response to SEARCH\n",
3082                              hostPeerName (cxn->myHost), cxn->ident);
3083                     ReQueue(cxn, &(cxn->imap_controlMsg_q), 
3084                             cxn->current_control);
3085                     
3086                     if (imap_sendClose(cxn) != RET_OK)
3087                         imap_Disconnect(cxn);
3088                     return;
3089                 }
3090                 break;
3091
3092             case IMAP_READING_STORE:
3093
3094                     if (okno==1) {
3095
3096                         d_printf(1,"%s:%d:IMAP Processed a Cancel fully\n",
3097                                  hostPeerName (cxn->myHost), cxn->ident);
3098
3099                         /* we can delete article now */
3100                         QueueForgetAbout(cxn, cxn->current_control,
3101                                          MSG_SUCCESS);
3102
3103                         cxn->cancel_succeeded++;
3104
3105                         if (imap_sendClose(cxn) != RET_OK)
3106                             imap_Disconnect(cxn);
3107                         return;
3108
3109                     } else {
3110
3111                         d_printf(1,"%s:%d:IMAP Store failed\n",
3112                                  hostPeerName (cxn->myHost), cxn->ident);
3113                         ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
3114
3115                         if (imap_sendClose(cxn) != RET_OK)
3116                             imap_Disconnect(cxn);
3117                         return;
3118                     }
3119
3120                     break;
3121
3122             case IMAP_READING_NOOP:
3123                 cxn->imap_state = IMAP_IDLE_AUTHED;
3124                 return;
3125                 break;
3126
3127             case IMAP_READING_CLOSE:
3128                 if (!okno) {
3129                     /* we can't do anything about it */
3130                     d_printf(1,"%s:%d:IMAP Close failed\n",
3131                              hostPeerName (cxn->myHost), cxn->ident);
3132                 }
3133
3134                 cxn->imap_state = IMAP_IDLE_AUTHED;
3135                 
3136                 imap_ProcessQueue(cxn);             
3137                 return;
3138                 break;
3139
3140             case IMAP_READING_QUIT:
3141                 
3142                 /* we don't care if the server said OK or NO just
3143                    that it said something */
3144
3145                 d_printf(1,"%s:%d:IMAP Read quit response\n",
3146                          hostPeerName (cxn->myHost), cxn->ident);
3147                 
3148                 cxn->imap_state = IMAP_DISCONNECTED;
3149
3150                 DeleteIfDisconnected(cxn);
3151                 break;
3152                     
3153
3154             default:
3155                 d_printf(0,"%s:%d:IMAP I don't understand state %d [%s]\n",
3156                          hostPeerName (cxn->myHost), cxn->ident,
3157                          cxn->imap_state,str);
3158                 imap_Disconnect(cxn);
3159                 break;
3160             }
3161             
3162
3163     } else {
3164         d_printf(0,"%s:%d:IMAP tag (%s) doesn't match what we gave (%s). What's up with that??\n",
3165                  hostPeerName (cxn->myHost), cxn->ident, str, cxn->imap_currentTag);
3166         imap_Disconnect(cxn);
3167     }
3168     
3169 }
3170
3171 /************************** END IMAP reading functions ***************************/
3172
3173 /*************************** LMTP reading functions ****************************/
3174
3175 static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
3176 {
3177     connection_t *cxn = (connection_t *) d;
3178     char str[4096];
3179     Buffer *readBuffers;
3180     int result;
3181     int response_code;
3182     conn_ret ret;
3183 #ifdef HAVE_SASL
3184     int inlen;
3185     char *in;
3186     int outlen;
3187     const char *out;
3188     char *inbase64;
3189     int inbase64len;
3190     imt_stat status;
3191     int saslresult;
3192     sasl_interact_t *client_interact=NULL;
3193 #endif /* HAVE_SASL */
3194
3195     char *p = bufferBase(b[0]);
3196
3197     bufferAddNullByte (b[0]) ;
3198
3199     if (i != IoDone) {
3200         errno = endPointErrno(e);
3201         syslog(LOG_ERR, "%s:%d LMTP i/o failed: %m",
3202                hostPeerName (cxn->myHost), cxn->ident);
3203
3204         freeBufferArray (b);
3205         lmtp_Disconnect(cxn);
3206         return;
3207     }
3208
3209     if (strchr (p, '\n') == NULL)
3210     {
3211         /* partial read. expand buffer and retry */
3212
3213         d_printf(0,"%s:%d:LMTP Partial. retry\n",
3214                  hostPeerName (cxn->myHost),cxn->ident);
3215         expandBuffer (b[0], BUFFER_EXPAND_AMOUNT) ;
3216         readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
3217         
3218         if ( !prepareRead (e, readBuffers, lmtp_readCB, cxn, 1) )
3219         {
3220             lmtp_Disconnect(cxn);
3221         }
3222
3223         freeBufferArray (b);
3224         return;
3225     }
3226
3227     clearTimer (cxn->lmtp_readBlockedTimerId) ;
3228
3229     /* Add what we got to our internal read buffer */
3230     strcat(cxn->lmtp_respBuffer, p);
3231
3232     bufferSetDataSize( b[0], 0);
3233
3234     freeBufferArray (b);
3235
3236  reset:
3237     /* see if we have a full line */
3238     ret = GetLine( cxn->lmtp_respBuffer, str, sizeof(str));
3239
3240     /* get a line */
3241     if (ret!=RET_OK)
3242     {
3243         if (ret!=RET_NO_FULLLINE)
3244         {
3245             /* was a more serious error */
3246             d_printf(0,"%s:%d:LMTP Internal error getting line from server\n",
3247                      hostPeerName (cxn->myHost),cxn->ident);
3248             lmtp_Disconnect(cxn);
3249             return;         
3250         }
3251
3252         /* set up to receive some more */
3253         readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
3254         prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
3255         return;
3256     }
3257
3258     switch (cxn->lmtp_state)
3259         {
3260
3261         case LMTP_READING_INTRO:
3262             
3263             if (ask_code(str)!=220)
3264             {
3265                 d_printf(0,"%s:%d:LMTP Initial server msg does not start with 220 (began with %d)\n",
3266                          hostPeerName (cxn->myHost),cxn->ident,
3267                          ask_code(str));
3268                 lmtp_Disconnect(cxn);
3269                 return;
3270             }
3271
3272             /* the initial intro could have many lines via
3273                continuations. see if we need to read more */
3274             if (ask_keepgoing(str)==1)
3275             {
3276                 goto reset;
3277             }
3278
3279             result = lmtp_getcapabilities(cxn);
3280
3281             if (result != RET_OK)
3282             {
3283                 d_printf(0,"%s:%d:LMTP lmtp_getcapabilities() failure\n",
3284                          hostPeerName (cxn->myHost),cxn->ident);
3285                 lmtp_Disconnect(cxn);
3286                 return;
3287             }
3288
3289             break;
3290
3291         case LMTP_READING_LHLO:
3292             /* recieve the response(s) */
3293             response_code = ask_code(str);
3294             
3295             if (response_code != 250) /* was none */
3296             {
3297                 d_printf(0,"%s:%d:LMTP Response code unexpected (%d)\n",
3298                          hostPeerName (cxn->myHost),cxn->ident,
3299                          response_code);
3300                 lmtp_Disconnect(cxn);
3301                 return;
3302             }
3303
3304             /* look for one we know about; ignore all others */
3305             if (strncmp(str+4,"8BITMIME",strlen("8BITMIME"))==0)
3306             {
3307                 cxn->lmtp_capabilities->Eightbitmime = 1;
3308             } else if (strncmp(str+4, "ENHANCEDSTATUSCODES",
3309                                strlen("ENHANCEDSTATUSCODES"))==0) {
3310                 cxn->lmtp_capabilities->EnhancedStatusCodes = 1;
3311             } else if (strncmp(str+4, "AUTH",4)==0) {
3312                 cxn->lmtp_capabilities->saslmechs = xstrdup(str + 4 + 5);
3313             } else if (strncmp(str+4,"PIPELINING",strlen("PIPELINING"))==0) {
3314                 cxn->lmtp_capabilities->pipelining = 1;
3315             } else {
3316                 /* don't care; ignore */
3317             }
3318             
3319             /* see if this is the last line of the capability */
3320             if (ask_keepgoing(str)==1)
3321             {
3322                 goto reset;
3323             } else {
3324                 /* we require a few capabilities */
3325                 if (!cxn->lmtp_capabilities->pipelining) {
3326                     d_printf(0,"%s:%d:LMTP We require PIPELINING\n",
3327                              hostPeerName (cxn->myHost),cxn->ident);
3328                     
3329                     lmtp_Disconnect(cxn);
3330                     return;
3331                 }
3332 #ifdef HAVE_SASL
3333                 if (cxn->lmtp_capabilities->saslmechs) {
3334                     /* start the authentication */
3335                     result = lmtp_authenticate(cxn);
3336                     
3337                     if (result != RET_OK) {
3338                         d_printf(0,"%s:%d:LMTP lmtp_authenticate() error\n",
3339                              hostPeerName (cxn->myHost),cxn->ident);
3340                         lmtp_Disconnect(cxn);
3341                         return;
3342                     }
3343 #else
3344                 if (0) {
3345                     /* noop */
3346 #endif
3347                 } else {
3348                     /* either we can't authenticate or the remote server
3349                        doesn't support it */
3350                     cxn->lmtp_state = LMTP_AUTHED_IDLE;
3351                     d_printf(1,"%s:%d:LMTP Even though we can't authenticate"
3352                              " we're going to try to feed anyway\n",
3353                              hostPeerName (cxn->myHost),cxn->ident);
3354                     /* We just assume we don't need to authenticate 
3355                        (great assumption huh?) */
3356                     hostRemoteStreams (cxn->myHost, cxn, true) ;
3357
3358                     cxn->lmtp_timeCon = theTime () ;
3359                     cxn->timeCon = theTime () ;
3360                     
3361                     /* try to send a message if we have one */
3362                     lmtp_sendmessage(cxn,NULL);
3363                     return;
3364                 }
3365             }
3366             break;
3367
3368 #ifdef HAVE_SASL
3369         case LMTP_READING_STEPAUTH:
3370             inlen = 0;
3371             status = lmtp_getauthline(str, &in, &inlen);
3372
3373             switch (status)
3374                 {
3375
3376                 case STAT_CONT:
3377
3378                     saslresult=sasl_client_step(cxn->saslconn_lmtp,
3379                                                 in,
3380                                                 inlen,
3381                                                 &client_interact,
3382                                                 &out,
3383                                                 &outlen);
3384
3385                     free(in);
3386
3387                     /* check if sasl succeeded */
3388                     if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {
3389                         d_printf(0,"%s:%d:LMTP sasl_client_step(): %s\n",
3390                                  hostPeerName (cxn->myHost),cxn->ident,
3391                                  sasl_errstring(saslresult,NULL,NULL));
3392
3393                         lmtp_Disconnect(cxn);
3394                         return;
3395                     }
3396
3397                     /* convert to base64 */
3398                     inbase64 = xmalloc(outlen*2+10);
3399
3400                     saslresult = sasl_encode64(out, outlen,
3401                                                inbase64, outlen*2+10, 
3402                                                (unsigned *) &inbase64len);
3403                     
3404                     if (saslresult != SASL_OK)
3405                     {
3406                         d_printf(0,"%s:%d:LMTP sasl_encode64(): %s\n",
3407                                  hostPeerName (cxn->myHost),cxn->ident,
3408                                  sasl_errstring(saslresult,NULL,NULL));
3409
3410                         lmtp_Disconnect(cxn);
3411                         return;
3412                     }
3413
3414                     /* add an endline */
3415                     strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10);
3416
3417                     /* send to server */
3418                     result = WriteToWire_lmtpstr(cxn,inbase64, inbase64len+2);
3419
3420                     if (result != RET_OK)
3421                     {
3422                         d_printf(0,"%s:%d:LMTP WriteToWire() failure\n",
3423                                  hostPeerName (cxn->myHost),cxn->ident);
3424                         lmtp_Disconnect(cxn);
3425                         return;
3426                     }
3427
3428                     cxn->lmtp_state = LMTP_WRITING_STEPAUTH;
3429                     break;
3430                     
3431                 case STAT_OK:
3432                     cxn->lmtp_sleepTimeout = init_reconnect_period ;
3433       
3434                     d_printf(0,"%s:%d LMTP authentication succeeded\n",
3435                              hostPeerName (cxn->myHost), cxn->ident);
3436
3437                     cxn->lmtp_disconnects=0;
3438
3439                     hostRemoteStreams (cxn->myHost, cxn, true) ;
3440
3441                     cxn->lmtp_timeCon = theTime () ;
3442                     cxn->timeCon = theTime () ;
3443
3444                     cxn->lmtp_state = LMTP_AUTHED_IDLE;
3445
3446
3447                     /* try to send a message if we have one */
3448                     lmtp_sendmessage(cxn,NULL);
3449                     return;
3450
3451                     break;
3452
3453                 default:
3454                     d_printf(0,"%s:%d:LMTP failed authentication\n",
3455                              hostPeerName (cxn->myHost),cxn->ident);
3456                     lmtp_Disconnect(cxn);
3457                     return;
3458                 }
3459             break;
3460 #endif /* HAVE_SASL */
3461                     
3462         case LMTP_READING_RSET:
3463             if (ask_keepgoing(str)) {
3464                 goto reset;
3465             }
3466             if (ask_code(str) != 250) {
3467                 d_printf(0,"%s:%d:LMTP RSET failed with (%d)\n",
3468                          hostPeerName (cxn->myHost),cxn->ident,
3469                          ask_code(str));
3470                 lmtp_Disconnect(cxn);
3471                 return;
3472             }
3473
3474             /* we pipelined so next we recieve the mail from response */
3475             cxn->lmtp_state = LMTP_READING_MAILFROM;
3476             goto reset;
3477
3478         case LMTP_READING_MAILFROM:
3479             if (ask_keepgoing(str)) {
3480                 goto reset;
3481             }
3482             if (ask_code(str) != 250) {
3483                 d_printf(0,"%s:%d:LMTP MAILFROM failed with (%d)\n",
3484                          hostPeerName (cxn->myHost),cxn->ident,
3485                          ask_code(str));
3486                 lmtp_Disconnect(cxn);
3487                 return;
3488             }
3489
3490             /* we pipelined so next we recieve the rcpt's */
3491             cxn->lmtp_state = LMTP_READING_RCPTTO;
3492             goto reset;
3493             break;
3494
3495         case LMTP_READING_RCPTTO:
3496             if (ask_keepgoing(str)) {
3497                 goto reset;
3498             }
3499             if (ask_code(str)!=250) {
3500                 d_printf(1,"%s:%d:LMTP RCPT TO failed with (%d) %s\n",
3501                          hostPeerName (cxn->myHost),cxn->ident,
3502                          ask_code(str), str);
3503
3504                 /* if got a 5xx don't try to send anymore */
3505                 cxn->current_article->trys=100;
3506
3507                 cxn->current_rcpts_issued--;
3508             } else {
3509                 cxn->current_rcpts_okayed++;
3510             }
3511
3512             /* if issued equals number okayed then we're done */
3513             if ( cxn->current_rcpts_okayed == cxn->current_rcpts_issued) {
3514                 cxn->lmtp_state = LMTP_READING_DATA;
3515             } else {
3516                 /* stay in same state */
3517             }
3518             goto reset;
3519             break;
3520
3521         case LMTP_READING_DATA:
3522             if (ask_keepgoing(str)) {
3523                 goto reset;
3524             }
3525             if (cxn->current_rcpts_issued == 0) {
3526                 if (cxn->current_article->trys < 100) {
3527                     d_printf(1, "%s:%d:LMTP None of the rcpts "
3528                              "were accepted for this message. Re-queueing\n",
3529                              hostPeerName (cxn->myHost),cxn->ident);
3530                 }
3531
3532                 ReQueue(cxn, &(cxn->lmtp_todeliver_q), cxn->current_article);
3533
3534                 cxn->lmtp_state = LMTP_AUTHED_IDLE;
3535                 lmtp_sendmessage(cxn,NULL);
3536             } else {
3537                 if (WriteArticle(cxn, cxn->current_bufs) != RET_OK)
3538                 {
3539                     d_printf(0, "%s:%d:LMTP Error writing article\n",
3540                              hostPeerName (cxn->myHost),cxn->ident);
3541                     lmtp_Disconnect(cxn);
3542                     return;
3543                 }
3544                 
3545                 cxn->lmtp_state = LMTP_WRITING_CONTENTS;
3546             }
3547
3548             break;
3549
3550         case LMTP_READING_CONTENTS:
3551             if (ask_keepgoing(str)) {
3552                 goto reset;
3553             }
3554
3555             /* need 1 response from server for every rcpt */
3556             cxn->current_rcpts_issued--;
3557             
3558             if (ask_code(str) != 250) {
3559                 d_printf(1, "%s:%d:LMTP DATA failed with %d (%s)\n",
3560                          hostPeerName (cxn->myHost),cxn->ident,
3561                          ask_code(str), str);
3562                 cxn->current_rcpts_okayed--;
3563             }
3564
3565             if (cxn->current_rcpts_issued>0) {
3566                 goto reset;
3567             }
3568
3569             /*
3570              * current_rcpts_okayed is number that succeeded
3571              *
3572              */
3573             if (cxn->current_rcpts_okayed == 0) {
3574                 cxn->lmtp_state = LMTP_AUTHED_IDLE;
3575             } else {
3576                 cxn->lmtp_state = LMTP_AUTHED_IDLE;
3577                 cxn->lmtp_succeeded++;      
3578                 d_printf(1, "%s:%d:LMTP Woohoo! message accepted\n",
3579                          hostPeerName (cxn->myHost),cxn->ident);
3580             }
3581
3582             /* we can delete article now */
3583             QueueForgetAbout(cxn, cxn->current_article, MSG_SUCCESS);
3584
3585             /* try to send another if we have one and we're still idle
3586              * forgetting the msg might have made us unidle
3587              */
3588             if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
3589                 lmtp_sendmessage(cxn,NULL);
3590             }
3591             
3592             break;
3593
3594         case LMTP_READING_NOOP:
3595             if (ask_keepgoing(str)) {
3596                 goto reset;
3597             }
3598             cxn->lmtp_state = LMTP_AUTHED_IDLE;
3599             break;
3600
3601         case LMTP_READING_QUIT:
3602             d_printf(1,"%s:%d:LMTP read quit\n",
3603                      hostPeerName (cxn->myHost),cxn->ident);
3604
3605             cxn->lmtp_state = LMTP_DISCONNECTED;            
3606
3607             DeleteIfDisconnected(cxn);
3608             break;
3609
3610         default:
3611
3612             d_printf(0,"%s:%d:LMTP Bad state in lmtp_readCB %d\n",
3613                      hostPeerName (cxn->myHost),cxn->ident,
3614                      cxn->lmtp_state);
3615             lmtp_Disconnect(cxn);
3616             return;
3617         }
3618
3619
3620 }
3621
3622 /*
3623  * Add a rcpt to:<foo> to the string
3624  *
3625  */
3626
3627 static void addrcpt(char *newrcpt, int newrcptlen, char **out, int *outalloc)
3628 {
3629     int size = strlen(*out);
3630     int fsize = size;
3631     int newsize = size + 9+strlen(deliver_rcpt_to)+newrcptlen+3;
3632     char c;
3633
3634     /* see if we need to grow the string */
3635     if (newsize > *outalloc) {
3636         (*outalloc) = newsize;
3637         (*out) = xrealloc(*out, *outalloc);
3638     }
3639
3640     strlcpy((*out) + size,"RCPT TO:<", newsize - size);
3641     size += 9;
3642
3643     c = newrcpt[newrcptlen];
3644     newrcpt[newrcptlen] = '\0';
3645     size += snprintf((*out) + size, newsize - size, deliver_rcpt_to, newrcpt);
3646     newrcpt[newrcptlen] = c;
3647
3648     strlcpy((*out) + size, ">\r\n", newsize - size);
3649
3650     /* has embedded '\n' */
3651     d_printf(2, "Attempting to send to: %s", (*out) + fsize);
3652 }
3653
3654 /*
3655  * Takes the newsgroups header value and makes it into a list of RCPT TO:'s we can send over the wire
3656  *
3657  *  in     - newsgroups header start
3658  *  in_end - end of newsgroups header
3659  *  num    - number of rcpt's we created
3660  */
3661
3662 static char *ConvertRcptList(char *in, char *in_end, int *num)
3663 {
3664     int retalloc = 400;
3665     char *ret = xmalloc(retalloc);
3666     char *str = in;
3667     char *laststart = in;
3668
3669     (*num) = 0;
3670
3671     /* start it off empty */     
3672     strlcpy(ret, "", retalloc);
3673     
3674     while ( str !=  in_end)
3675     {
3676         if ((*str) == ',')
3677         {
3678             /* eliminate leading whitespace */
3679             while (((*laststart) ==' ') || ((*laststart)=='\t'))
3680             {
3681                 laststart++;
3682             }
3683
3684 #ifndef SMTPMODE
3685             addrcpt(laststart, str - laststart, &ret, &retalloc);
3686             (*num)++;
3687 #endif /* SMTPMODE */
3688             laststart = str+1;
3689         }
3690
3691         str++;
3692     }
3693
3694     if (laststart<str)
3695     {
3696         addrcpt(laststart, str - laststart, &ret, &retalloc);
3697         (*num)++;
3698     }
3699
3700     return ret;
3701 }
3702
3703 static void addto(char *newrcpt, int newrcptlen, const char *sep,
3704                   char **out, int *outalloc)
3705 {
3706     int size = strlen(*out);
3707     int newsize = size + strlen(sep)+1+strlen(deliver_to_header)+newrcptlen+1;
3708     char c;
3709
3710     /* see if we need to grow the string */
3711     if (newsize > *outalloc) {
3712         (*outalloc) = newsize;
3713         (*out) = xrealloc(*out, *outalloc);
3714     }
3715
3716     size += snprintf((*out) + size, newsize - size, "%s<", sep);
3717
3718     c = newrcpt[newrcptlen];
3719     newrcpt[newrcptlen] = '\0';
3720     size += snprintf((*out) + size, newsize - size, deliver_to_header,newrcpt);
3721     newrcpt[newrcptlen] = c;
3722
3723     strlcpy((*out) + size, ">", newsize - size);
3724 }
3725
3726 /*
3727  * Takes the newsgroups header value and makes it into a To: header
3728  *
3729  *  in     - newsgroups header start
3730  *  in_end - end of newsgroups header
3731  */
3732
3733 static char *BuildToHeader(char *in, char *in_end)
3734 {
3735     int retalloc = 400;
3736     char *ret = xmalloc(retalloc);
3737     char *str = in;
3738     char *laststart = in;
3739     const char *sep = "";
3740
3741     /* start it off with the header name */     
3742     strlcpy(ret,"To: ", retalloc);
3743     
3744     while ( str !=  in_end)
3745     {
3746         if ((*str) == ',')
3747         {
3748             /* eliminate leading whitespace */
3749             while (((*laststart) ==' ') || ((*laststart)=='\t'))
3750             {
3751                 laststart++;
3752             }
3753
3754             addto(laststart, str - laststart, sep, &ret, &retalloc);
3755             laststart = str+1;
3756
3757             /* separate multiple addresses with a comma */
3758             sep = ", ";
3759         }
3760
3761         str++;
3762     }
3763
3764     if (laststart<str)
3765     {
3766         addto(laststart, str - laststart, sep, &ret, &retalloc);
3767     }
3768
3769     /* terminate the header */
3770     strlcat(ret, "\n\r", retalloc);
3771     return ret;
3772 }
3773
3774 /*************************** END LMTP reading functions ****************************/
3775
3776
3777
3778 /*
3779  * Process the control message queue. If we run out ask the host for more.
3780  *
3781  *  cxn       - connection object
3782  */
3783
3784 static void imap_ProcessQueue(connection_t *cxn)
3785 {
3786     article_queue_t *item;
3787     int result;
3788
3789  retry:
3790
3791     /* pull an article off the queue */
3792     result = PopFromQueue(&(cxn->imap_controlMsg_q), &item);
3793
3794     if (result==RET_QUEUE_EMPTY)
3795     {
3796         if (cxn->issue_quit)
3797         {
3798             imap_sendQuit(cxn);
3799             return;
3800         }
3801
3802         cxn->imap_state = IMAP_IDLE_AUTHED;
3803
3804         /* now we wait for articles from our Host, or we have some
3805            articles already. On infrequently used connections, the
3806            network link is torn down and rebuilt as needed. So we may
3807            be rebuilding the connection here in which case we have an
3808            article to send. */
3809
3810         /* make sure imap has _lots_ of space too */
3811         if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) && 
3812             (QueueItems(&(cxn->imap_controlMsg_q)) == 0))
3813         {
3814             if (hostGimmeArticle (cxn->myHost,cxn)==true)
3815                 goto retry;
3816         }
3817
3818         return;
3819     }
3820
3821     cxn->current_control = item;
3822
3823     switch (item->type)
3824         {
3825         case CREATE_FOLDER:
3826             imap_CreateGroup(cxn, item->data.control->folder);
3827             break;
3828
3829         case CANCEL_MSG:
3830             imap_CancelMsg(cxn, item->data.control->folder);
3831             break;
3832
3833         case DELETE_FOLDER:
3834             imap_DeleteGroup(cxn, item->data.control->folder); 
3835             break;
3836         default:
3837             break;
3838         }
3839
3840     return;
3841 }
3842
3843
3844
3845 /*
3846  *
3847  * Pulls a message off the queue and trys to start sending it. If the
3848  * message is a control message put it in the control queue and grab
3849  * another message. If the message doesn't exist on disk or something
3850  * is wrong with it tell the host and try again. If we run out of
3851  * messages to get tell the host we want more
3852  *
3853  * cxn       - connection object
3854  * justadded - the article that was just added to the queue
3855  */
3856
3857 static void lmtp_sendmessage(connection_t *cxn, Article justadded)
3858 {
3859     bool res;
3860     conn_ret result;
3861     char *p;
3862     Buffer *bufs;
3863     char *control_header = NULL;
3864     char *control_header_end = NULL;
3865
3866     article_queue_t *item;
3867     char *rcpt_list, *rcpt_list_end;
3868
3869     /* retry point */
3870  retry:
3871
3872     /* pull an article off the queue */
3873     result = PopFromQueue(&(cxn->lmtp_todeliver_q), &item);
3874
3875     if (result==RET_QUEUE_EMPTY)
3876     {
3877         if (cxn->issue_quit) {
3878             lmtp_IssueQuit(cxn);
3879             return;
3880         }
3881         /* now we wait for articles from our Host, or we have some
3882            articles already. On infrequently used connections, the
3883            network link is torn down and rebuilt as needed. So we may
3884            be rebuilding the connection here in which case we have an
3885            article to send. */
3886
3887         /* make sure imap has space too */
3888         d_printf(1,"%s:%d stalled waiting for articles\n",
3889                  hostPeerName (cxn->myHost),cxn->ident);
3890         if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) && 
3891             (QueueItems(&(cxn->imap_controlMsg_q)) == 0)) {
3892             if (hostGimmeArticle (cxn->myHost,cxn)==true)
3893                 goto retry;
3894         }
3895
3896         return;
3897     }
3898
3899     /* make sure contents ok; this also should load it into memory */
3900     res = artContentsOk (item->data.article);
3901     if (res==false)
3902     {
3903         if (justadded == item->data.article) {
3904             ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
3905             return;
3906         } else {
3907             /* tell to reject taking this message */
3908             QueueForgetAbout(cxn,item, MSG_MISSING);
3909         }
3910
3911         goto retry;
3912     }
3913
3914     /* Check if it's a control message */
3915     bufs = artGetNntpBuffers (item->data.article);
3916     if (bufs == NULL)
3917     {
3918         /* tell to reject taking this message */
3919         QueueForgetAbout(cxn,item, MSG_MISSING);
3920         goto retry;
3921     }
3922
3923     result = FindHeader(bufs, "Control", &control_header, &control_header_end);
3924     if (result == RET_OK) {
3925         result = AddControlMsg(cxn, item->data.article, bufs, 
3926                                control_header,control_header_end, 1);
3927         if (result != RET_OK) {
3928             d_printf(1,"%s:%d Error adding to [imap] control queue\n",
3929                      hostPeerName (cxn->myHost),cxn->ident) ;
3930             ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
3931             return;
3932         }
3933
3934         switch(cxn->imap_state) {
3935         case IMAP_IDLE_AUTHED:
3936             /* we're idle. let's process the queue */
3937             imap_ProcessQueue(cxn);
3938             break;
3939         case IMAP_DISCONNECTED:
3940         case IMAP_WAITING:
3941             /* Let's connect. Once we're connected we can 
3942                worry about the message */
3943             if (cxn->imap_sleepTimerId == 0) {
3944                 if (imap_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,0);
3945             }
3946             break;
3947         default:
3948             /* we're doing something right now */
3949             break;
3950         }
3951         
3952         /* all we did was add a control message. 
3953            we still want to get an lmtp message */
3954         goto retry;
3955     }
3956
3957     if (cxn->current_bufs != NULL) {
3958         /*      freeBufferArray(cxn->current_bufs); */
3959         cxn->current_bufs = NULL;
3960     }
3961     cxn->current_bufs = bufs;
3962     cxn->current_article = item;
3963     
3964     /* we make use of pipelining here
3965        send:
3966          rset
3967          mail from
3968          rcpt to
3969          data
3970     */
3971
3972     /* find out who it's going to */
3973     result = FindHeader(cxn->current_bufs, "Newsgroups", 
3974                         &rcpt_list, &rcpt_list_end);
3975
3976     if ((result != RET_OK) || (rcpt_list == NULL)) {
3977         d_printf(1,"%s:%d Didn't find Newsgroups header\n",
3978                  hostPeerName (cxn->myHost),cxn->ident) ;
3979         QueueForgetAbout(cxn, cxn->current_article, MSG_FAIL_DELIVER);
3980         goto retry;     
3981     }
3982
3983     /* free's original rcpt_list */
3984     rcpt_list = ConvertRcptList(rcpt_list, rcpt_list_end, 
3985                                 &cxn->current_rcpts_issued);
3986     cxn->current_rcpts_okayed = 0;
3987     
3988     if(mailfrom_name == NULL)
3989         mailfrom_name = xstrdup("");
3990     p = concat("RSET\r\n"
3991                "MAIL FROM:<", mailfrom_name, ">\r\n",
3992                rcpt_list,
3993                "DATA\r\n", (char *) 0);
3994
3995     cxn->lmtp_state = LMTP_WRITING_UPTODATA;
3996     result = WriteToWire_lmtpstr(cxn, p, strlen(p));
3997
3998     if (result != RET_OK) {
3999         d_printf(0,"%s:%d failed trying to write\n",
4000                  hostPeerName (cxn->myHost),cxn->ident) ;
4001         lmtp_Disconnect(cxn);
4002         return;
4003     }
4004
4005     /* prepend To: header to article */
4006     if (deliver_to_header) {
4007         char *to_list, *to_list_end;
4008         int i, len;
4009
4010         result = FindHeader(cxn->current_bufs, "Followup-To", 
4011                             &to_list, &to_list_end);
4012
4013         if ((result != RET_OK) || (to_list == NULL)) {
4014             result = FindHeader(cxn->current_bufs, "Newsgroups", 
4015                                 &to_list, &to_list_end);
4016         }
4017
4018         /* free's original to_list */
4019         to_list = BuildToHeader(to_list, to_list_end);
4020
4021         len = bufferArrayLen(cxn->current_bufs);
4022         cxn->current_bufs = xrealloc(cxn->current_bufs,
4023                                      sizeof(Buffer) * (len+2));
4024         cxn->current_bufs[len+1] = NULL;
4025
4026         for (i = len; i > 0; i--) {
4027             cxn->current_bufs[i] = cxn->current_bufs[i-1];
4028         }
4029
4030         cxn->current_bufs[0] = newBufferByCharP(to_list, strlen(to_list+1),
4031                                                 strlen(to_list));
4032     }
4033
4034     hostArticleOffered (cxn->myHost, cxn);
4035 }
4036
4037 /*
4038  * Called by the EndPoint class when the timer goes off
4039  */
4040 static void dosomethingTimeoutCbk (TimeoutId id, void *data)
4041 {
4042   Connection cxn = (Connection) data ;
4043
4044   ASSERT (id == cxn->dosomethingTimerId) ;
4045
4046   show_stats(cxn);
4047
4048   /* we're disconnected but there are things to send */
4049   if ((cxn->lmtp_state == LMTP_DISCONNECTED) && 
4050       (cxn->lmtp_sleepTimerId == 0) &&
4051       QueueItems(&(cxn->lmtp_todeliver_q)) > 0)
4052   {
4053       if (lmtp_Connect(cxn) != RET_OK)
4054           prepareReopenCbk(cxn, 1);
4055   }
4056
4057   if ((cxn->imap_state == IMAP_DISCONNECTED) &&
4058       (cxn->imap_sleepTimerId == 0) &&
4059       (QueueItems(&(cxn->imap_controlMsg_q)) > 0))
4060   {
4061       if (imap_Connect(cxn) != RET_OK)
4062           prepareReopenCbk(cxn, 0);
4063   }
4064
4065
4066   /* if we're idle and there are items to send let's send them */
4067   if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) && 
4068       QueueItems(&(cxn->lmtp_todeliver_q)) > 0) {
4069       lmtp_sendmessage(cxn,NULL);
4070   } else if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
4071       lmtp_noop(cxn);
4072   }
4073
4074   if ((cxn->imap_state == IMAP_IDLE_AUTHED) &&
4075       (QueueItems(&(cxn->imap_controlMsg_q)) > 0)) {
4076       imap_ProcessQueue(cxn);
4077   } else if (cxn->imap_state == IMAP_IDLE_AUTHED) {
4078       imap_noop(cxn);
4079   }
4080
4081   /* set up the timer. */
4082   clearTimer (cxn->dosomethingTimerId) ;
4083
4084   cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk, 
4085                                           cxn->dosomethingTimeout, cxn);  
4086 }
4087
4088 /* Give all articles in the queue back to the host. We're probably
4089  * going to exit soon.
4090  * */
4091
4092 static void DeferAllArticles(connection_t *cxn, Q_t *q)
4093 {
4094     article_queue_t *cur;
4095     conn_ret ret;
4096     
4097     while (1)
4098     {
4099         ret = PopFromQueue(q, &cur);
4100         if (ret == RET_QUEUE_EMPTY) return;
4101
4102         if (ret == RET_OK)
4103         {
4104             QueueForgetAbout(cxn, cur, MSG_GIVE_BACK);
4105         } else {
4106             d_printf(0,"%s:%d Error emptying queue (deffering all articles)\n",
4107                      hostPeerName (cxn->myHost),cxn->ident);
4108             return;
4109         }
4110     }
4111 }
4112
4113 /*
4114  * Does the actual deletion of a connection and all its private data.
4115  */
4116 static void delConnection (Connection cxn)
4117 {
4118   bool shutDown;
4119   Connection c, q;
4120
4121   if (cxn == NULL)
4122     return ;
4123
4124   d_printf (1,"Deleting connection: %s:%d\n",
4125            hostPeerName (cxn->myHost),cxn->ident) ;
4126
4127   for (c = gCxnList, q = NULL ; c != NULL ; q = c, c = c->next)
4128     if (c == cxn)
4129       {
4130         if (gCxnList == c)
4131           gCxnList = gCxnList->next ;
4132         else
4133           q->next = c->next ;
4134         break ;
4135       }
4136   
4137   ASSERT (c != NULL) ;
4138  
4139   if (cxn->lmtp_endpoint != NULL)
4140     delEndPoint (cxn->lmtp_endpoint) ;
4141   if (cxn->imap_endpoint != NULL)
4142     delEndPoint (cxn->imap_endpoint) ;
4143
4144   delBuffer (cxn->imap_rBuffer) ;
4145   delBuffer (cxn->lmtp_rBuffer) ;
4146
4147   /* tell the Host we're outta here. */
4148   shutDown = hostCxnGone (cxn->myHost, cxn) ;
4149
4150   cxn->ident = 0 ;
4151   cxn->timeCon = 0 ;
4152
4153   free (cxn->ServerName) ;
4154
4155   clearTimer (cxn->imap_readBlockedTimerId) ;
4156   clearTimer (cxn->imap_writeBlockedTimerId) ;
4157   clearTimer (cxn->lmtp_readBlockedTimerId) ;
4158   clearTimer (cxn->lmtp_writeBlockedTimerId) ;
4159
4160   clearTimer (cxn->imap_sleepTimerId);
4161   cxn->imap_sleepTimerId = 0;  
4162   clearTimer (cxn->lmtp_sleepTimerId);  
4163   cxn->lmtp_sleepTimerId = 0;
4164
4165   clearTimer (cxn->dosomethingTimerId);
4166
4167   free (cxn->imap_respBuffer);
4168   free (cxn->lmtp_respBuffer);
4169
4170   free (cxn) ;
4171
4172   if (shutDown)
4173     {
4174       /* exit program if that was the last connexion for the last host */
4175       /* XXX what about if there are ever multiple listeners?
4176          XXX    this will be executed if all hosts on only one of the 
4177          XXX    listeners have gone */
4178       time_t now = theTime () ;
4179       char dateString [30] ;
4180
4181       strlcpy (dateString,ctime (&now),sizeof (dateString)) ;
4182       dateString [24] = '\0' ;
4183
4184       notice ("ME finishing at %s", dateString) ;
4185
4186       exit (0) ;
4187     }
4188 }
4189
4190
4191 /******************** PUBLIC FUNCTIONS ****************************/
4192
4193
4194
4195   /*
4196    * Create a new Connection.
4197    * 
4198    * HOST is the host object we're owned by.
4199    * IDENT is an identifier to be added to syslog entries so we can tell
4200    *    what's happening on different connections to the same peer.
4201    * IPNAME is the name (or ip address) of the remote)
4202    * MAXTOUT is the maximum amount of time to wait for a response before
4203    *    considering the remote host dead.
4204    * PORTNUM is the portnum to contact on the remote end.
4205    * RESPTIMEOUT is the amount of time to wait for a response from a remote
4206    *    before considering the connection dead.
4207    * CLOSEPERIOD is the number of seconds after connecting that the
4208    *     connections should be closed down and reinitialized (due to problems
4209    *     with old NNTP servers that hold history files open. Value of 0 means
4210    *     no close down.
4211    */
4212
4213 Connection newConnection (Host host,
4214                           unsigned int ident,
4215                           const char *ipname,
4216                           unsigned int artTout UNUSED,
4217                           unsigned int portNum UNUSED,
4218                           unsigned int respTimeout,
4219                           unsigned int closePeriod UNUSED,
4220                           double lowPassLow UNUSED,
4221                           double lowPassHigh UNUSED,
4222                           double lowPassFilter UNUSED)
4223 {
4224     Connection cxn;
4225     /* check arguments */
4226
4227     /* allocate connection structure */
4228     cxn = xcalloc (1, sizeof(connection_t)) ;
4229
4230     cxn->ident = ident ;
4231     cxn->ServerName = xstrdup (ipname) ;
4232     cxn->myHost = host ;
4233
4234     /* setup mailfrom user */
4235     if (gethostname(hostname, MAXHOSTNAMELEN)!=0)
4236     {
4237         d_printf(0,"%s gethostname failed\n",ipname);
4238         return NULL;
4239     }
4240
4241
4242     mailfrom_name = concat("news@", hostname, (char *) 0);
4243
4244     cxn->next = gCxnList ;
4245     gCxnList = cxn ;
4246     gCxnCount++ ;
4247
4248     /* init stuff */
4249     Initialize(cxn, respTimeout);
4250
4251     return cxn;
4252 }
4253
4254
4255 /* Causes the Connection to build the network connection. */
4256 bool cxnConnect (Connection cxn)
4257 {
4258     /* make the lmtp connection */
4259     if (lmtp_Connect(cxn) != RET_OK) return false;
4260
4261     if (imap_Connect(cxn) != RET_OK) return false;
4262
4263     return true;
4264 }
4265
4266
4267 static void QuitIfIdle(Connection cxn)
4268 {
4269     if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) && 
4270         (QueueItems(&(cxn->lmtp_todeliver_q))<=0)) {
4271         lmtp_IssueQuit(cxn);
4272     }
4273     if ((cxn->imap_state == IMAP_IDLE_AUTHED) && 
4274         (QueueItems(&(cxn->imap_controlMsg_q))<=0)) {
4275         imap_sendQuit(cxn);
4276     }
4277 }
4278
4279 static void DeleteIfDisconnected(Connection cxn)
4280 {
4281     /* we want to shut everything down. if both connections disconnected now we can */
4282     if ((cxn->issue_quit >= 1) &&
4283         (cxn->lmtp_state == LMTP_DISCONNECTED) &&
4284         (cxn->imap_state == IMAP_DISCONNECTED))
4285     {
4286
4287         switch (cxn->issue_quit)
4288         {
4289         case 1:
4290             if (cxn->lmtp_state == LMTP_DISCONNECTED)
4291             {
4292                 cxn->lmtp_state = LMTP_WAITING;
4293                 cxn->imap_state = IMAP_WAITING;
4294                 cxn->issue_quit = 0;
4295                 hostCxnWaiting (cxn->myHost,cxn) ;  /* tell our Host we're waiting */
4296             }
4297             break;
4298         case 2:
4299             if (cxn->lmtp_state == LMTP_DISCONNECTED)
4300             {
4301                 cxn->issue_quit = 0;
4302                 
4303                 if (imap_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,0);
4304                 if (lmtp_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,1);
4305             }
4306             break;
4307         case 3:
4308             if (cxn->lmtp_state == LMTP_DISCONNECTED)
4309             {
4310                 hostCxnDead (cxn->myHost,cxn) ;
4311                 delConnection(cxn);
4312             }
4313             break;
4314             
4315         }       
4316     }
4317 }
4318
4319   /* puts the connection into the wait state (i.e. waits for an article
4320      before initiating a connect). Can only be called right after
4321      newConnection returns, or while the Connection is in the (internal)
4322      Sleeping state. */
4323 void cxnWait (Connection cxn)
4324 {
4325     cxn->issue_quit = 1;
4326
4327     QuitIfIdle(cxn);
4328 }
4329
4330   /* The Connection will disconnect as if cxnDisconnect were called and then
4331      it automatically reconnects to the remote. */
4332 void cxnFlush (Connection cxn)
4333 {
4334     cxn->issue_quit = 2;
4335
4336     QuitIfIdle(cxn);
4337 }
4338
4339
4340
4341   /* The Connection sends remaining articles, then issues a QUIT and then
4342      deletes itself */
4343 void cxnClose (Connection cxn)
4344 {
4345     d_printf(0,"%s:%d Closing cxn\n",hostPeerName (cxn->myHost), cxn->ident);
4346     cxn->issue_quit = 3;
4347
4348     QuitIfIdle(cxn);
4349
4350     DeleteIfDisconnected(cxn);
4351 }
4352
4353   /* The Connection drops all queueed articles, then issues a QUIT and then
4354      deletes itself */
4355 void cxnTerminate (Connection cxn)
4356 {
4357     d_printf(0,"%s:%d Terminate\n",hostPeerName (cxn->myHost), cxn->ident);
4358
4359     cxn->issue_quit = 3;    
4360
4361     /* give any articles back to host in both queues */
4362     DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
4363     DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));
4364
4365     QuitIfIdle(cxn);
4366 }
4367
4368   /* Blow away the connection gracelessly and immedately clean up */
4369 void cxnNuke (Connection cxn)
4370 {
4371     d_printf(0,"%s:%d Nuking connection\n",cxn->ServerName, cxn->ident);
4372
4373     cxn->issue_quit = 4;
4374
4375     /* give any articles back to host in both queues */
4376     DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
4377     DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));
4378
4379     imap_Disconnect(cxn);
4380     lmtp_Disconnect(cxn);
4381
4382     hostCxnDead (cxn->myHost,cxn);    
4383     delConnection(cxn);
4384 }
4385
4386 /*
4387  * must
4388  *   true  - must queue article. Don't try sending
4389  *   false - queue of article may fail. Try sending
4390  *
4391  * Always adds to lmtp queue even if control message
4392  *
4393  */
4394
4395 static bool ProcessArticle(Connection cxn, Article art, bool must)
4396 {
4397     conn_ret result;
4398
4399     /* Don't accept any articles when we're closing down the connection */
4400     if (cxn->issue_quit > 1) {
4401         return false;
4402     }
4403
4404     /* if it's a regular message let's add it to the queue */
4405     result = AddToQueue(&(cxn->lmtp_todeliver_q), art, DELIVER,1,must);
4406
4407     if (result == RET_EXCEEDS_SIZE) {
4408         return false;
4409     }
4410
4411     if (result != RET_OK)
4412     {
4413         d_printf(0,"%s:%d Error adding to delivery queue\n",
4414                  hostPeerName (cxn->myHost), cxn->ident);
4415         return must;
4416     }
4417
4418     if (must == true) return true;
4419
4420     switch (cxn->lmtp_state)
4421         {
4422         case LMTP_WAITING:
4423         case LMTP_DISCONNECTED:     
4424             if (cxn->lmtp_sleepTimerId == 0)
4425                 if (lmtp_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,1);
4426             break;
4427             
4428         case LMTP_AUTHED_IDLE:
4429             lmtp_sendmessage(cxn,art);
4430             break;
4431         default:
4432             /* currently doing something */
4433             break;
4434         }
4435     
4436     return true;
4437 }
4438
4439   /* Tells the Connection to take the article and handle its
4440      transmission. If it can't (due to queue size or whatever), then the
4441      function returns false. The connection assumes ownership of the
4442      article if it accepts it (returns true). */
4443 bool cxnTakeArticle (Connection cxn, Article art)
4444 {
4445     /* if we're closing down always refuse */
4446     if (cxn->issue_quit == 1) return false;
4447
4448     return ProcessArticle (cxn,art,false);
4449 }
4450
4451   /* Tell the Connection to take the article (if it can) for later
4452      processing. Assumes ownership of it if it takes it. */
4453 bool cxnQueueArticle (Connection cxn, Article art)
4454 {
4455     return ProcessArticle (cxn,art,true);
4456 }
4457
4458 /* generate a syslog message for the connections activity. Called by Host. */
4459 void cxnLogStats (Connection cxn, bool final)
4460 {
4461   const char *peerName ;
4462   time_t now = theTime() ;
4463   int total, good, bad;
4464
4465   ASSERT (cxn != NULL) ;
4466
4467   peerName = hostPeerName (cxn->myHost) ;
4468
4469   total = cxn->lmtp_succeeded + cxn->lmtp_failed;
4470   total += cxn->cancel_succeeded + cxn->cancel_failed;
4471   total += cxn->create_succeeded + cxn->create_failed;
4472   total += cxn->remove_succeeded + cxn->remove_failed;
4473
4474   good = cxn->lmtp_succeeded;
4475   good += cxn->cancel_succeeded;
4476   good += cxn->create_succeeded;
4477   good += cxn->remove_succeeded;
4478
4479   bad = cxn->lmtp_failed;
4480   bad += cxn->cancel_failed;
4481   bad += cxn->create_failed;
4482   bad += cxn->remove_failed;
4483   notice ("%s:%d %s seconds %ld accepted %d refused %d rejected %d",
4484           peerName, cxn->ident, (final ? "final" : "checkpoint"),
4485           (long) (now - cxn->timeCon), total, 0, bad);
4486   show_stats(cxn);
4487
4488   if (final) {
4489       cxn->lmtp_succeeded   = 0;
4490       cxn->lmtp_failed     = 0;
4491       cxn->cancel_succeeded = 0;
4492       cxn->cancel_failed   = 0;
4493       cxn->create_succeeded = 0;
4494       cxn->create_failed   = 0;
4495       cxn->remove_succeeded = 0;
4496       cxn->remove_failed   = 0;
4497
4498       if (cxn->timeCon > 0)
4499         cxn->timeCon = theTime() ;
4500     }
4501
4502 }
4503
4504   /* return the number of articles the connection can be given. This lets
4505      the host shovel in as many as possible. May be zero. */
4506 size_t cxnQueueSpace (Connection cxn)
4507 {
4508     int lmtpsize;
4509     int imapsize;
4510
4511     lmtpsize = QueueSpace(&(cxn->lmtp_todeliver_q));
4512     imapsize = QueueSpace(&(cxn->imap_controlMsg_q));
4513
4514     if (lmtpsize >=1) lmtpsize--;
4515     if (imapsize >=1) imapsize--;
4516
4517     d_printf(1,"%s:%d Q Space lmtp size = %d state = %d\n",
4518              hostPeerName (cxn->myHost), cxn->ident,
4519              lmtpsize,cxn->lmtp_state);
4520     d_printf(1,"%s:%d Q Space imap size = %d state = %d\n",
4521              hostPeerName (cxn->myHost), cxn->ident,
4522              imapsize,cxn->imap_state); 
4523
4524     /* return the smaller of our 2 queues */
4525     if (lmtpsize < imapsize)
4526         return lmtpsize;
4527     else
4528         return imapsize;
4529 }
4530
4531   /* adjust the mode no-CHECK filter values */
4532 void cxnSetCheckThresholds (Connection cxn,
4533                             double lowFilter UNUSED,
4534                             double highFilter UNUSED,
4535                             double lowPassFilter UNUSED)
4536 {
4537     d_printf(1,"%s:%d Threshold change. This means nothing to me\n",
4538              hostPeerName (cxn->myHost), cxn->ident);
4539 }
4540
4541 /* print some debugging info. */
4542 void gPrintCxnInfo (FILE *fp, unsigned int indentAmt)
4543 {
4544   char indent [INDENT_BUFFER_SIZE] ;
4545   unsigned int i ;
4546   Connection cxn ;
4547
4548   for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
4549     indent [i] = ' ' ;
4550   indent [i] = '\0' ;
4551
4552   fprintf (fp,"%sGlobal Connection list : (count %d) {\n",
4553            indent,gCxnCount) ;
4554   for (cxn = gCxnList ; cxn != NULL ; cxn = cxn->next)
4555     printCxnInfo (cxn,fp,indentAmt + INDENT_INCR) ;
4556   fprintf (fp,"%s}\n",indent) ;
4557 }
4558
4559 void printCxnInfo (Connection cxn, FILE *fp, unsigned int indentAmt)
4560 {
4561   char indent [INDENT_BUFFER_SIZE] ;
4562   unsigned int i ;
4563   article_queue_t *artH ;
4564
4565   for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
4566     indent [i] = ' ' ;
4567   indent [i] = '\0' ;
4568
4569   fprintf (fp,"%sConnection : %p {\n",indent, (void *) cxn) ;
4570   fprintf (fp,"%s    host : %p\n",indent, (void *) cxn->myHost) ;
4571   fprintf (fp,"%s    endpoint (imap): %p\n",indent, (void *) cxn->imap_endpoint) ;
4572   fprintf (fp,"%s    endpoint (lmtp): %p\n",indent, (void *) cxn->lmtp_endpoint) ;
4573   fprintf (fp,"%s    state (imap) : %s\n",indent, imap_stateToString (cxn->imap_state)) ;
4574   fprintf (fp,"%s    state (lmtp) : %s\n",indent, lmtp_stateToString (cxn->lmtp_state)) ;
4575   fprintf (fp,"%s    ident : %d\n",indent,cxn->ident) ;
4576   fprintf (fp,"%s    ip-name (imap): %s\n", indent, cxn->ServerName) ;
4577   fprintf (fp,"%s    ip-name (lmtp): %s\n", indent, cxn->ServerName) ;
4578   fprintf (fp,"%s    port-number (imap) : %d\n",indent,cxn->imap_port) ;
4579   fprintf (fp,"%s    port-number (lmtp) : %d\n",indent,cxn->lmtp_port) ;
4580
4581   fprintf (fp,"%s    Issuing Quit : %d\n",indent, cxn->issue_quit) ;
4582
4583   fprintf (fp,"%s    time-connected (imap) : %ld\n",indent,(long) cxn->imap_timeCon) ;
4584   fprintf (fp,"%s    time-connected (lmtp) : %ld\n",indent,(long) cxn->lmtp_timeCon) ;
4585   fprintf (fp,"%s    articles from INN : %d\n",indent,
4586            cxn->lmtp_succeeded+
4587            cxn->lmtp_failed+
4588            cxn->cancel_succeeded+
4589            cxn->cancel_failed+
4590            cxn->create_succeeded+
4591            cxn->create_failed+
4592            cxn->remove_succeeded+
4593            cxn->remove_failed+
4594            QueueSpace(&(cxn->lmtp_todeliver_q))+
4595            QueueSpace(&(cxn->imap_controlMsg_q))
4596            );
4597   fprintf(fp,"%s    LMTP STATS: yes: %d no: %d\n",indent, 
4598           cxn->lmtp_succeeded, cxn->lmtp_failed);
4599   fprintf(fp,"%s    control:    yes: %d no: %d\n",indent, 
4600           cxn->cancel_succeeded, cxn->cancel_failed);
4601   fprintf(fp,"%s    create:     yes: %d no: %d\n",indent, 
4602           cxn->create_succeeded, cxn->create_failed);
4603   fprintf(fp,"%s    remove:     yes: %d no: %d\n",indent, 
4604           cxn->remove_succeeded, cxn->remove_failed);
4605
4606   fprintf (fp,"%s    response-timeout : %d\n",indent,cxn->imap_readTimeout) ;
4607   fprintf (fp,"%s    response-callback : %d\n",indent,cxn->imap_readBlockedTimerId) ;
4608
4609   fprintf (fp,"%s    write-timeout : %d\n",indent,cxn->imap_writeTimeout) ;
4610   fprintf (fp,"%s    write-callback : %d\n",indent,cxn->imap_writeBlockedTimerId) ;
4611
4612   fprintf (fp,"%s    reopen wait : %d\n",indent,cxn->imap_sleepTimeout) ;
4613   fprintf (fp,"%s    reopen id : %d\n",indent,cxn->imap_sleepTimerId) ;
4614
4615   fprintf (fp,"%s    IMAP queue {\n",indent) ;
4616   for (artH = cxn->imap_controlMsg_q.head; artH != NULL ; artH = artH->next)
4617     printArticleInfo (artH->data.article,fp,indentAmt + INDENT_INCR) ;
4618   fprintf (fp,"%s    }\n",indent) ;
4619
4620   fprintf (fp,"%s    LMTP queue {\n",indent) ;
4621   for (artH = cxn->lmtp_todeliver_q.head ; artH != NULL ; artH = artH->next)
4622     printArticleInfo (artH->data.control->article,fp,indentAmt + INDENT_INCR) ;
4623   fprintf (fp,"%s    }\n",indent) ;
4624
4625   fprintf (fp,"%s}\n",indent) ;
4626 }
4627
4628 /* config file load callback */
4629 int cxnConfigLoadCbk (void *data UNUSED)
4630 {
4631   long iv ;
4632   int rval = 1 ;
4633   FILE *fp = (FILE *) data ;
4634
4635   if (getInteger (topScope,"max-reconnect-time",&iv,NO_INHERIT))
4636     {
4637       if (iv < 1)
4638         {
4639           rval = 0 ;
4640           logOrPrint (LOG_ERR,fp,
4641                       "ME config: value of %s (%ld) in %s cannot be less"
4642                       " than 1. Using %ld", "max-reconnect-time",
4643                       iv,"global scope",(long) MAX_RECON_PER);
4644           iv = MAX_RECON_PER ;
4645         }
4646     }
4647   else
4648     iv = MAX_RECON_PER ;
4649   max_reconnect_period = (unsigned int) iv ;
4650
4651   if (getInteger (topScope,"initial-reconnect-time",&iv,NO_INHERIT))
4652     {
4653       if (iv < 1)
4654         {
4655           rval = 0 ;
4656           logOrPrint (LOG_ERR,fp,
4657                       "ME config: value of %s (%ld) in %s cannot be less"
4658                       " than 1. Using %ld", "initial-reconnect-time",
4659                       iv,"global scope",(long)INIT_RECON_PER);
4660           iv = INIT_RECON_PER ;
4661         }
4662     }
4663   else
4664     iv = INIT_RECON_PER ;
4665   init_reconnect_period = (unsigned int) iv ;
4666
4667   return rval ;
4668 }
4669
4670 /* check connection state is in cxnWaitingS, cxnConnectingS or cxnIdleS */
4671 bool cxnCheckstate (Connection cxn)
4672 {
4673     d_printf(5, "%s:%d Being asked to check state\n",
4674              hostPeerName (cxn->myHost), cxn->ident);
4675
4676     /* return false if either connection is doing something */
4677     if (cxn->imap_state > IMAP_IDLE_AUTHED) return false;
4678     if (cxn->lmtp_state > LMTP_AUTHED_IDLE) return false;
4679
4680     return true;
4681 }