/*  $Id: imap_connection.c 7103 2004-12-23 22:36:27Z rra $
**
**  Feed articles to an IMAP server via LMTP and IMAP.
**
**  Written by Tim Martin.
**
**  Instead of feeding articles via nntp to another host this feeds the
**  messages via lmtp to a host and the control messages (cancel's etc..) it
**  performs via IMAP.  This means it has 2 active connections at any given
**  time and 2 queues.
**
**  When an article comes in it is immediatly placed in the lmtp queue. When
**  an article is picked off the lmtp queue for processing first check if it's
**  a control message.  If so, place it in the IMAP queue.  If not, attempt to
**  deliver via LMTP.
**
**  This attempts to follow the exact same api as connection.c.
**  
**  TODO:
**  
**  feed to smtp 
**  security layers?  <--punt on for now
**  authname/password per connection object
**  untagged IMAP messages
*/

#include "config.h"
#include "clibrary.h"
#include "portable/socket.h"
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <netdb.h>
#include <time.h>
#include <syslog.h>

#include "inn/messages.h"
#include "libinn.h"

#include "buffer.h"
#include "connection.h"
#include "endpoint.h"
#include "host.h"
#include "innfeed.h"
#include "article.h"
#include "configfile.h"

#ifdef HAVE_SASL
# include <sasl/sasl.h>
#endif

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 1024
#endif

#define IMAP_PORT 143

#ifdef SMTPMODE
# define LMTP_PORT 25
#else
# define LMTP_PORT 2003
#endif

#define IMAP_TAGLENGTH 6

#define QUEUE_MAX_SIZE 250

#define DOSOMETHING_TIMEOUT 60



/* external */
extern char *deliver_username;
extern char *deliver_authname;
extern char *deliver_password;
extern char *deliver_realm;
extern char *deliver_rcpt_to;
extern char *deliver_to_header;


char hostname[MAXHOSTNAMELEN];
char *mailfrom_name = NULL; /* default to no return path */

#ifdef HAVE_SASL
static int initialized_sasl = 0; /* weather sasl_client_init() has been called */
#endif

/* states the imap connection may be in */
typedef enum {

    IMAP_DISCONNECTED = 1,
    IMAP_WAITING,
    
    IMAP_CONNECTED_NOTAUTH,

    IMAP_READING_INTRO,

    IMAP_WRITING_CAPABILITY,
    IMAP_READING_CAPABILITY,

    IMAP_WRITING_STARTAUTH,
    IMAP_READING_STEPAUTH,
    IMAP_WRITING_STEPAUTH,

    IMAP_IDLE_AUTHED,
    IMAP_WRITING_NOOP,
    IMAP_READING_NOOP,

    IMAP_WRITING_CREATE,
    IMAP_READING_CREATE,

    IMAP_WRITING_DELETE,
    IMAP_READING_DELETE,

    IMAP_WRITING_SELECT,
    IMAP_READING_SELECT,

    IMAP_WRITING_SEARCH,
    IMAP_READING_SEARCH,

    IMAP_WRITING_STORE,
    IMAP_READING_STORE,
    
    IMAP_WRITING_CLOSE,
    IMAP_READING_CLOSE,

    IMAP_WRITING_QUIT,
    IMAP_READING_QUIT
   
} imap_state_t;

typedef enum {
    LMTP_DISCONNECTED = 1,
    LMTP_WAITING,

    LMTP_CONNECTED_NOTAUTH,

    LMTP_READING_INTRO,

    LMTP_WRITING_LHLO,
    LMTP_READING_LHLO,

    LMTP_WRITING_STARTAUTH,
    LMTP_READING_STEPAUTH,
    LMTP_WRITING_STEPAUTH,

    LMTP_AUTHED_IDLE,
    LMTP_WRITING_NOOP,
    LMTP_READING_NOOP,
   
    LMTP_READING_RSET,
    LMTP_READING_MAILFROM,
    LMTP_READING_RCPTTO,
    LMTP_READING_DATA,
    LMTP_READING_CONTENTS,

    LMTP_WRITING_UPTODATA,
    LMTP_WRITING_CONTENTS,

    LMTP_WRITING_QUIT,
    LMTP_READING_QUIT

} lmtp_state_t;

typedef struct imap_capabilities_s {

    int imap4;         /* does server support imap4bis? */
    int logindisabled; /* does the server allow the login command? */

    char *saslmechs;   /* supported SASL mechanisms */

} imap_capabilities_t;

typedef struct lmtp_capabilities_s {

    int Eightbitmime;
    int EnhancedStatusCodes;
    int pipelining;

    char *saslmechs;

} lmtp_capabilities_t;

typedef enum {
    STAT_CONT = 0,
    STAT_NO = 1,
    STAT_OK = 2,
    STAT_FAIL = 3
} imt_stat;

/* Message types */
typedef enum {
    DELIVER,
    CREATE_FOLDER,
    CANCEL_MSG,
    DELETE_FOLDER
} control_type_t;

typedef struct control_item_s {

    Article article;
    char *folder;
    char *msgid;                 /* only for cancel's */
    unsigned long  uid;          /* only for cancel's */

} control_item_t;

typedef struct article_queue_s {

    control_type_t type;

    time_t arrived;
    time_t nextsend; /* time we should next try to send article */

    int trys;

    int counts_toward_size;

    union {
	Article article;
	control_item_t *control;
	void *generic;
    } data;

    struct article_queue_s *next;

} article_queue_t;

typedef struct Q_s {

    article_queue_t *head;

    article_queue_t *tail;

    int size;

} Q_t;

typedef struct connection_s {

    /* common stuff */
    char *ServerName;

    char *lmtp_respBuffer;         /* buffer all responses are read into */
    Buffer lmtp_rBuffer;           /* buffer all responses are read into */

    Host myHost ;                   /* the host who owns the connection */
    
    time_t timeCon ;                /* the time the connect happened 
				       (last auth succeeded) */

    int issue_quit;                 /* Three states:
				     *   0 - don't do anything
				     *   1 - after issue quit enter wait state
				     *   2 - after issue quit reconnect
				     *   3 - after issue quit delete connection
				     *   4 - nuke cxn
				     */

    /* Statistics */    
    int lmtp_succeeded;
    int lmtp_failed;

    int cancel_succeeded;
    int cancel_failed;
    
    int create_succeeded;
    int create_failed;
    
    int remove_succeeded;
    int remove_failed;

    
    /* LMTP stuff */
    int lmtp_port;
    lmtp_state_t lmtp_state;
#ifdef HAVE_SASL
    sasl_conn_t *saslconn_lmtp;
#endif /* HAVE_SASL */
    int sockfd_lmtp;

    time_t lmtp_timeCon ;

    EndPoint lmtp_endpoint;
    unsigned int ident ;               /* an identifier for syslogging. */

    lmtp_capabilities_t *lmtp_capabilities;

    int lmtp_disconnects;
    char *lmtp_tofree_str;

    article_queue_t *current_article;
    Buffer *current_bufs;
    int     current_rcpts_issued;
    int     current_rcpts_okayed;

    /* Timer for the max amount of time to wait for a response from the
       remote */
    unsigned int lmtp_readTimeout ;
    TimeoutId lmtp_readBlockedTimerId ;

    /* Timer for the max amount of time to wait for a any amount of data
       to be written to the remote */
    unsigned int lmtp_writeTimeout ;
    TimeoutId lmtp_writeBlockedTimerId ;

    /* Timer for the number of seconds to sleep before attempting a
       reconnect. */
    unsigned int lmtp_sleepTimeout ;
    TimeoutId lmtp_sleepTimerId ;

    /* Timer for max amount between queueing some articles and trying to send them */
    unsigned int dosomethingTimeout ;
    TimeoutId dosomethingTimerId ;

    Q_t lmtp_todeliver_q;



    /* IMAP stuff */
    int imap_port;
#ifdef HAVE_SASL
    sasl_conn_t *imap_saslconn;
#endif /* HAVE_SASL */

    char *imap_respBuffer;
    Buffer imap_rBuffer;
    EndPoint imap_endpoint;

    imap_capabilities_t *imap_capabilities;
    
    int imap_sockfd;

    time_t imap_timeCon ;

    imap_state_t imap_state;
    int imap_disconnects;
    char *imap_tofree_str;

    char imap_currentTag[IMAP_TAGLENGTH];
    int  imap_tag_num;

    /* Timer for the max amount of time to wait for a response from the
       remote */
    unsigned int imap_readTimeout ;
    TimeoutId imap_readBlockedTimerId ;

    /* Timer for the max amount of time to wait for a any amount of data
       to be written to the remote */
    unsigned int imap_writeTimeout ;
    TimeoutId imap_writeBlockedTimerId ;

    /* Timer for the number of seconds to sleep before attempting a
       reconnect. */
    unsigned int imap_sleepTimeout ;
    TimeoutId imap_sleepTimerId ;

    Q_t imap_controlMsg_q;

    article_queue_t *current_control;

    struct connection_s *next;

} connection_t;

static Connection gCxnList = NULL ;
static unsigned int gCxnCount= 0 ;
static unsigned int max_reconnect_period = MAX_RECON_PER ;
static unsigned int init_reconnect_period = INIT_RECON_PER;

typedef enum {
    RET_OK = 0,
    RET_FAIL = 1,
    RET_QUEUE_EMPTY,
    RET_EXCEEDS_SIZE,
    RET_NO_FULLLINE,
    RET_NO,
    RET_ARTICLE_BAD
} conn_ret;


/********** Private Function Declarations *************/

static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
static void imap_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);
static void lmtp_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);

static conn_ret lmtp_Connect(connection_t *cxn);
static conn_ret imap_Connect(connection_t *cxn);

static void prepareReopenCbk (Connection cxn, int type);

static void lmtp_readTimeoutCbk (TimeoutId id, void *data);
static void imap_readTimeoutCbk (TimeoutId id, void *data);

static void dosomethingTimeoutCbk (TimeoutId id, void *data);

static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen);
static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen);

static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback, 
			    EndPoint endp, Buffer *array);
static void lmtp_sendmessage(connection_t *cxn, Article justadded);
static void imap_ProcessQueue(connection_t *cxn);

static conn_ret FindHeader(Buffer *bufs, const char *header, char **start, char **end);
static conn_ret PopFromQueue(Q_t *q, article_queue_t **item);

enum failure_type {
    MSG_SUCCESS = 0,
    MSG_FAIL_DELIVER = 1,
    MSG_GIVE_BACK = 2,
    MSG_MISSING = 3
};

static void QueueForgetAbout(connection_t *cxn, article_queue_t *item, 
			     enum failure_type failed);

static void delConnection (Connection cxn);
static void DeleteIfDisconnected(Connection cxn);
static void DeferAllArticles(connection_t *cxn, Q_t *q);

static void lmtp_Disconnect(connection_t *cxn);
static void imap_Disconnect(connection_t *cxn);
static conn_ret imap_listenintro(connection_t *cxn);

static void imap_writeTimeoutCbk (TimeoutId id, void *data);
static void lmtp_writeTimeoutCbk (TimeoutId id, void *data);

/******************** PRIVATE FUNCTIONS ***************************/

static const char *imap_stateToString(int state)
{
    switch (state)
	{
	case IMAP_DISCONNECTED: return "disconnected";   
	case IMAP_WAITING: return "waiting";
	case IMAP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
	case IMAP_READING_INTRO: return "reading intro";
	case IMAP_WRITING_CAPABILITY: return "writing CAPABILITY";
	case IMAP_READING_CAPABILITY: return "reading CAPABILITY";
	case IMAP_WRITING_STARTAUTH: return "writing AUTHENTICATE";
	case IMAP_READING_STEPAUTH: return "reading stepauth";
	case IMAP_WRITING_STEPAUTH: return "writing stepauth";
	case IMAP_IDLE_AUTHED: return "idle (authenticated)";
	case IMAP_WRITING_NOOP: return "writing NOOP";
	case IMAP_READING_NOOP: return "reading NOOP response";
	case IMAP_WRITING_CREATE: return "writing CREATE";
	case IMAP_READING_CREATE: return "reading CREATE response";
	case IMAP_WRITING_DELETE: return "writing DELETE command";
	case IMAP_READING_DELETE: return "reading DELETE response";	       
	case IMAP_WRITING_SELECT: return "writing SELECT";
	case IMAP_READING_SELECT: return "reading SELECT response";
	case IMAP_WRITING_SEARCH: return "writing SEARCH";
	case IMAP_READING_SEARCH: return "reading SEARCH response";
	case IMAP_WRITING_STORE: return "writing STORE";
	case IMAP_READING_STORE: return "reading STORE response";	    
	case IMAP_WRITING_CLOSE: return "writing CLOSE";
	case IMAP_READING_CLOSE: return "reading CLOSE response";
	case IMAP_WRITING_QUIT: return "writing LOGOUT";
	case IMAP_READING_QUIT: return "reading LOGOUT response";
	default: return "Unknown state";
	}
}

static const char *lmtp_stateToString(int state)
{
    switch(state)
	{
	case LMTP_DISCONNECTED: return "disconnected";
	case LMTP_WAITING: return "waiting";
	case LMTP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
	case LMTP_READING_INTRO: return "reading intro";
	case LMTP_WRITING_LHLO: return "writing LHLO";
	case LMTP_READING_LHLO: return "reading LHLO response";
	case LMTP_WRITING_STARTAUTH: return "writing AUTH";
	case LMTP_READING_STEPAUTH: return "reading stepauth";
	case LMTP_WRITING_STEPAUTH: return "writing stepauth";
	case LMTP_AUTHED_IDLE: return "idle (authenticated)";
	case LMTP_WRITING_NOOP: return "writing NOOP";
	case LMTP_READING_NOOP: return "reading NOOP response";
	case LMTP_READING_RSET: return "reading RSET response";
	case LMTP_READING_MAILFROM: return "reading MAIL FROM response";
	case LMTP_READING_RCPTTO: return "reading RCPT TO response";
	case LMTP_READING_DATA: return "reading DATA response";
	case LMTP_READING_CONTENTS: return "reading contents response";
	case LMTP_WRITING_UPTODATA: 
	    return "writing RSET, MAIL FROM, RCPT TO, DATA commands";
	case LMTP_WRITING_CONTENTS: return "writing contents of message";
	case LMTP_WRITING_QUIT: return "writing QUIT";
	case LMTP_READING_QUIT: return "reading QUIT";
	default: return "unknown state";
	}
}

/******************************* Queue functions ***********************************/

/*
 * Add a message to a generic queue
 *
 *  q       - the queue adding to
 *  item    - the data to add to the queue
 *  type    - the type of item it is (i.e. cancel,lmtp,etc..)
 *  addsmsg - weather this should be counted toward the queue size
 *            this is for control msg's that create multiple queue items.
 *            For example a cancel message canceling a message in multiple
 *            newsgroups will create >1 queue item but we only want it to count
 *            once towards the queue
 *  must    - wheather we must take it even though it may put us over our max size
 */

static conn_ret AddToQueue(Q_t *q, void *item, 
			   control_type_t type, int addsmsg, bool must)
{
    article_queue_t *newentry;

    if (must == false)
    {
	if (q->size >= QUEUE_MAX_SIZE)
        {
	    return RET_EXCEEDS_SIZE;
	}
    } else {
	if (q->size >= QUEUE_MAX_SIZE * 10)
        {
	    d_printf(0, "Queue has grown way too much. Dropping article\n");
	    return RET_FAIL;
	}
    }

    /* add to the end of our queue */
    newentry = xmalloc(sizeof(article_queue_t));

    newentry->type = type;

    /* send as soon as possible */
    newentry->nextsend = newentry->arrived = time(NULL);

    newentry->trys = 0;

    newentry->data.generic = item;
    newentry->next = NULL;
    newentry->counts_toward_size = addsmsg;

    /* add to end of queue */
    if (q->tail == NULL)
    {
	q->head = newentry;
	q->tail = newentry;
    } else {

	q->tail->next = newentry;
	q->tail = newentry;
    }

    q->size+=addsmsg;

    return RET_OK;
}

/*
 * Pop an item from the queue
 *
 * q    - the queue to pop from
 * item - where the item shall be placed upon sucess
 *
 */

static conn_ret PopFromQueue(Q_t *q, article_queue_t **item)
{
    /* if queue empty return error */
    if ( q->head == NULL)
    {
	return RET_QUEUE_EMPTY;
    }
    
    /* set what we return */
    *item = q->head;

    q->head = q->head->next;
    if (q->head == NULL) q->tail = NULL;

    q->size-=(*item)->counts_toward_size;
    
    return RET_OK;
}

/*
 * ReQueue an item. Will either put it back in the queue for another try
 * or forget about it
 *
 *  cxn     - our connection object (needed so forget about things)
 *  q       - the queue to requeue to 
 *  entry   - the item to put back
 */

static void ReQueue(connection_t *cxn, Q_t *q, article_queue_t *entry)
{
    /* look at the time it's been here */
    entry->nextsend = time(NULL) + (entry->trys *30); /* xxx better formula? */

    entry->trys++;
    
    /* give up after 5 tries xxx configurable??? */
    if (entry->trys >= 5)
    {
	QueueForgetAbout(cxn, entry, MSG_FAIL_DELIVER);
	return;
    }


    /* ok let's add back to the end of the queue */
    entry->next = NULL;

    /* add to end of queue */
    if (q->tail == NULL)
    {
	q->head = entry;
	q->tail = entry;
    } else {
	q->tail->next = entry;
	q->tail = entry;
    }

    q->size+=entry->counts_toward_size;
}



/*
 * Forget about an item. Tells host object if we succeeded/failed/etc with the message
 *
 * cxn    - connection object
 * item   - item
 * failed - type of failure (see below)
 *
 * failed:
 *   0 - succeeded delivering message
 *   1 - failed delivering message
 *   2 - Try to give back to host
 *   3 - Article missing (i.e. can't find on disk)
 */
static void QueueForgetAbout(connection_t *cxn, article_queue_t *item, 
			     enum failure_type failed)
{
    Article art = NULL;
    
    switch (item->type)
	{
	case DELIVER:
	    if (failed>0)
		cxn->lmtp_failed++;
	    art = item->data.article;
	    break;

	case CANCEL_MSG:
	    if (failed>0)
		cxn->cancel_failed++;
	    free(item->data.control->msgid);
	    free(item->data.control->folder);

	    if (item->counts_toward_size == 1)
		art = item->data.control->article;

	    free(item->data.control );
	    break;

	case CREATE_FOLDER: 
	    if (failed>0)
		cxn->create_failed++;
	    free(item->data.control->folder);

	    art = item->data.control->article;

	    free(item->data.control );
	    break;
 
	case DELETE_FOLDER:
	    if (failed>0)
		cxn->remove_failed++;
	    free(item->data.control->folder);

	    art = item->data.control->article;

	    free(item->data.control );
	    break;

	default:
	    d_printf(0, "%s:%d QueueForgetAbout(): "
		     "Unknown type to forget about\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    break;
	}

    if (art!=NULL) {
	switch (failed) {
	case MSG_SUCCESS:
	    hostArticleAccepted (cxn->myHost, cxn, art);
	    break;

	case MSG_FAIL_DELIVER:
	    hostArticleRejected (cxn->myHost, cxn, art);
	    break;

	case MSG_GIVE_BACK:
	    hostTakeBackArticle (cxn->myHost, cxn, art);
	    break;

	case MSG_MISSING:
	    hostArticleIsMissing(cxn->myHost, cxn, art);
	    break;
	default:
	    d_printf(0,"%s:%d QueueForgetAbout(): failure type unknown\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    break;
	}
    }

    free(item);
}

/*
 * How much space is available in the queue
 */

static int QueueSpace(Q_t *q)
{
    int ret = QUEUE_MAX_SIZE - q->size;
    if (ret < 0) ret = 0;
    return ret;
}

/*
 * How many items are in the queue
 */

static int QueueItems(Q_t *q)
{
    return q->size;
}


/***************************** END Queue functions ***********************************/

/***************************** Generic Parse Functions *******************************/

/* returns the end of the header */

static char *GetUntil(char *str)
{
    while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
    {
	str++;
    }

    return str;
}

static char *GotoNextLine(char *str)
{
    while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
    {
	str++;
    }

    if (*str == '\r') str++;
    if (*str == '\n') str++;

    return str;
}

/*
 * Finds the given header in the message
 *  Returns NULL if not found
 */
static conn_ret FindHeader(Buffer *bufs, const char *header, char **start,
	char **end)
{
    Buffer b;
    int size;
    char *str_base;
    char *str;
    int headerlen = strlen(header);
    
    if (bufs==NULL)
    {
	if (start)
	    *start=NULL; 
	return RET_ARTICLE_BAD; 
    }
    
    b = bufs[0];
    size = bufferSize(b);
    str_base = bufferBase(b);
    str = str_base;
    
    while ((str - str_base) < size - headerlen)
    {
	if (*str == header[0])
	{
	    if ((strncasecmp(header, str, headerlen)==0) && ( *(str + headerlen)==':'))
	    {

		if (start)
		{
		    *start = str+headerlen+1;

		    /* get rid of leading whitespace */
		    while ( isspace((int) **start))			
			(*start)++;
		}
		
		if (end)
		    *end = GetUntil(str+headerlen+1);
		
		return RET_OK;
	    }
	} else if (*str == '\n') { 
	    /* end of headers */
	    return RET_NO;
	}
	str = GotoNextLine(str);
    }

    return RET_NO;
}

static conn_ret GetLine(char *buf, char *ret, int retmaxsize)
{
    char *str_base;
    char *str;

    int size = strlen(buf);
    str_base = buf;
    str = str_base;

    while ( (*str) != '\0')
    {
	if ((*str) == '\n')
        {
	    if (str-str_base > retmaxsize)
	    {
		d_printf(0, "Max size exceeded! %s\n",str_base);
		return RET_FAIL;
	    }

	    /* fill in the return string */
	    memcpy(ret, str_base, str-str_base);
	    ret[ str - str_base -1] = '\0';

	    memcpy( str_base, str_base + (str-str_base)+1, size - (str-str_base));
	    str_base[size - (str-str_base)]='\0';

	    return RET_OK;
	}

	str++;
    }

    /* couldn't find a full line */
    return RET_NO_FULLLINE;
}



/************************** END Generic Parse Functions *******************************/

/************************ Writing to Network functions *****************/

static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback, 
			    EndPoint endp, Buffer *array)
{

    if (array == NULL) return RET_FAIL;

    prepareWrite (endp,
                  array,
                  NULL,
		  callback,
                  cxn);

    return RET_OK;
}

static conn_ret WriteToWire_str(connection_t *cxn, EndpRWCB callback,
				EndPoint endp, char *str, int slen)
{
    conn_ret result;
    Buffer buff;
    Buffer *writeArr;

    if (slen==-1) slen = strlen(str);

    buff = newBufferByCharP(str, slen+1, slen);
    ASSERT (buff != NULL);   

    writeArr = makeBufferArray (buff, NULL) ;

    result = WriteToWire(cxn, callback, endp, writeArr);

    return result;
}

static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen)
{
    /* prepare the timeouts */
    clearTimer (cxn->imap_readBlockedTimerId) ;
    
    /* set up the write timer. */
    clearTimer (cxn->imap_writeBlockedTimerId) ;

    if (cxn->imap_writeTimeout > 0)
	cxn->imap_writeBlockedTimerId = prepareSleep (imap_writeTimeoutCbk, cxn->imap_writeTimeout,
						 cxn);
    cxn->imap_tofree_str = str;
    return WriteToWire_str(cxn, imap_writeCB, cxn->imap_endpoint, str, slen);
}

static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen)
{
    /* prepare the timeouts */
    clearTimer (cxn->lmtp_readBlockedTimerId) ;
    
    /* set up the write timer. */
    clearTimer (cxn->lmtp_writeBlockedTimerId) ;

    if (cxn->lmtp_writeTimeout > 0)
	cxn->lmtp_writeBlockedTimerId = prepareSleep (lmtp_writeTimeoutCbk, cxn->lmtp_writeTimeout,
						 cxn) ;



    cxn->lmtp_tofree_str = str;
    return WriteToWire_str(cxn, lmtp_writeCB, cxn->lmtp_endpoint, str, slen);
}

static conn_ret WriteArticle(connection_t *cxn, Buffer *array)
{
    int array_len = bufferArrayLen (array);
    int lup=0;
    int result;

    for (lup=0;lup<array_len;lup++)
    {
	int current_size;
	Buffer current_buf;
	char *current_start;

	current_buf = array[lup];
		
	current_size = bufferDataSize( current_buf );
	
	current_start = bufferBase( current_buf );

    }

    /* just call writetowire since it's easy */
    result = WriteToWire(cxn, lmtp_writeCB, cxn->lmtp_endpoint, array);

    if (result!=RET_OK)
    {
	return result;
    }

    cxn->lmtp_state = LMTP_WRITING_CONTENTS;

    return RET_OK;
}

/************************ END Writing to Network functions *****************/



/*
 * Adds a cancel item to the control queue
 * Cancel item to delete message with <msgid> in <folder>
 *
 * cxn       - connection object
 * folder    - pointer to start of folder string (this is a pointer into the actual message buffer)
 * folderlen - length of folder string 
 * msgid     - pointer to start of msgid string (this is a pointer into the actual message buffer)
 * msgidlen  - length of msgid string 
 * art       - the article for this control message (NULL if this cancel object lacks one)
 * must      - if must be accepted into queue
 */

static conn_ret addCancelItem(connection_t *cxn, 
			      char *folder, int folderlen,
			      char *msgid, int msgidlen,
			      Article art, int must)
{
    control_item_t *item;
    conn_ret result;
    int i;

    ASSERT(folder); ASSERT(msgid); ASSERT(cxn);

    /* sanity check folder, msgid */
    for (i = 0; i < folderlen; i++) ASSERT(!isspace((int) folder[i]));
    for (i = 0; i < msgidlen; i++) ASSERT(!isspace((int) msgid[i]));

    /* create the object */
    item = xcalloc (1, sizeof(control_item_t));

    item->folder = xcalloc(folderlen+1, 1);
    memcpy(item->folder, folder, folderlen);
    item->folder[folderlen] = '\0';
    
    item->msgid = xcalloc (msgidlen+1, 1);
    memcpy(item->msgid, msgid, msgidlen);
    item->msgid[msgidlen] = '\0';

    item->article = art;
    
    /* try to add to the queue (counts if art isn't null) */
    result = AddToQueue(&(cxn->imap_controlMsg_q), item, CANCEL_MSG, (art != NULL), must);
    if (result != RET_OK) {
	d_printf(1,"%s:%d addCancelItem(): "
		 "I thought we had in space in [imap] queue "
		 "but apparently not\n",
		 hostPeerName (cxn->myHost), cxn->ident);

	/* cleanup */
	free(item->folder);
	free(item->msgid);
	free(item);

	return result;
    }
		
    return RET_OK;
}

static conn_ret AddControlMsg(connection_t *cxn, 
			      Article art, 
			      Buffer *bufs, 
			      char *control_header, 
			      char *control_header_end, 
			      bool must)
{
    char *rcpt_list = NULL, *rcpt_list_end;
    control_item_t *item;
    conn_ret res = RET_OK;
    int t;

    /* make sure contents ok; this also should load it into memory */
    if (!artContentsOk (art)) {
	d_printf(0, "%s:%d AddControlMsg(): "
		 "artContentsOk() said article was bad\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	hostArticleIsMissing (cxn->myHost, cxn, art);
	return RET_FAIL;
    }

    res = RET_OK;
    /* now let's look at the control to see what it is */
    if (!strncasecmp(control_header,"newgroup",8)) {
	control_header += 8;
	t = CREATE_FOLDER;
    } else if (!strncasecmp(control_header,"rmgroup",7)) {
	/* jump past "rmgroup" */
	control_header += 7;
	t = DELETE_FOLDER;
    } else if (!strncasecmp(control_header,"cancel",6)) {
	t = CANCEL_MSG;
	control_header += 6;
    } else {
	/* unrecognized type */
	char tmp[100];
	char *endstr;
	size_t clen;

	endstr = strchr(control_header,'\n');
	clen = endstr - control_header;

	if (clen > sizeof(tmp)-1) clen = sizeof(tmp)-1;

	memcpy(tmp,control_header, clen);
	tmp[clen]='\0';
	
	d_printf(0,"%s:%d Don't understand control header [%s]\n",
		 hostPeerName (cxn->myHost), cxn->ident,tmp);
	return RET_FAIL;
    }

    switch (t) {
    case CREATE_FOLDER:
    case DELETE_FOLDER:
    {
	int folderlen;

	/* go past all white space */
	while ((*control_header == ' ') && 
	       (control_header != control_header_end)) {
	    control_header++;
	}

	/* trim trailing whitespace */
	while (control_header_end[-1] == ' ') {
	    control_header_end--;
	}

	if (control_header >= control_header_end) {
	    d_printf(0,"%s:%d addControlMsg(): "
		     "newgroup/rmgroup header has no group specified\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    return RET_FAIL;
	}

	folderlen = control_header_end - control_header;

	item = xcalloc(1, sizeof(control_item_t));

	item->folder = xcalloc(folderlen + 1, 1);
	memcpy(item->folder, control_header, folderlen);
	item->folder[folderlen] = '\0';

	item->article = art;

	if (AddToQueue(&(cxn->imap_controlMsg_q), item, t, 1, must) != RET_OK) {
	    d_printf(1,"%s:%d addCancelItem(): "
		     "I thought we had in space in [imap] queue"
		     " but apparently not\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    free(item->folder);
	    free(item);
	    return RET_FAIL;
	}

	break;
    }

    case CANCEL_MSG:
    {
	char *str, *laststart;

	while (((*control_header) == ' ') && 
	       (control_header != control_header_end))
	{
	    control_header++;
	}

	if (control_header == control_header_end)
	{
	    d_printf(0, "%s:%d Control header contains cancel "
		        "with no msgid specified\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    return RET_FAIL;
	}

	if (FindHeader(bufs, "Newsgroups", &rcpt_list, &rcpt_list_end)!=RET_OK)
	{
	    d_printf(0,"%s:%d Cancel msg contains no newsgroups header\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    return RET_FAIL;
	}

	str = rcpt_list;
	laststart = rcpt_list;

	while (str != rcpt_list_end)
	{
	    if (*str == ',') {
		/* eliminate leading whitespace */
		while (((*laststart) ==' ') || ((*laststart)=='\t'))
		{
		    laststart++;
		}

		res = addCancelItem(cxn, laststart, 
				    str - laststart, 
				    control_header, 
				    control_header_end - control_header,
				    NULL, must);
		if (res!=RET_OK) return res;
		
		laststart = str+1;
	    }

	    str++;
	}
	
	if (laststart<str)
	{

	    res = addCancelItem(cxn, laststart, str - laststart, 
				control_header,
				control_header_end - control_header, 
				art, must);
	    if (res!=RET_OK) return res;
	}
	break;
    }
    default:
	/* huh?!? */
	d_printf(0, "%s:%d internal error in addControlMsg()\n",
		     hostPeerName (cxn->myHost), cxn->ident);
    }
    return RET_FAIL;
}

/*
 * Show msg handling statistics
 */

static void show_stats(connection_t *cxn)
{   
    d_printf(0, "%s:%d\n",hostPeerName (cxn->myHost), cxn->ident);
    d_printf(0, "  imap queue = %d lmtp queue = %d\n",
	     QueueItems(&(cxn->imap_controlMsg_q)),
	     QueueItems(&(cxn->lmtp_todeliver_q)));
    d_printf(0,"  imap state = %s\n", imap_stateToString(cxn->imap_state));
    d_printf(0,"  lmtp state = %s\n", lmtp_stateToString(cxn->lmtp_state));
    d_printf(0,"  delivered:  yes: %d no: %d\n",
	     cxn->lmtp_succeeded, 
	     cxn->lmtp_failed);
    d_printf(0,"  cancel:     yes: %d no: %d\n",
	     cxn->cancel_succeeded, cxn->cancel_failed);
    d_printf(0,"  create:     yes: %d no: %d\n",
	     cxn->create_succeeded, cxn->create_failed);
    d_printf(0,"  remove:     yes: %d no: %d\n",
	     cxn->remove_succeeded, cxn->remove_failed);
}

/**************************** SASL helper functions ******************************/

#ifdef HAVE_SASL
/* callback to get userid or authid */
static int getsimple(void *context __attribute__((unused)),
		     int id,
		     const char **result,
		     unsigned *len)
{
  char *username;
  char *authid;

  if (! result)
    return SASL_BADPARAM;


  switch (id) {
  case SASL_CB_GETREALM:
      *result = deliver_realm;
      if (len)
	  *len = deliver_realm ? strlen(deliver_realm) : 0;
      break;

  case SASL_CB_USER:
      *result = deliver_username;
      if (len)
	  *len = deliver_username ? strlen(deliver_username) : 0;
    break;
  case SASL_CB_AUTHNAME:
    authid=deliver_authname;
    *result = authid;
    if (len)
      *len = authid ? strlen(authid) : 0;
      break;
  case SASL_CB_LANGUAGE:
    *result = NULL;
    if (len)
      *len = 0;
    break;
  default:
    return SASL_BADPARAM;
  }
  return SASL_OK;
}

/* callback to get password */
static int
getsecret(sasl_conn_t *conn,
	  void *context __attribute__((unused)),
	  int id,
	  sasl_secret_t **psecret)
{
  size_t passlen;

  if (! conn || ! psecret || id != SASL_CB_PASS)
    return SASL_BADPARAM;

  if (deliver_password==NULL)
  {
      d_printf(0,"SASL requested a password but I don't have one\n");
      return SASL_FAIL;
  }

  passlen = strlen(deliver_password);
  *psecret = xmalloc(sizeof(sasl_secret_t) + passlen + 1);
  if (! *psecret)
    return SASL_FAIL;

  strlcpy((*psecret)->data, deliver_password, passlen + 1);
  (*psecret)->len = passlen;

  return SASL_OK;
}


/* callbacks we support */
static sasl_callback_t saslcallbacks[] = {
  {
    SASL_CB_GETREALM, &getsimple, NULL
  }, {
    SASL_CB_USER, &getsimple, NULL
  }, {
    SASL_CB_AUTHNAME, &getsimple, NULL
  }, {
    SASL_CB_PASS, &getsecret, NULL    
  }, {
    SASL_CB_LIST_END, NULL, NULL
  }
};

static sasl_security_properties_t *make_secprops(int min,int max)
{
  sasl_security_properties_t *ret=
    xmalloc(sizeof(sasl_security_properties_t));

  ret->maxbufsize=1024;
  ret->min_ssf=min;
  ret->max_ssf=max;

  ret->security_flags=0;
  ret->property_names=NULL;
  ret->property_values=NULL;

  return ret;
}

#ifndef NI_WITHSCOPEID
#define NI_WITHSCOPEID	0
#endif
#ifndef NI_MAXHOST
#define NI_MAXHOST	1025
#endif
#ifndef NI_MAXSERV
#define NI_MAXSERV	32
#endif

static int iptostring(const struct sockaddr *addr, socklen_t addrlen,
		     char *out, unsigned outlen) {
    char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
    
    if(!addr || !out) return SASL_BADPARAM;

    getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
		NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV);

    if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
	return SASL_BUFOVER;

    snprintf(out, outlen, "%s;%s", hbuf, pbuf);

    return SASL_OK;
}

static conn_ret SetSASLProperties(sasl_conn_t *conn, int sock, int minssf, int maxssf)
{
  int saslresult;
  sasl_security_properties_t *secprops=NULL;
  int addrsize=sizeof(struct sockaddr_in);
  char localip[60], remoteip[60];
  struct sockaddr_in saddr_l;
  struct sockaddr_in saddr_r;

  /* create a security structure and give it to sasl */
  secprops = make_secprops(minssf, maxssf);
  if (secprops != NULL)
  {
    sasl_setprop(conn, SASL_SEC_PROPS, secprops);
    free(secprops);
  }

  if (getpeername(sock,(struct sockaddr *)&saddr_r,&addrsize)!=0)
    return RET_FAIL;

  if (iptostring((struct sockaddr *)&saddr_r, sizeof(struct sockaddr_in),
		remoteip, sizeof(remoteip)))
    return RET_FAIL;

  if (sasl_setprop(conn, SASL_IPREMOTEPORT, remoteip)!=SASL_OK)
    return RET_FAIL;
  
  addrsize=sizeof(struct sockaddr_in);
  if (getsockname(sock,(struct sockaddr *) &saddr_l,&addrsize)!=0)
    return RET_FAIL;

  if (iptostring((struct sockaddr *)&saddr_l, sizeof(struct sockaddr_in),
		 localip, sizeof(localip)))
    return RET_FAIL;
    
  if (sasl_setprop(conn, SASL_IPLOCALPORT, localip)!=SASL_OK)
    return RET_FAIL;
  
  return RET_OK;
}
#endif /* HAVE_SASL */

/************************** END SASL helper functions ******************************/

/************************* Startup functions **********************************/

static conn_ret Initialize(connection_t *cxn, int respTimeout)
{
#ifdef HAVE_SASL
    conn_ret saslresult;
#endif /* HAVE_SASL */

    d_printf(1,"%s:%d initializing....\n",
	     hostPeerName (cxn->myHost), cxn->ident);

#ifdef HAVE_SASL
    /* only call sasl_client_init() once */
    if (initialized_sasl == 0)
    {
	/* Initialize SASL */
	saslresult=sasl_client_init(saslcallbacks);
	
	if (saslresult!=SASL_OK)
	{
	    d_printf(0,
		     "%s:%d Error initializing SASL (sasl_client_init) (%s)\n",
		     hostPeerName (cxn->myHost), cxn->ident,
		     sasl_errstring(saslresult, NULL, NULL));
	    return RET_FAIL;
	} else {
	    initialized_sasl = 1;
	}
    }
#endif /* HAVE_SASL */

    cxn->lmtp_rBuffer = newBuffer(4096);
    if (cxn->lmtp_rBuffer == NULL)
    {
	d_printf(0, "%s:%d Failure allocating buffer for lmtp_rBuffer\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }
    bufferAddNullByte(cxn->lmtp_rBuffer);


    cxn->imap_rBuffer = newBuffer(4096);
    if (cxn->imap_rBuffer == NULL)
    {
	d_printf(0, "%s:%d Failure allocating buffer for imap_rBuffer \n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }
    bufferAddNullByte(cxn->imap_rBuffer);

    /* Initialize timeouts */
    cxn->lmtp_writeTimeout = respTimeout;
    cxn->lmtp_readTimeout = respTimeout;
    cxn->imap_writeTimeout = respTimeout;
    cxn->imap_readTimeout = respTimeout;
    cxn->lmtp_sleepTimerId = 0 ;
    cxn->lmtp_sleepTimeout = init_reconnect_period ;
    cxn->imap_sleepTimerId = 0 ;
    cxn->imap_sleepTimeout = init_reconnect_period ;

    cxn->dosomethingTimeout = DOSOMETHING_TIMEOUT;

    /* set up the write timer. */
    clearTimer (cxn->dosomethingTimerId) ;

    if (cxn->dosomethingTimeout > 0)
	cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk, 
						cxn->dosomethingTimeout, cxn);



    return RET_OK;
}


/* initialize the network */
static conn_ret init_net(char *serverFQDN, 
			 int port,
			 int *sock)
{
  struct sockaddr_in addr;
  struct hostent *hp;

  if ((hp = gethostbyname(serverFQDN)) == NULL) {
      d_printf(0, "gethostbyname(): %s\n", strerror(errno));
    return RET_FAIL;
  }

  if (( (*sock) = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      d_printf(0, "socket(): %s\n", strerror(errno));
      return RET_FAIL;	
  }

  addr.sin_family = AF_INET;
  memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
  addr.sin_port = htons(port);

  if (connect( (*sock), (struct sockaddr *) &addr, sizeof (addr)) < 0) {
      d_printf(0,"connect(): %s\n",
	       strerror(errno));
      return RET_FAIL;
  }

  return RET_OK;
}



static conn_ret SetupLMTPConnection(connection_t *cxn,
				    char *serverName,
				    int port)
{
#ifdef HAVE_SASL
    int saslresult;
#endif /* HAVE_SASL */
    conn_ret result;

    cxn->lmtp_port = port;

    if (serverName==NULL)
    {
	d_printf(0, "%s:%d serverName is null\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;	
    }

#ifdef HAVE_SASL
    /* Free the SASL connection if we already had one */
    if (cxn->saslconn_lmtp!=NULL)
    {
	sasl_dispose(&cxn->saslconn_lmtp);
    }

    /* Start SASL */
    saslresult=sasl_client_new("lmtp",
			       serverName,
			       NULL,
			       NULL,
			       NULL,
			       0,
			       &cxn->saslconn_lmtp);

    if (saslresult != SASL_OK)
    {

	d_printf(0, "%s:%d:LMTP Error creating a new SASL connection (%s)\n",
		 hostPeerName (cxn->myHost), cxn->ident,
		 sasl_errstring(saslresult,NULL,NULL));
	return RET_FAIL;
    }
#endif /* HAVE_SASL */

    /* Connect the Socket */
    result = init_net(serverName,
		      LMTP_PORT, /*port,*/
		      &(cxn->sockfd_lmtp));
   
    if (result != RET_OK)
    {
	d_printf(0, "%s:%d unable to connect to lmtp host\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }

    if (cxn->lmtp_respBuffer) free(cxn->lmtp_respBuffer);
    cxn->lmtp_respBuffer = xmalloc (4096);
    cxn->lmtp_respBuffer[0]='\0';

    /* Free if we had an existing one */
    if (cxn->lmtp_endpoint != NULL)
    {
	delEndPoint(cxn->lmtp_endpoint);
	cxn->lmtp_endpoint = NULL;
    }

    cxn->lmtp_endpoint = newEndPoint(cxn->sockfd_lmtp);
    if (cxn->lmtp_endpoint == NULL)
    {
	d_printf(0, "%s:%d:LMTP failure creating endpoint\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }

#ifdef HAVE_SASL
    /* Set the SASL properties */
    result = SetSASLProperties(cxn->saslconn_lmtp, cxn->sockfd_lmtp, 
			       0, 0);
    
    if (result != RET_OK)
    {
	d_printf(0,"%s:%d:LMTP error setting SASL properties\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }
#endif /* HAVE_SASL */


    return RET_OK;
}

static conn_ret SetupIMAPConnection(connection_t *cxn,
				    char *serverName,
				    int port)
{
#ifdef HAVE_SASL
    int saslresult;
#endif /* HAVE_SASL */
    conn_ret result;

    cxn->imap_port = port;

    if (serverName==NULL)
    {
	d_printf(0,"%s:%d:IMAP Servername is null",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;	
    }

#ifdef HAVE_SASL
    /* Free the SASL connection if we already had one */
    if (cxn->imap_saslconn!=NULL)
    {
	sasl_dispose(&cxn->imap_saslconn);
    }

    /* Start SASL */
    saslresult=sasl_client_new("imap",
			       serverName,
			       NULL,
			       NULL,
			       NULL,
			       0,
			       &cxn->imap_saslconn);

    if (saslresult != SASL_OK)
    {
	d_printf(0,"%s:%d:IMAP Error creating a new SASL connection (%s)",
		 hostPeerName (cxn->myHost), cxn->ident,
		 sasl_errstring(saslresult,NULL,NULL));
	return RET_FAIL;
    }
#endif /* HAVE_SASL */

    /* Connect the Socket */
    result = init_net(serverName,
		      port,
		      &(cxn->imap_sockfd));
   
    if (result != RET_OK)
    {
	d_printf(0,"%s:%d:IMAP Unable to start network connection for IMAP",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }

    if (cxn->imap_respBuffer) free(cxn->imap_respBuffer);
    cxn->imap_respBuffer = xmalloc (4096);
    cxn->imap_respBuffer[0]='\0';

    /* Free if we had an existing one */
    if (cxn->imap_endpoint != NULL)
    {
	delEndPoint(cxn->imap_endpoint);
	cxn->imap_endpoint = NULL;
    }

    cxn->imap_endpoint = newEndPoint(cxn->imap_sockfd);
    if (cxn->imap_endpoint == NULL)
    {
	d_printf(0,"%s:%d:IMAP Failure creating imap endpoint\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return RET_FAIL;
    }

#ifdef HAVE_SASL
    /* Set the SASL properties */
    result = SetSASLProperties(cxn->imap_saslconn, cxn->imap_sockfd, 
			       0, 0);    
    if (result != RET_OK)
    {
	d_printf(0,"%s:%d:IMAP Error setting sasl properties",
		 hostPeerName (cxn->myHost), cxn->ident);
	return result;
    }
#endif /* HAVE_SASL */


    return RET_OK;
}

/************************* END Startup functions **********************************/

/* Return the response code for this line
   -1 if it doesn't seem to have one
*/
static int ask_code(char *str)
{
    int ret = 0;
    
    if (str==NULL) return -1;

    if (strlen(str) < 3) return -1;

    /* check to make sure 0-2 are digits */
    if ((isdigit((int) str[0])==0) ||
	(isdigit((int) str[1])==0) ||
	(isdigit((int) str[2])==0))
    {
	d_printf(0,
		 "Parse error: response does not begin with a code [%s]\n",
		 str);
	return -1;
    }


    ret = ((str[0]-'0')*100)+
	  ((str[1]-'0')*10)+
	  (str[2]-'0');
    
    return ret;
}

/* is this a continuation or not?
   220-fdfsd is        (1)
   220 fdsfs is not    (0)
 */

static int ask_keepgoing(char *str)
{
    if (str==NULL) return 0;
    if (strlen(str) < 4) return 0;

    if (str[3]=='-') return 1;

    return 0;
}


static conn_ret lmtp_listenintro(connection_t *cxn)
{
    Buffer *readBuffers;

    /* set up to receive */
    readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
    prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);    

    cxn->lmtp_state = LMTP_READING_INTRO;

    return RET_OK;
}



/************************** IMAP functions ***********************/

static conn_ret imap_Connect(connection_t *cxn)
{
    conn_ret result;

    ASSERT(cxn->imap_sleepTimerId == 0);

    /* make the IMAP connection */
    result = SetupIMAPConnection(cxn,
				 cxn->ServerName,
				 IMAP_PORT); 

    /* Listen to the intro and start the authenticating process */
    result = imap_listenintro(cxn);

    return result;
}

/*
 * This is called when the data write timeout for the remote
 * goes off. We tear down the connection and notify our host.
 */
static void imap_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
{
  connection_t *cxn = (Connection) data ;
  const char *peerName ;

  peerName = hostPeerName (cxn->myHost) ;

  syslog (LOG_WARNING, "timeout for %s", peerName);
  d_printf(0, "%s: shutting down non-responsive IMAP connection (%s)\n",
	   hostPeerName (cxn->myHost), 
	   imap_stateToString(cxn->imap_state));

  cxnLogStats (cxn,true) ;

  imap_Disconnect(cxn);
}

/*
 * This is called when the timeout for the reponse from the remote
 * goes off. We tear down the connection and notify our host.
 */
static void imap_readTimeoutCbk (TimeoutId id, void *data)
{
  Connection cxn = (Connection) data ;
  const char *peerName ;

  ASSERT (id == cxn->imap_readBlockedTimerId) ;

  peerName = hostPeerName (cxn->myHost) ;

  warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
  d_printf(0, "%s:%d shutting down non-responsive IMAP connection (%s)\n",
	   hostPeerName (cxn->myHost), cxn->ident,
	   imap_stateToString(cxn->imap_state));

  cxnLogStats (cxn,true);

  if (cxn->imap_state == IMAP_DISCONNECTED)
    {
      imap_Disconnect(cxn);
      lmtp_Disconnect(cxn);
      delConnection (cxn) ;
    }
  else {
      imap_Disconnect(cxn);
  }      
}

/*
 * Called by the EndPoint class when the timer goes off
 */
static void imap_reopenTimeoutCbk (TimeoutId id, void *data)
{
  Connection cxn = (Connection) data ;

  ASSERT (id == cxn->imap_sleepTimerId) ;

  cxn->imap_sleepTimerId = 0 ;

  d_printf(1,"%s:%d:IMAP Reopen timer rang. Try to make new connection now\n",
           hostPeerName (cxn->myHost), cxn->ident) ;
  
  if (cxn->imap_state != IMAP_DISCONNECTED)
    {
      warn ("%s:%d cxnsleep connection in bad state: %s",
            hostPeerName (cxn->myHost), cxn->ident,
            imap_stateToString (cxn->imap_state)) ;
    }
  else {
      if (imap_Connect(cxn) != RET_OK)
	  prepareReopenCbk(cxn, 0);
  }
}

static void imap_Disconnect(connection_t *cxn)
{
    clearTimer (cxn->imap_sleepTimerId) ;
    cxn->imap_sleepTimerId = 0;
    clearTimer (cxn->imap_readBlockedTimerId) ;
    clearTimer (cxn->imap_writeBlockedTimerId) ;

    DeferAllArticles(cxn, &(cxn->imap_controlMsg_q)) ;      /* give any articles back to Host */

    cxn->imap_state = IMAP_DISCONNECTED;

    cxn->imap_disconnects++;

    cxn->imap_respBuffer[0]='\0';

    if (cxn->issue_quit == 0)
	prepareReopenCbk(cxn,0);

    DeleteIfDisconnected(cxn);
}

/************************** END IMAP functions ***********************/

/************************ LMTP functions **************************/

/*
 * Create a network lmtp connection
 * and start listening for the intro string
 *
 */

static conn_ret lmtp_Connect(connection_t *cxn)
{
    conn_ret result;

    ASSERT(cxn->lmtp_sleepTimerId == 0);

    /* make the LMTP connection */
    result = SetupLMTPConnection(cxn,
				 cxn->ServerName,
				 LMTP_PORT);

    if (result!=RET_OK) return result;

    /* Listen to the intro */
    result = lmtp_listenintro(cxn);

    return result;
}



static void lmtp_Disconnect(connection_t *cxn)
{
    clearTimer (cxn->lmtp_sleepTimerId) ;
    cxn->lmtp_sleepTimerId = 0;
    clearTimer (cxn->lmtp_readBlockedTimerId) ;
    clearTimer (cxn->lmtp_writeBlockedTimerId) ;

    /* give any articles back to Host */
    DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q)) ;      

    cxn->lmtp_state = LMTP_DISCONNECTED;

    cxn->lmtp_disconnects++;

    cxn->lmtp_respBuffer[0]='\0';

    if (cxn->issue_quit == 0)
	prepareReopenCbk(cxn,1);

    DeleteIfDisconnected(cxn);
}



/*
 * Called by the EndPoint class when the timer goes off
 */
static void lmtp_reopenTimeoutCbk (TimeoutId id, void *data)
{
  Connection cxn = (Connection) data ;

  ASSERT (id == cxn->lmtp_sleepTimerId) ;

  cxn->lmtp_sleepTimerId = 0 ;

  d_printf(1,"%s:%d:LMTP Reopen timer rang. Try to make new connection now\n",
           hostPeerName (cxn->myHost), cxn->ident) ;
        
  if (cxn->lmtp_state != LMTP_DISCONNECTED)
    {
      warn ("%s:%d cxnsleep connection in bad state: %s",
            hostPeerName (cxn->myHost), cxn->ident,
            lmtp_stateToString (cxn->lmtp_state)) ;
    }
  else {
      if (lmtp_Connect(cxn) != RET_OK)
	  prepareReopenCbk(cxn, 1);
  }
}

/*
 * Set up the callback used when the Connection is sleeping (i.e. will try
 * to reopen the connection).
 *
 * type (0 = imap, 1 = lmtp)
 */
static void prepareReopenCbk (Connection cxn, int type)
{
    /* xxx check state */



    if (type == 0) {

	cxn->imap_sleepTimerId = prepareSleep (imap_reopenTimeoutCbk, 
					       cxn->imap_sleepTimeout, cxn) ;
	d_printf (1,"%s:%d IMAP connection error\n"
		  "  will try to reconnect in %d seconds\n",
		  hostPeerName (cxn->myHost), cxn->ident, 
		  cxn->imap_sleepTimeout) ;
    } else {
	cxn->lmtp_sleepTimerId = prepareSleep (lmtp_reopenTimeoutCbk, 
					       cxn->lmtp_sleepTimeout, cxn) ;
	d_printf (1,"%s:%d:LMTP connection error\n"
		  "will try to reconnect in %d seconds\n",
		  hostPeerName (cxn->myHost), cxn->ident, 
		  cxn->lmtp_sleepTimeout) ;
    }

    /* bump the sleep timer amount each time to wait longer and longer. Gets
       reset in resetConnection() */
    if (type == 0) {
	cxn->imap_sleepTimeout *= 2 ;
	if (cxn->imap_sleepTimeout > max_reconnect_period)
	    cxn->imap_sleepTimeout = max_reconnect_period ;
    } else {
	cxn->lmtp_sleepTimeout *= 2 ;
	if (cxn->lmtp_sleepTimeout > max_reconnect_period)
	    cxn->lmtp_sleepTimeout = max_reconnect_period ;
    }
}

/*
 * This is called when the timeout for the reponse from the remote
 * goes off. We tear down the connection and notify our host.
 */
static void lmtp_readTimeoutCbk (TimeoutId id, void *data)
{
  Connection cxn = (Connection) data ;
  const char *peerName ;

  ASSERT (id == cxn->lmtp_readBlockedTimerId) ;

  peerName = hostPeerName (cxn->myHost) ;

  warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
  d_printf(0,"%s:%d shutting down non-responsive LMTP connection (%s)\n",
           hostPeerName (cxn->myHost), cxn->ident,
	   lmtp_stateToString(cxn->lmtp_state));

  cxnLogStats (cxn,true) ;

  if (cxn->lmtp_state == LMTP_DISCONNECTED) {
      imap_Disconnect(cxn);
      lmtp_Disconnect(cxn);
      delConnection (cxn) ;
  } else {
      lmtp_Disconnect(cxn);
  }      
}



/*
 * This is called when the data write timeout for the remote
 * goes off. We tear down the connection and notify our host.
 */
static void lmtp_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
{
    connection_t *cxn = (Connection) data ;
    const char *peerName ;

    peerName = hostPeerName (cxn->myHost) ;

    syslog (LOG_WARNING, "timeout for %s", peerName);
    d_printf(0, "%s:%d shutting down non-responsive LMTP connection (%s)\n",
	     hostPeerName (cxn->myHost), cxn->ident,
	     lmtp_stateToString(cxn->lmtp_state)) ;

    cxnLogStats (cxn,true);

    lmtp_Disconnect(cxn);
}

/************************ END LMTP functions **************************/

/************************** LMTP write functions ********************/

static conn_ret lmtp_noop(connection_t *cxn)
{
    int result;
    char *p;

    p = xstrdup("NOOP\r\n");
    result = WriteToWire_lmtpstr(cxn, p, strlen(p));
    if (result!=RET_OK) return result;

    cxn->lmtp_state = LMTP_WRITING_NOOP;

    return RET_OK;
}

static conn_ret lmtp_IssueQuit(connection_t *cxn)
{
    int result;
    char *p;

    p = xstrdup("QUIT\r\n");
    result = WriteToWire_lmtpstr(cxn, p, strlen(p));
    if (result!=RET_OK) return result;

    cxn->lmtp_state = LMTP_WRITING_QUIT;

    return RET_OK;
}

static conn_ret lmtp_getcapabilities(connection_t *cxn)
{
    int result;
    char *p;

    if (cxn->lmtp_capabilities != NULL)
    {
	if (cxn->lmtp_capabilities->saslmechs) {
	    free( cxn->lmtp_capabilities->saslmechs);
	}
	free( cxn->lmtp_capabilities );
	cxn->lmtp_capabilities = NULL;
    }

    cxn->lmtp_capabilities = xcalloc (1, sizeof(lmtp_capabilities_t));
    cxn->lmtp_capabilities->saslmechs = NULL;

#ifdef SMTPMODE
    p = concat("EHLO ", hostname, "\r\n", (char *) 0);
#else
    p = concat("LHLO ", hostname, "\r\n", (char *) 0);
#endif /* SMTPMODE */

    result = WriteToWire_lmtpstr(cxn, p, strlen(p));
    if (result!=RET_OK) return result;

    cxn->lmtp_state = LMTP_WRITING_LHLO;

    return RET_OK;    
}

#ifdef HAVE_SASL
static conn_ret lmtp_authenticate(connection_t *cxn)
{
    int saslresult;
    
    const char *mechusing;
    const char *out;
    unsigned int outlen;
    char *inbase64;
    int inbase64len;
    int status;
    int result;

    char *p;

    sasl_interact_t *client_interact=NULL;


    saslresult=sasl_client_start(cxn->saslconn_lmtp, 
				 cxn->lmtp_capabilities->saslmechs,
				 &client_interact,
				 &out, &outlen,
				 &mechusing);



    if ((saslresult != SASL_OK) && 
	(saslresult != SASL_CONTINUE)) {

	d_printf(0,"%s:%d:LMTP Error calling sasl_client_start (%s)\n",
		 hostPeerName (cxn->myHost), cxn->ident,
		 sasl_errstring(saslresult, NULL, NULL));
	return RET_FAIL;
    }

    d_printf(1,"%s:%d:LMTP Decided to try to authenticate with SASL mechanism=%s\n",           
	     hostPeerName (cxn->myHost), cxn->ident,mechusing);

    if (!out)
    {
	/* no initial client response */
        p = concat("AUTH ", mechusing, "\r\n", (char *) 0);
    } else if (!outlen) {
	/* empty initial client response */
        p = concat("AUTH ", mechusing, " =\r\n", (char *) 0);
    } else {
	/* initial client response - convert to base64 */
	inbase64 = xmalloc(outlen*2+10);

	saslresult = sasl_encode64(out, outlen,
				   inbase64, outlen*2+10,
                                   (unsigned *) &inbase64len);
	if (saslresult != SASL_OK) return RET_FAIL;
        p = concat("AUTH ", mechusing, " ", inbase64, "\r\n", (char *) 0);
        free(inbase64);
    }

    result = WriteToWire_lmtpstr(cxn, p, strlen(p));

    cxn->lmtp_state = LMTP_WRITING_STARTAUTH;

    return RET_OK;
}

static imt_stat lmtp_getauthline(char *str, char **line, int *linelen)
{
  char buf[4096];
  int saslresult;
  int response_code = -1;
  
  response_code = ask_code(str);

  if (response_code == 334) {
      
      /* continue */

  } else if (response_code == 235) {

      /* woohoo! authentication complete */
      return STAT_OK;

  } else {
      /* failure of some sort */
      d_printf(0,"?:?:LMTP Authentication failure (%d) [%s]\n",
	       response_code,str);
      return STAT_NO;
  }

  str += 4; /* jump past the "334 " */

  *line = xmalloc(strlen(str)+30);
  if ((*line)==NULL) {
      return STAT_NO;
  }

  /* decode this line */      
  saslresult = sasl_decode64(str, strlen(str), 
			     *line, strlen(str)+1, (unsigned *) linelen);
  if (saslresult != SASL_OK) {
      d_printf(0,"?:?:LMTP base64 decoding error\n");
      return STAT_NO;
  }

  return STAT_CONT;
}
#endif /* HAVE_SASL */

static void lmtp_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
	void *d)
{
    connection_t *cxn = (connection_t *) d;
    Buffer *readBuffers;

    clearTimer (cxn->lmtp_writeBlockedTimerId) ;

    /* Free the string that was written */
    freeBufferArray (b);
    if (cxn->lmtp_tofree_str!=NULL)
    {
	free(cxn->lmtp_tofree_str);
	cxn->lmtp_tofree_str=NULL;
    }

    /* set up to receive */
    readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
    prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);

   /* set up the response timer. */
    clearTimer (cxn->lmtp_readBlockedTimerId) ;

    if (cxn->lmtp_readTimeout > 0)
	cxn->lmtp_readBlockedTimerId = prepareSleep (lmtp_readTimeoutCbk, 
						     cxn->lmtp_readTimeout, cxn) ;


    switch (cxn->lmtp_state)
	{
	    
	case LMTP_WRITING_LHLO:
	    cxn->lmtp_state = LMTP_READING_LHLO;
	    break;

	case LMTP_WRITING_STARTAUTH:
	case LMTP_WRITING_STEPAUTH:

	    cxn->lmtp_state = LMTP_READING_STEPAUTH;

	    break;

	case LMTP_WRITING_UPTODATA:
	    /* expect result to rset */
	    cxn->lmtp_state = LMTP_READING_RSET;
	    break;

	case LMTP_WRITING_CONTENTS:
	    /* so we sent the whole DATA command
	       let's see what the server responded */

	    cxn->lmtp_state = LMTP_READING_CONTENTS;

	    break;

	case LMTP_WRITING_NOOP:
	    cxn->lmtp_state = LMTP_READING_NOOP;
	    break;

	case LMTP_WRITING_QUIT:
	    cxn->lmtp_state = LMTP_READING_QUIT;
	    break;

	default:

	    d_printf(0,"%s:%d:LMTP Unknown state. Internal error\n",
		     hostPeerName (cxn->myHost), cxn->ident) ;

	    break;
	}
}

/************************** END LMTP write functions ********************/

/************************** IMAP sending functions ************************/


static void imap_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
	void *d)
{
    connection_t *cxn = (connection_t *) d;
    Buffer *readBuffers;

    clearTimer (cxn->imap_writeBlockedTimerId) ;

    /* free the string we just wrote out */
    freeBufferArray (b);
    if (cxn->imap_tofree_str!=NULL)
    {
	free(cxn->imap_tofree_str);
	cxn->imap_tofree_str=NULL;
    }

    /* set up to receive */
    readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
    prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);

    /* set up the response timer. */
    clearTimer (cxn->imap_readBlockedTimerId) ;

    if (cxn->imap_readTimeout > 0)
	cxn->imap_readBlockedTimerId = prepareSleep(imap_readTimeoutCbk, 
						    cxn->imap_readTimeout, 
						    cxn);

    switch (cxn->imap_state) {
    case IMAP_WRITING_CAPABILITY:
	cxn->imap_state = IMAP_READING_CAPABILITY;
	break;

    case IMAP_WRITING_STEPAUTH:
    case IMAP_WRITING_STARTAUTH:
	cxn->imap_state = IMAP_READING_STEPAUTH;
	break;

    case IMAP_WRITING_CREATE:
	cxn->imap_state = IMAP_READING_CREATE;
	break;

    case IMAP_WRITING_DELETE:
	cxn->imap_state = IMAP_READING_DELETE;
	break;

    case IMAP_WRITING_SELECT:
	cxn->imap_state = IMAP_READING_SELECT;
	break;

    case IMAP_WRITING_SEARCH:
	cxn->imap_state = IMAP_READING_SEARCH;
	break;

    case IMAP_WRITING_STORE:
	cxn->imap_state = IMAP_READING_STORE;
	break;

    case IMAP_WRITING_CLOSE:
	cxn->imap_state = IMAP_READING_CLOSE;
	break;

    case IMAP_WRITING_NOOP:
	cxn->imap_state = IMAP_READING_NOOP;
	break;

    case IMAP_WRITING_QUIT:
	cxn->imap_state = IMAP_READING_QUIT;
	break;

    default:
	d_printf(0,"%s:%d:IMAP invalid connection state\n",
		 hostPeerName (cxn->myHost), cxn->ident) ;
	imap_Disconnect(cxn);
	break;
    }
}

/*
 * Tag is already allocated
 */

static void imap_GetTag(connection_t *cxn)
{
    sprintf(cxn->imap_currentTag,"%06d",cxn->imap_tag_num);
    cxn->imap_tag_num++;
    if (cxn->imap_tag_num >= 999999)
    {
	cxn->imap_tag_num = 0;
    }
}

#ifdef HAVE_SASL
static conn_ret imap_sendAuthStep(connection_t *cxn, char *str)
{
    conn_ret result;
    int saslresult;
    char in[4096];
    unsigned int inlen;
    const char *out;
    unsigned int outlen;
    char *inbase64;
    unsigned int inbase64len;

    /* base64 decode it */

    saslresult = sasl_decode64(str, strlen(str), 
			       in, strlen(str)+1, &inlen);
    if (saslresult != SASL_OK) {
	d_printf(0,"%s:%d:IMAP base64 decoding error\n",
		 hostPeerName (cxn->myHost), cxn->ident) ;
	return RET_FAIL;
    }

    saslresult=sasl_client_step(cxn->imap_saslconn,
				in,
				inlen,
				NULL,
				&out,
				&outlen);

    /* check if sasl succeeded */
    if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {

	d_printf(0,"%s:%d:IMAP sasl_client_step failed with %s\n",
		 hostPeerName (cxn->myHost), cxn->ident,
		 sasl_errstring(saslresult,NULL,NULL));
	cxn->imap_state = IMAP_CONNECTED_NOTAUTH;
	return RET_FAIL;
    }

    inbase64 = xmalloc(outlen * 2 + 10);

    /* convert to base64 */
    saslresult = sasl_encode64(out, outlen,
			       inbase64, outlen*2, (unsigned *) &inbase64len);

    if (saslresult != SASL_OK) return RET_FAIL;

    /* append endline */
    strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10 - inbase64len);
    inbase64len+=2;
    
    /* send to server */
    result = WriteToWire_imapstr(cxn,inbase64, inbase64len);
    
    cxn->imap_state = IMAP_WRITING_STEPAUTH;

    return result;
}
#endif /* HAVE_SASL */

static conn_ret imap_sendAuthenticate(connection_t *cxn)
{
    int result;

    char *p;

#ifdef HAVE_SASL
    const char *mechusing;
    char *inbase64;
    int inbase64len;
    int saslresult=SASL_NOMECH;

    sasl_interact_t *client_interact=NULL;

    if (cxn->imap_capabilities->saslmechs) {
	saslresult=sasl_client_start(cxn->imap_saslconn, 
				     cxn->imap_capabilities->saslmechs,
				     &client_interact,
				     NULL, NULL,
				     &mechusing);
    }



    /* If no mechs try "login" */
    if (saslresult == SASL_NOMECH)
    {

#else /* HAVE_SASL */

     { /* always do login */

#endif /* HAVE_SASL */
	d_printf(1,"%s:%d:IMAP No mechanism found. Trying login method\n",
		 hostPeerName (cxn->myHost), cxn->ident) ;

	if (cxn->imap_capabilities->logindisabled==1)
	{
	    d_printf(0,"%s:%d:IMAP Login command w/o security layer not allowed on this server\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    return RET_FAIL;
	}

	if (deliver_authname==NULL)
	{
	    d_printf(0,"%s:%d:IMAP Unable to log in because can't find a authname\n",
		     hostPeerName (cxn->myHost), cxn->ident) ;
	    return RET_FAIL;
	}

	if (deliver_password==NULL)
	{
	    d_printf(0,"%s:%d:IMAP Unable to log in because can't find a password\n",
		     hostPeerName (cxn->myHost), cxn->ident) ;
	    return RET_FAIL;
	}

	imap_GetTag(cxn);

        p = concat(cxn->imap_currentTag, " LOGIN ", deliver_authname, " \"",
                   deliver_password, "\"\r\n", (char *) 0);
	
	result = WriteToWire_imapstr(cxn, p, strlen(p));
	
	cxn->imap_state = IMAP_WRITING_STARTAUTH;
	
	return RET_OK;       
    }

#ifdef HAVE_SASL
    if ((saslresult != SASL_OK) && 
	(saslresult != SASL_CONTINUE)) {

	d_printf(0,"%s:%d:IMAP Error calling sasl_client_start (%s) mechusing = %s\n",
		 hostPeerName (cxn->myHost), cxn->ident,
		 sasl_errstring(saslresult, NULL, NULL), mechusing);
	return RET_FAIL;
    }

    d_printf(1,"%s:%d:IMAP Trying to authenticate to imap with %s mechanism\n",
	     hostPeerName (cxn->myHost), cxn->ident,
	     mechusing);

    imap_GetTag(cxn);

    p = concat(cxn->imap_currentTag, " AUTHENTICATE ", mechusing, "\r\n",
               (char *) 0);
    result = WriteToWire_imapstr(cxn, p, strlen(p));

    cxn->imap_state = IMAP_WRITING_STARTAUTH;

    return RET_OK;
#endif /* HAVE_SASL */
}

static conn_ret imap_CreateGroup(connection_t *cxn, char *bboard)
{
    conn_ret result;
    char *tosend;

    d_printf(1,"%s:%d:IMAP Ok creating group [%s]\n",
	     hostPeerName (cxn->myHost), cxn->ident,
	     bboard);

    imap_GetTag(cxn);

    tosend = concat(cxn->imap_currentTag, " CREATE ", bboard, "\r\n",
                    (char *) 0);

    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result!=RET_OK) return result;
    
    cxn->imap_state = IMAP_WRITING_CREATE;
    
    return RET_OK;
}

static conn_ret imap_DeleteGroup(connection_t *cxn, char *bboard)
{
    conn_ret result;
    char *tosend;

    d_printf(1,"%s:%d:IMAP Ok removing bboard [%s]\n",
		 hostPeerName (cxn->myHost), cxn->ident, bboard);

    imap_GetTag(cxn);

    tosend = concat(cxn->imap_currentTag, " DELETE ", bboard, "\r\n",
                    (char *) 0);
    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result!=RET_OK) return result;

    cxn->imap_state = IMAP_WRITING_DELETE;
    
    return RET_OK;
}

static conn_ret imap_CancelMsg(connection_t *cxn, char *newsgroup)
{
    conn_ret result;
    char *tosend;

    ASSERT(newsgroup);

    imap_GetTag(cxn);

    /* select mbox */
    tosend = concat(cxn->imap_currentTag, " SELECT ", newsgroup, "\r\n",
                    (char *) 0);
    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result != RET_OK) return result;

    cxn->imap_state = IMAP_WRITING_SELECT;

    hostArticleOffered (cxn->myHost, cxn);

    return RET_OK;
}

static conn_ret imap_sendSearch(connection_t *cxn, char *msgid)
{
    conn_ret result;
    char *tosend;

    ASSERT(msgid);

    imap_GetTag(cxn);

    /* preform search */
    tosend = concat(cxn->imap_currentTag,
                    " UID SEARCH header \"Message-ID\" \"", msgid, "\"\r\n",
                    (char *) 0);
    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result != RET_OK) return result;

    cxn->imap_state = IMAP_WRITING_SEARCH;

    return RET_OK;
}

static conn_ret imap_sendKill(connection_t *cxn, unsigned uid)
{
    conn_ret result;
    char *tosend;
    size_t length;

    imap_GetTag(cxn);

    length = 7 + 50 + 20;
    tosend = xmalloc(length);
    snprintf(tosend,length,"%s UID STORE %d +FLAGS.SILENT (\\Deleted)\r\n",
             cxn->imap_currentTag, uid);

    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result != RET_OK) return result;

    cxn->imap_state = IMAP_WRITING_STORE;

    return RET_OK;
}

static conn_ret imap_sendSimple(connection_t *cxn, const char *atom, int st)
{
    char *tosend;
    conn_ret result;

    imap_GetTag(cxn);
    tosend = concat(cxn->imap_currentTag, " ", atom, "\r\n", (char *) 0);

    result = WriteToWire_imapstr(cxn, tosend, -1);
    if (result != RET_OK) return result;

    cxn->imap_state = st;

    return RET_OK;
}

static conn_ret imap_sendClose(connection_t *cxn)
{
    return imap_sendSimple(cxn, "CLOSE", IMAP_WRITING_CLOSE);
}

static conn_ret imap_sendQuit(connection_t *cxn)
{
    return imap_sendSimple(cxn, "LOGOUT", IMAP_WRITING_QUIT);
}

static conn_ret imap_noop(connection_t *cxn)
{
    return imap_sendSimple(cxn, "NOOP", IMAP_WRITING_NOOP);
}
   	

static conn_ret imap_sendCapability(connection_t *cxn)
{
    return imap_sendSimple(cxn, "CAPABILITY", IMAP_WRITING_CAPABILITY);
}

/************************** END IMAP sending functions ************************/

/************************** IMAP reading functions ***************************/

static conn_ret imap_listenintro(connection_t *cxn)
{
    Buffer *readBuffers;

    /* set up to receive */
    readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
    prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);    

    cxn->imap_state = IMAP_READING_INTRO;

    return RET_OK;
}

static conn_ret imap_ParseCapability(char *string, imap_capabilities_t **caps)
{
    char *str = string;
    char *start = str;
    size_t mechlen;

    /* allocate the caps structure if it doesn't already exist */
    if ( (*caps) == NULL)
	(*caps) = xcalloc(1, sizeof(imap_capabilities_t));

    while ( (*str) != '\0')
    {

	while (((*str) != '\0') && ((*str)!=' '))
	{
	    str++;
	}
	
	if ( (*str) != '\0')
	{
	    *str = '\0';
	    str++;
	}

	if ( strcasecmp(start,"IMAP4")==0)
	{
	    (*caps)->imap4 = 1;
	} else if (strcasecmp(start,"LOGINDISABLED")==0) {
	    (*caps)->logindisabled = 1;
	} else if ( strncmp(start, "AUTH=", 5)==0) {
	    
	    if ( (*caps)->saslmechs == NULL)
	    {
                (*caps)->saslmechs = xstrdup (start + 5);
	    } else {
                mechlen = strlen((*caps)->saslmechs) + 1;
                mechlen += strlen(start + 5) + 1;
		(*caps)->saslmechs = xrealloc((*caps)->saslmechs, mechlen);
                strlcat((*caps)->saslmechs, " ", mechlen);
                strlcat((*caps)->saslmechs, start + 5, mechlen);
	    }
	}
	
	start = str;
          
    }

    if ((*caps)->saslmechs) {
	d_printf(1,"?:?:IMAP parsed capabilities: saslmechs = %s\n",
		 (*caps)->saslmechs);
    }

    return RET_OK;
}


static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
{
    connection_t *cxn = (connection_t *) d;
    Buffer *readBuffers ;

    int okno;
    char *str;
    char strbuf[4096];
    char *linestart;
    conn_ret ret;
    char *p;

    p = bufferBase(b[0]);

    /* Add what we got to our internal read buffer */
    bufferAddNullByte (b[0]) ;

    if (i != IoDone) {
	errno = endPointErrno(e);

	syslog(LOG_ERR, "%s:%d IMAP i/o failed: %m",
	       hostPeerName (cxn->myHost), cxn->ident);
	freeBufferArray (b);
	imap_Disconnect(cxn);
	return;
    }

    if (strchr (p, '\n') == NULL) {
	/* partial read. expand buffer and retry */
	
	if (expandBuffer (b[0], BUFFER_EXPAND_AMOUNT)==false) {
	    d_printf(0,"%s:%d:IMAP expanding buffer returned false\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    
	    imap_Disconnect(cxn);
	    return;
	}
	readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
	
	if (!prepareRead (e, readBuffers, imap_readCB, cxn, 1)) {
	    imap_Disconnect(cxn);
	}
	
	freeBufferArray (b);
	return;
    }

    clearTimer (cxn->imap_readBlockedTimerId) ;

    /* we got something. add to our buffer and free b */

    strcat(cxn->imap_respBuffer, p);

    bufferSetDataSize( b[0], 0);

    freeBufferArray (b);



    /* goto here to take another step */
 reset:

    /* see if we have a full line */
    ret = GetLine( cxn->imap_respBuffer , strbuf, sizeof(strbuf));
    str = strbuf;
    linestart = str;

    /* if we don't have a full line */
    if ( ret == RET_NO_FULLLINE)
	{

	    readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;

	    if ( !prepareRead (e, readBuffers, imap_readCB, cxn, 1) )
		{
		    imap_Disconnect(cxn);
		}
	    return;

	} else if (ret!=RET_OK)
	    {
		return;
	    }

    /* if untagged */
    if ((str[0]=='*') && (str[1]==' '))
    {
	str+=2;
	
	/* now figure out what kind of untagged it is */
	if (strncasecmp(str,"CAPABILITY ",11)==0)
	{
	    str+=11;
	    
	    imap_ParseCapability(str,&(cxn->imap_capabilities));
	    
	} else if (strncasecmp(str,"SEARCH",6)==0) {

	    str+=6;
	    
	    if ( (*str) == ' ')
	    {
		str++;
		
		cxn->current_control->data.control->uid = atoi(str);
		
		d_printf(1,"%s:%d:IMAP i think the UID = %ld\n",
			 hostPeerName (cxn->myHost), cxn->ident,
			 cxn->current_control->data.control->uid);
	    } else {
		/* it's probably a blank uid (i.e. message doesn't exist) */
		cxn->current_control->data.control->uid = (unsigned long)-1;
	    }

	    
	} else if (strncasecmp(str,"OK ",3)==0) {
	    
	    if (cxn->imap_state==IMAP_READING_INTRO)
	    {
		imap_sendCapability(cxn); /* xxx errors */
		return;
		
	    } else {
		
	    }
	    
	    
	} else {
	    /* untagged command not understood */
	}

	/* always might be more to look at */
	goto reset;

    } else if ((str[0]=='+') && (str[1]==' ')) {

	str+=2;

	if (cxn->imap_state == IMAP_READING_STEPAUTH)
	{
#ifdef HAVE_SASL
	    if (imap_sendAuthStep(cxn, str)!=RET_OK)
	    {
		imap_Disconnect(cxn);
	    }
#else
	    d_printf(0,"%s:%d:IMAP got a '+ ...' without SASL. Something's wrong\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    imap_Disconnect(cxn);
#endif /* HAVE_SASL */

	    return;
	} else {
	    d_printf(0,"%s:%d:IMAP got a '+ ...' in state where not expected\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    imap_Disconnect(cxn);
	    return;
	}
	

	
    } else if (strncmp(str, cxn->imap_currentTag, IMAP_TAGLENGTH)==0) {	
	/* matches our tag */
	str += IMAP_TAGLENGTH;
	
	if (str[0]!=' ')
	{
	    d_printf(0,"%s:%d:IMAP Parse error: tag with no space afterward\n",
		     hostPeerName (cxn->myHost), cxn->ident);
	    imap_Disconnect(cxn);
	    return;	    
	}
	str++;

	/* should be OK/NO */
	if (strncmp(str,"OK",2)==0)
	{
	    okno = 1;
	} else {
	    okno = 0;
	}
	
	switch(cxn->imap_state)
	    {
	    case IMAP_READING_CAPABILITY:

		if (okno==1) {
		    if (imap_sendAuthenticate(cxn)!=RET_OK)
		    {
			d_printf(0,"%s:%d:IMAP sendauthenticate failed\n",
				 hostPeerName (cxn->myHost), cxn->ident);
			imap_Disconnect(cxn);
		    }
		    return;
		} else {
		    d_printf(0,"%s:%d:IMAP CAPABILITY gave a NO response\n",
			     hostPeerName (cxn->myHost), cxn->ident);
		    imap_Disconnect(cxn);
		}
		return;
		
		break;
	    case IMAP_READING_STEPAUTH:
		
		if (okno == 1) {

		    cxn->imap_sleepTimeout = init_reconnect_period ;

		    cxn->imap_timeCon = theTime () ;
		    cxn->timeCon = theTime () ;
		    
		    d_printf(0,"%s:%d IMAP authentication succeeded\n",
			     hostPeerName (cxn->myHost), cxn->ident);

		    cxn->imap_disconnects=0;
		    
		    cxn->imap_state = IMAP_IDLE_AUTHED;

		    /* try to send a message if we have one */
		    
		    imap_ProcessQueue(cxn);
		} else {
		    d_printf(0,"%s:%d:IMAP Authentication failed with [%s]\n",		     
			     hostPeerName (cxn->myHost), cxn->ident,str);
		    imap_Disconnect(cxn);
		}

		return;

		break;
		
	    case IMAP_READING_CREATE:

		    if (okno==1) {
			
			d_printf(1,"%s:%d:IMAP Create of bboard successful\n",
				 hostPeerName (cxn->myHost), cxn->ident);
				 
			cxn->create_succeeded++;

			/* we can delete article now */
			QueueForgetAbout(cxn, cxn->current_control, 
					 MSG_SUCCESS);
		    } else {
			d_printf(1,"%s:%d:IMAP Create failed with [%s] for %s\n",
				 hostPeerName (cxn->myHost), cxn->ident,str,
				 cxn->current_control->data.control->folder);

			ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
		    }

		    imap_ProcessQueue(cxn);
			
		    break;
		    
	    case IMAP_READING_DELETE:

		    if (okno==1) {
			d_printf(1,"%s:%d:IMAP Delete successful\n",
				 hostPeerName (cxn->myHost), cxn->ident);
			cxn->remove_succeeded++;

			/* we can delete article now */
			QueueForgetAbout(cxn, cxn->current_control, 
					 MSG_SUCCESS);
		    } else {
			d_printf(1,"%s:%d:IMAP Delete mailbox failed with [%s] for %s\n",
				 hostPeerName (cxn->myHost), cxn->ident,str,
				 cxn->current_control->data.control->folder);

			ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
			
		    }

		    imap_ProcessQueue(cxn);
		    return;
			
		    break;

	    case IMAP_READING_SELECT:

		    if (okno==1) {

			imap_sendSearch(cxn, cxn->current_control->data.control->msgid);
			return;

		    } else {
			d_printf(1,"%s:%d:IMAP Select failed with [%s] for %s\n",
				 hostPeerName (cxn->myHost), cxn->ident,str,
				 cxn->current_control->data.control->folder);

			ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
			
			cxn->imap_state = IMAP_IDLE_AUTHED;

			imap_ProcessQueue(cxn);
			return;
		    }

		    break;

	    case IMAP_READING_SEARCH:
		/* if no message let's forget about it */
		if (cxn->current_control->data.control->uid
			== (unsigned long)-1) {
		    d_printf(2, "%s:%d:IMAP Search didn't find the message\n",
			     hostPeerName (cxn->myHost), cxn->ident);
		    QueueForgetAbout(cxn, cxn->current_control, 
				     MSG_FAIL_DELIVER);
		    if (imap_sendClose(cxn) != RET_OK)
			imap_Disconnect(cxn);
		    return;
		}

		if (okno==1) {
		    /* we got a uid. let's delete it */
		    if (imap_sendKill(cxn, 
                                      cxn->current_control->data.control->uid)
			    != RET_OK)
			imap_Disconnect(cxn);
		    return;
		} else {
		    d_printf(0, "%s:%d IMAP Received NO response to SEARCH\n",
			     hostPeerName (cxn->myHost), cxn->ident);
		    ReQueue(cxn, &(cxn->imap_controlMsg_q), 
			    cxn->current_control);
		    
		    if (imap_sendClose(cxn) != RET_OK)
			imap_Disconnect(cxn);
		    return;
		}
		break;

	    case IMAP_READING_STORE:

		    if (okno==1) {

			d_printf(1,"%s:%d:IMAP Processed a Cancel fully\n",
				 hostPeerName (cxn->myHost), cxn->ident);

			/* we can delete article now */
			QueueForgetAbout(cxn, cxn->current_control,
					 MSG_SUCCESS);

			cxn->cancel_succeeded++;

			if (imap_sendClose(cxn) != RET_OK)
			    imap_Disconnect(cxn);
			return;

		    } else {

			d_printf(1,"%s:%d:IMAP Store failed\n",
				 hostPeerName (cxn->myHost), cxn->ident);
			ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);

			if (imap_sendClose(cxn) != RET_OK)
			    imap_Disconnect(cxn);
			return;
		    }

		    break;

	    case IMAP_READING_NOOP:
		cxn->imap_state = IMAP_IDLE_AUTHED;
		return;
		break;

	    case IMAP_READING_CLOSE:
		if (!okno) {
		    /* we can't do anything about it */
		    d_printf(1,"%s:%d:IMAP Close failed\n",
			     hostPeerName (cxn->myHost), cxn->ident);
		}

		cxn->imap_state = IMAP_IDLE_AUTHED;
		
		imap_ProcessQueue(cxn);		    
		return;
		break;

	    case IMAP_READING_QUIT:
		
		/* we don't care if the server said OK or NO just
		   that it said something */

		d_printf(1,"%s:%d:IMAP Read quit response\n",
			 hostPeerName (cxn->myHost), cxn->ident);
		
		cxn->imap_state = IMAP_DISCONNECTED;

		DeleteIfDisconnected(cxn);
		break;
		    

	    default:
		d_printf(0,"%s:%d:IMAP I don't understand state %d [%s]\n",
			 hostPeerName (cxn->myHost), cxn->ident,
			 cxn->imap_state,str);
		imap_Disconnect(cxn);
		break;
	    }
	    

    } else {
	d_printf(0,"%s:%d:IMAP tag (%s) doesn't match what we gave (%s). What's up with that??\n",
		 hostPeerName (cxn->myHost), cxn->ident, str, cxn->imap_currentTag);
	imap_Disconnect(cxn);
    }
    
}

/************************** END IMAP reading functions ***************************/

/*************************** LMTP reading functions ****************************/

static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
{
    connection_t *cxn = (connection_t *) d;
    char str[4096];
    Buffer *readBuffers;
    int result;
    int response_code;
    conn_ret ret;
#ifdef HAVE_SASL
    int inlen;
    char *in;
    int outlen;
    const char *out;
    char *inbase64;
    int inbase64len;
    imt_stat status;
    int saslresult;
    sasl_interact_t *client_interact=NULL;
#endif /* HAVE_SASL */

    char *p = bufferBase(b[0]);

    bufferAddNullByte (b[0]) ;

    if (i != IoDone) {
	errno = endPointErrno(e);
	syslog(LOG_ERR, "%s:%d LMTP i/o failed: %m",
	       hostPeerName (cxn->myHost), cxn->ident);

	freeBufferArray (b);
	lmtp_Disconnect(cxn);
	return;
    }

    if (strchr (p, '\n') == NULL)
    {
	/* partial read. expand buffer and retry */

	d_printf(0,"%s:%d:LMTP Partial. retry\n",
		 hostPeerName (cxn->myHost),cxn->ident);
	expandBuffer (b[0], BUFFER_EXPAND_AMOUNT) ;
	readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
	
	if ( !prepareRead (e, readBuffers, lmtp_readCB, cxn, 1) )
	{
	    lmtp_Disconnect(cxn);
        }

	freeBufferArray (b);
	return;
    }

    clearTimer (cxn->lmtp_readBlockedTimerId) ;

    /* Add what we got to our internal read buffer */
    strcat(cxn->lmtp_respBuffer, p);

    bufferSetDataSize( b[0], 0);

    freeBufferArray (b);

 reset:
    /* see if we have a full line */
    ret = GetLine( cxn->lmtp_respBuffer, str, sizeof(str));

    /* get a line */
    if (ret!=RET_OK)
    {
	if (ret!=RET_NO_FULLLINE)
	{
	    /* was a more serious error */
	    d_printf(0,"%s:%d:LMTP Internal error getting line from server\n",
		     hostPeerName (cxn->myHost),cxn->ident);
	    lmtp_Disconnect(cxn);
	    return;	    
	}

	/* set up to receive some more */
	readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
	prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
	return;
    }

    switch (cxn->lmtp_state)
	{

	case LMTP_READING_INTRO:
	    
	    if (ask_code(str)!=220)
	    {
		d_printf(0,"%s:%d:LMTP Initial server msg does not start with 220 (began with %d)\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 ask_code(str));
		lmtp_Disconnect(cxn);
		return;
	    }

	    /* the initial intro could have many lines via
               continuations. see if we need to read more */
	    if (ask_keepgoing(str)==1)
	    {
		goto reset;
	    }

	    result = lmtp_getcapabilities(cxn);

	    if (result != RET_OK)
	    {
		d_printf(0,"%s:%d:LMTP lmtp_getcapabilities() failure\n",
			 hostPeerName (cxn->myHost),cxn->ident);
		lmtp_Disconnect(cxn);
		return;
	    }

	    break;

	case LMTP_READING_LHLO:
	    /* recieve the response(s) */
	    response_code = ask_code(str);
	    
	    if (response_code != 250) /* was none */
	    {
		d_printf(0,"%s:%d:LMTP Response code unexpected (%d)\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 response_code);
		lmtp_Disconnect(cxn);
		return;
	    }

	    /* look for one we know about; ignore all others */
	    if (strncmp(str+4,"8BITMIME",strlen("8BITMIME"))==0)
	    {
		cxn->lmtp_capabilities->Eightbitmime = 1;
	    } else if (strncmp(str+4, "ENHANCEDSTATUSCODES",
			       strlen("ENHANCEDSTATUSCODES"))==0) {
		cxn->lmtp_capabilities->EnhancedStatusCodes = 1;
	    } else if (strncmp(str+4, "AUTH",4)==0) {
		cxn->lmtp_capabilities->saslmechs = xstrdup(str + 4 + 5);
	    } else if (strncmp(str+4,"PIPELINING",strlen("PIPELINING"))==0) {
		cxn->lmtp_capabilities->pipelining = 1;
	    } else {
		/* don't care; ignore */
	    }
	    
	    /* see if this is the last line of the capability */
	    if (ask_keepgoing(str)==1)
	    {
		goto reset;
	    } else {
		/* we require a few capabilities */
		if (!cxn->lmtp_capabilities->pipelining) {
		    d_printf(0,"%s:%d:LMTP We require PIPELINING\n",
			     hostPeerName (cxn->myHost),cxn->ident);
		    
		    lmtp_Disconnect(cxn);
		    return;
		}
#ifdef HAVE_SASL
		if (cxn->lmtp_capabilities->saslmechs) {
		    /* start the authentication */
		    result = lmtp_authenticate(cxn);
		    
		    if (result != RET_OK) {
			d_printf(0,"%s:%d:LMTP lmtp_authenticate() error\n",
			     hostPeerName (cxn->myHost),cxn->ident);
			lmtp_Disconnect(cxn);
			return;
		    }
#else
		if (0) {
		    /* noop */
#endif
		} else {
		    /* either we can't authenticate or the remote server
		       doesn't support it */
		    cxn->lmtp_state = LMTP_AUTHED_IDLE;
		    d_printf(1,"%s:%d:LMTP Even though we can't authenticate"
			     " we're going to try to feed anyway\n",
			     hostPeerName (cxn->myHost),cxn->ident);
		    /* We just assume we don't need to authenticate 
		       (great assumption huh?) */
		    hostRemoteStreams (cxn->myHost, cxn, true) ;

		    cxn->lmtp_timeCon = theTime () ;
		    cxn->timeCon = theTime () ;
		    
		    /* try to send a message if we have one */
		    lmtp_sendmessage(cxn,NULL);
		    return;
		}
	    }
	    break;

#ifdef HAVE_SASL
	case LMTP_READING_STEPAUTH:
	    inlen = 0;
	    status = lmtp_getauthline(str, &in, &inlen);

	    switch (status)
		{

		case STAT_CONT:

		    saslresult=sasl_client_step(cxn->saslconn_lmtp,
						in,
						inlen,
						&client_interact,
						&out,
						&outlen);

		    free(in);

		    /* check if sasl succeeded */
		    if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {
			d_printf(0,"%s:%d:LMTP sasl_client_step(): %s\n",
				 hostPeerName (cxn->myHost),cxn->ident,
				 sasl_errstring(saslresult,NULL,NULL));

			lmtp_Disconnect(cxn);
			return;
		    }

		    /* convert to base64 */
		    inbase64 = xmalloc(outlen*2+10);

		    saslresult = sasl_encode64(out, outlen,
					       inbase64, outlen*2+10, 
					       (unsigned *) &inbase64len);
		    
		    if (saslresult != SASL_OK)
		    {
			d_printf(0,"%s:%d:LMTP sasl_encode64(): %s\n",
				 hostPeerName (cxn->myHost),cxn->ident,
				 sasl_errstring(saslresult,NULL,NULL));

			lmtp_Disconnect(cxn);
			return;
		    }

		    /* add an endline */
		    strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10);

		    /* send to server */
		    result = WriteToWire_lmtpstr(cxn,inbase64, inbase64len+2);

		    if (result != RET_OK)
		    {
			d_printf(0,"%s:%d:LMTP WriteToWire() failure\n",
				 hostPeerName (cxn->myHost),cxn->ident);
			lmtp_Disconnect(cxn);
			return;
		    }

		    cxn->lmtp_state = LMTP_WRITING_STEPAUTH;
		    break;
		    
		case STAT_OK:
		    cxn->lmtp_sleepTimeout = init_reconnect_period ;
      
		    d_printf(0,"%s:%d LMTP authentication succeeded\n",
			     hostPeerName (cxn->myHost), cxn->ident);

		    cxn->lmtp_disconnects=0;

		    hostRemoteStreams (cxn->myHost, cxn, true) ;

		    cxn->lmtp_timeCon = theTime () ;
		    cxn->timeCon = theTime () ;

		    cxn->lmtp_state = LMTP_AUTHED_IDLE;


		    /* try to send a message if we have one */
		    lmtp_sendmessage(cxn,NULL);
		    return;

		    break;

		default:
		    d_printf(0,"%s:%d:LMTP failed authentication\n",
			     hostPeerName (cxn->myHost),cxn->ident);
		    lmtp_Disconnect(cxn);
		    return;
		}
	    break;
#endif /* HAVE_SASL */
		    
	case LMTP_READING_RSET:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }
	    if (ask_code(str) != 250) {
		d_printf(0,"%s:%d:LMTP RSET failed with (%d)\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 ask_code(str));
		lmtp_Disconnect(cxn);
		return;
	    }

	    /* we pipelined so next we recieve the mail from response */
	    cxn->lmtp_state = LMTP_READING_MAILFROM;
	    goto reset;

	case LMTP_READING_MAILFROM:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }
	    if (ask_code(str) != 250) {
		d_printf(0,"%s:%d:LMTP MAILFROM failed with (%d)\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 ask_code(str));
		lmtp_Disconnect(cxn);
		return;
	    }

	    /* we pipelined so next we recieve the rcpt's */
	    cxn->lmtp_state = LMTP_READING_RCPTTO;
	    goto reset;
	    break;

	case LMTP_READING_RCPTTO:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }
	    if (ask_code(str)!=250) {
		d_printf(1,"%s:%d:LMTP RCPT TO failed with (%d) %s\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 ask_code(str), str);

		/* if got a 5xx don't try to send anymore */
		cxn->current_article->trys=100;

		cxn->current_rcpts_issued--;
	    } else {
		cxn->current_rcpts_okayed++;
	    }

	    /* if issued equals number okayed then we're done */
	    if ( cxn->current_rcpts_okayed == cxn->current_rcpts_issued) {
		cxn->lmtp_state = LMTP_READING_DATA;
	    } else {
		/* stay in same state */
	    }
	    goto reset;
	    break;

	case LMTP_READING_DATA:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }
	    if (cxn->current_rcpts_issued == 0) {
		if (cxn->current_article->trys < 100) {
		    d_printf(1, "%s:%d:LMTP None of the rcpts "
			     "were accepted for this message. Re-queueing\n",
			     hostPeerName (cxn->myHost),cxn->ident);
		}

		ReQueue(cxn, &(cxn->lmtp_todeliver_q), cxn->current_article);

		cxn->lmtp_state = LMTP_AUTHED_IDLE;
		lmtp_sendmessage(cxn,NULL);
	    } else {
		if (WriteArticle(cxn, cxn->current_bufs) != RET_OK)
		{
		    d_printf(0, "%s:%d:LMTP Error writing article\n",
			     hostPeerName (cxn->myHost),cxn->ident);
		    lmtp_Disconnect(cxn);
		    return;
		}
		
		cxn->lmtp_state = LMTP_WRITING_CONTENTS;
	    }

	    break;

	case LMTP_READING_CONTENTS:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }

	    /* need 1 response from server for every rcpt */
	    cxn->current_rcpts_issued--;
	    
	    if (ask_code(str) != 250) {
		d_printf(1, "%s:%d:LMTP DATA failed with %d (%s)\n",
			 hostPeerName (cxn->myHost),cxn->ident,
			 ask_code(str), str);
		cxn->current_rcpts_okayed--;
	    }

	    if (cxn->current_rcpts_issued>0) {
		goto reset;
	    }

	    /*
	     * current_rcpts_okayed is number that succeeded
	     *
	     */
	    if (cxn->current_rcpts_okayed == 0) {
		cxn->lmtp_state = LMTP_AUTHED_IDLE;
	    } else {
		cxn->lmtp_state = LMTP_AUTHED_IDLE;
		cxn->lmtp_succeeded++;	    
		d_printf(1, "%s:%d:LMTP Woohoo! message accepted\n",
			 hostPeerName (cxn->myHost),cxn->ident);
	    }

	    /* we can delete article now */
	    QueueForgetAbout(cxn, cxn->current_article, MSG_SUCCESS);

	    /* try to send another if we have one and we're still idle
	     * forgetting the msg might have made us unidle
	     */
	    if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
		lmtp_sendmessage(cxn,NULL);
	    }
	    
	    break;

	case LMTP_READING_NOOP:
	    if (ask_keepgoing(str)) {
		goto reset;
	    }
	    cxn->lmtp_state = LMTP_AUTHED_IDLE;
	    break;

	case LMTP_READING_QUIT:
	    d_printf(1,"%s:%d:LMTP read quit\n",
		     hostPeerName (cxn->myHost),cxn->ident);

	    cxn->lmtp_state = LMTP_DISCONNECTED;	    

	    DeleteIfDisconnected(cxn);
	    break;

	default:

	    d_printf(0,"%s:%d:LMTP Bad state in lmtp_readCB %d\n",
		     hostPeerName (cxn->myHost),cxn->ident,
		     cxn->lmtp_state);
	    lmtp_Disconnect(cxn);
	    return;
	}


}

/*
 * Add a rcpt to:<foo> to the string
 *
 */

static void addrcpt(char *newrcpt, int newrcptlen, char **out, int *outalloc)
{
    int size = strlen(*out);
    int fsize = size;
    int newsize = size + 9+strlen(deliver_rcpt_to)+newrcptlen+3;
    char c;

    /* see if we need to grow the string */
    if (newsize > *outalloc) {
	(*outalloc) = newsize;
	(*out) = xrealloc(*out, *outalloc);
    }

    strlcpy((*out) + size,"RCPT TO:<", newsize - size);
    size += 9;

    c = newrcpt[newrcptlen];
    newrcpt[newrcptlen] = '\0';
    size += snprintf((*out) + size, newsize - size, deliver_rcpt_to, newrcpt);
    newrcpt[newrcptlen] = c;

    strlcpy((*out) + size, ">\r\n", newsize - size);

    /* has embedded '\n' */
    d_printf(2, "Attempting to send to: %s", (*out) + fsize);
}

/*
 * Takes the newsgroups header value and makes it into a list of RCPT TO:'s we can send over the wire
 *
 *  in     - newsgroups header start
 *  in_end - end of newsgroups header
 *  num    - number of rcpt's we created
 */

static char *ConvertRcptList(char *in, char *in_end, int *num)
{
    int retalloc = 400;
    char *ret = xmalloc(retalloc);
    char *str = in;
    char *laststart = in;

    (*num) = 0;

    /* start it off empty */     
    strlcpy(ret, "", retalloc);
    
    while ( str !=  in_end)
    {
	if ((*str) == ',')
	{
	    /* eliminate leading whitespace */
	    while (((*laststart) ==' ') || ((*laststart)=='\t'))
	    {
		laststart++;
	    }

#ifndef SMTPMODE
	    addrcpt(laststart, str - laststart, &ret, &retalloc);
	    (*num)++;
#endif /* SMTPMODE */
	    laststart = str+1;
	}

	str++;
    }

    if (laststart<str)
    {
	addrcpt(laststart, str - laststart, &ret, &retalloc);
	(*num)++;
    }

    return ret;
}

static void addto(char *newrcpt, int newrcptlen, const char *sep,
		  char **out, int *outalloc)
{
    int size = strlen(*out);
    int newsize = size + strlen(sep)+1+strlen(deliver_to_header)+newrcptlen+1;
    char c;

    /* see if we need to grow the string */
    if (newsize > *outalloc) {
	(*outalloc) = newsize;
	(*out) = xrealloc(*out, *outalloc);
    }

    size += snprintf((*out) + size, newsize - size, "%s<", sep);

    c = newrcpt[newrcptlen];
    newrcpt[newrcptlen] = '\0';
    size += snprintf((*out) + size, newsize - size, deliver_to_header,newrcpt);
    newrcpt[newrcptlen] = c;

    strlcpy((*out) + size, ">", newsize - size);
}

/*
 * Takes the newsgroups header value and makes it into a To: header
 *
 *  in     - newsgroups header start
 *  in_end - end of newsgroups header
 */

static char *BuildToHeader(char *in, char *in_end)
{
    int retalloc = 400;
    char *ret = xmalloc(retalloc);
    char *str = in;
    char *laststart = in;
    const char *sep = "";

    /* start it off with the header name */     
    strlcpy(ret,"To: ", retalloc);
    
    while ( str !=  in_end)
    {
	if ((*str) == ',')
	{
	    /* eliminate leading whitespace */
	    while (((*laststart) ==' ') || ((*laststart)=='\t'))
	    {
		laststart++;
	    }

	    addto(laststart, str - laststart, sep, &ret, &retalloc);
	    laststart = str+1;

	    /* separate multiple addresses with a comma */
	    sep = ", ";
	}

	str++;
    }

    if (laststart<str)
    {
	addto(laststart, str - laststart, sep, &ret, &retalloc);
    }

    /* terminate the header */
    strlcat(ret, "\n\r", retalloc);
    return ret;
}

/*************************** END LMTP reading functions ****************************/



/*
 * Process the control message queue. If we run out ask the host for more.
 *
 *  cxn       - connection object
 */

static void imap_ProcessQueue(connection_t *cxn)
{
    article_queue_t *item;
    int result;

 retry:

    /* pull an article off the queue */
    result = PopFromQueue(&(cxn->imap_controlMsg_q), &item);

    if (result==RET_QUEUE_EMPTY)
    {
	if (cxn->issue_quit)
	{
	    imap_sendQuit(cxn);
	    return;
	}

	cxn->imap_state = IMAP_IDLE_AUTHED;

	/* now we wait for articles from our Host, or we have some
	   articles already. On infrequently used connections, the
	   network link is torn down and rebuilt as needed. So we may
	   be rebuilding the connection here in which case we have an
	   article to send. */

	/* make sure imap has _lots_ of space too */
	if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) && 
	    (QueueItems(&(cxn->imap_controlMsg_q)) == 0))
	{
	    if (hostGimmeArticle (cxn->myHost,cxn)==true)
		goto retry;
	}

	return;
    }

    cxn->current_control = item;

    switch (item->type)
	{
	case CREATE_FOLDER:
	    imap_CreateGroup(cxn, item->data.control->folder);
	    break;

	case CANCEL_MSG:
	    imap_CancelMsg(cxn, item->data.control->folder);
	    break;

	case DELETE_FOLDER:
	    imap_DeleteGroup(cxn, item->data.control->folder); 
	    break;
	default:
	    break;
	}

    return;
}



/*
 *
 * Pulls a message off the queue and trys to start sending it. If the
 * message is a control message put it in the control queue and grab
 * another message. If the message doesn't exist on disk or something
 * is wrong with it tell the host and try again. If we run out of
 * messages to get tell the host we want more
 *
 * cxn       - connection object
 * justadded - the article that was just added to the queue
 */

static void lmtp_sendmessage(connection_t *cxn, Article justadded)
{
    bool res;
    conn_ret result;
    char *p;
    Buffer *bufs;
    char *control_header = NULL;
    char *control_header_end = NULL;

    article_queue_t *item;
    char *rcpt_list, *rcpt_list_end;

    /* retry point */
 retry:

    /* pull an article off the queue */
    result = PopFromQueue(&(cxn->lmtp_todeliver_q), &item);

    if (result==RET_QUEUE_EMPTY)
    {
	if (cxn->issue_quit) {
	    lmtp_IssueQuit(cxn);
	    return;
	}
	/* now we wait for articles from our Host, or we have some
	   articles already. On infrequently used connections, the
	   network link is torn down and rebuilt as needed. So we may
	   be rebuilding the connection here in which case we have an
	   article to send. */

	/* make sure imap has space too */
	d_printf(1,"%s:%d stalled waiting for articles\n",
		 hostPeerName (cxn->myHost),cxn->ident);
	if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) && 
	    (QueueItems(&(cxn->imap_controlMsg_q)) == 0)) {
	    if (hostGimmeArticle (cxn->myHost,cxn)==true)
		goto retry;
	}

	return;
    }

    /* make sure contents ok; this also should load it into memory */
    res = artContentsOk (item->data.article);
    if (res==false)
    {
	if (justadded == item->data.article) {
	    ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
	    return;
	} else {
	    /* tell to reject taking this message */
	    QueueForgetAbout(cxn,item, MSG_MISSING);
	}

	goto retry;
    }

    /* Check if it's a control message */
    bufs = artGetNntpBuffers (item->data.article);
    if (bufs == NULL)
    {
	/* tell to reject taking this message */
	QueueForgetAbout(cxn,item, MSG_MISSING);
	goto retry;
    }

    result = FindHeader(bufs, "Control", &control_header, &control_header_end);
    if (result == RET_OK) {
	result = AddControlMsg(cxn, item->data.article, bufs, 
			       control_header,control_header_end, 1);
	if (result != RET_OK) {
	    d_printf(1,"%s:%d Error adding to [imap] control queue\n",
		     hostPeerName (cxn->myHost),cxn->ident) ;
	    ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
	    return;
	}

	switch(cxn->imap_state) {
	case IMAP_IDLE_AUTHED:
	    /* we're idle. let's process the queue */
	    imap_ProcessQueue(cxn);
	    break;
	case IMAP_DISCONNECTED:
	case IMAP_WAITING:
	    /* Let's connect. Once we're connected we can 
	       worry about the message */
	    if (cxn->imap_sleepTimerId == 0) {
		if (imap_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,0);
	    }
	    break;
	default:
	    /* we're doing something right now */
	    break;
	}
	
	/* all we did was add a control message. 
	   we still want to get an lmtp message */
	goto retry;
    }

    if (cxn->current_bufs != NULL) {
	/*	freeBufferArray(cxn->current_bufs); */
	cxn->current_bufs = NULL;
    }
    cxn->current_bufs = bufs;
    cxn->current_article = item;
    
    /* we make use of pipelining here
       send:
         rset
         mail from
	 rcpt to
	 data
    */

    /* find out who it's going to */
    result = FindHeader(cxn->current_bufs, "Newsgroups", 
			&rcpt_list, &rcpt_list_end);

    if ((result != RET_OK) || (rcpt_list == NULL)) {
	d_printf(1,"%s:%d Didn't find Newsgroups header\n",
		 hostPeerName (cxn->myHost),cxn->ident) ;
	QueueForgetAbout(cxn, cxn->current_article, MSG_FAIL_DELIVER);
	goto retry;	
    }

    /* free's original rcpt_list */
    rcpt_list = ConvertRcptList(rcpt_list, rcpt_list_end, 
				&cxn->current_rcpts_issued);
    cxn->current_rcpts_okayed = 0;
    
    if(mailfrom_name == NULL)
	mailfrom_name = xstrdup("");
    p = concat("RSET\r\n"
               "MAIL FROM:<", mailfrom_name, ">\r\n",
               rcpt_list,
               "DATA\r\n", (char *) 0);

    cxn->lmtp_state = LMTP_WRITING_UPTODATA;
    result = WriteToWire_lmtpstr(cxn, p, strlen(p));

    if (result != RET_OK) {
	d_printf(0,"%s:%d failed trying to write\n",
		 hostPeerName (cxn->myHost),cxn->ident) ;
	lmtp_Disconnect(cxn);
	return;
    }

    /* prepend To: header to article */
    if (deliver_to_header) {
	char *to_list, *to_list_end;
	int i, len;

	result = FindHeader(cxn->current_bufs, "Followup-To", 
			    &to_list, &to_list_end);

	if ((result != RET_OK) || (to_list == NULL)) {
	    result = FindHeader(cxn->current_bufs, "Newsgroups", 
				&to_list, &to_list_end);
	}

	/* free's original to_list */
	to_list = BuildToHeader(to_list, to_list_end);

	len = bufferArrayLen(cxn->current_bufs);
	cxn->current_bufs = xrealloc(cxn->current_bufs,
                                     sizeof(Buffer) * (len+2));
	cxn->current_bufs[len+1] = NULL;

	for (i = len; i > 0; i--) {
	    cxn->current_bufs[i] = cxn->current_bufs[i-1];
	}

	cxn->current_bufs[0] = newBufferByCharP(to_list, strlen(to_list+1),
						strlen(to_list));
    }

    hostArticleOffered (cxn->myHost, cxn);
}

/*
 * Called by the EndPoint class when the timer goes off
 */
static void dosomethingTimeoutCbk (TimeoutId id, void *data)
{
  Connection cxn = (Connection) data ;

  ASSERT (id == cxn->dosomethingTimerId) ;

  show_stats(cxn);

  /* we're disconnected but there are things to send */
  if ((cxn->lmtp_state == LMTP_DISCONNECTED) && 
      (cxn->lmtp_sleepTimerId == 0) &&
      QueueItems(&(cxn->lmtp_todeliver_q)) > 0)
  {
      if (lmtp_Connect(cxn) != RET_OK)
	  prepareReopenCbk(cxn, 1);
  }

  if ((cxn->imap_state == IMAP_DISCONNECTED) &&
      (cxn->imap_sleepTimerId == 0) &&
      (QueueItems(&(cxn->imap_controlMsg_q)) > 0))
  {
      if (imap_Connect(cxn) != RET_OK)
	  prepareReopenCbk(cxn, 0);
  }


  /* if we're idle and there are items to send let's send them */
  if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) && 
      QueueItems(&(cxn->lmtp_todeliver_q)) > 0) {
      lmtp_sendmessage(cxn,NULL);
  } else if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
      lmtp_noop(cxn);
  }

  if ((cxn->imap_state == IMAP_IDLE_AUTHED) &&
      (QueueItems(&(cxn->imap_controlMsg_q)) > 0)) {
      imap_ProcessQueue(cxn);
  } else if (cxn->imap_state == IMAP_IDLE_AUTHED) {
      imap_noop(cxn);
  }

  /* set up the timer. */
  clearTimer (cxn->dosomethingTimerId) ;

  cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk, 
					  cxn->dosomethingTimeout, cxn);  
}

/* Give all articles in the queue back to the host. We're probably
 * going to exit soon.
 * */

static void DeferAllArticles(connection_t *cxn, Q_t *q)
{
    article_queue_t *cur;
    conn_ret ret;
    
    while (1)
    {
	ret = PopFromQueue(q, &cur);
	if (ret == RET_QUEUE_EMPTY) return;

	if (ret == RET_OK)
        {
	    QueueForgetAbout(cxn, cur, MSG_GIVE_BACK);
	} else {
	    d_printf(0,"%s:%d Error emptying queue (deffering all articles)\n",
		     hostPeerName (cxn->myHost),cxn->ident);
	    return;
	}
    }
}

/*
 * Does the actual deletion of a connection and all its private data.
 */
static void delConnection (Connection cxn)
{
  bool shutDown;
  Connection c, q;

  if (cxn == NULL)
    return ;

  d_printf (1,"Deleting connection: %s:%d\n",
           hostPeerName (cxn->myHost),cxn->ident) ;

  for (c = gCxnList, q = NULL ; c != NULL ; q = c, c = c->next)
    if (c == cxn)
      {
        if (gCxnList == c)
          gCxnList = gCxnList->next ;
        else
          q->next = c->next ;
        break ;
      }
  
  ASSERT (c != NULL) ;
 
  if (cxn->lmtp_endpoint != NULL)
    delEndPoint (cxn->lmtp_endpoint) ;
  if (cxn->imap_endpoint != NULL)
    delEndPoint (cxn->imap_endpoint) ;

  delBuffer (cxn->imap_rBuffer) ;
  delBuffer (cxn->lmtp_rBuffer) ;

  /* tell the Host we're outta here. */
  shutDown = hostCxnGone (cxn->myHost, cxn) ;

  cxn->ident = 0 ;
  cxn->timeCon = 0 ;

  free (cxn->ServerName) ;

  clearTimer (cxn->imap_readBlockedTimerId) ;
  clearTimer (cxn->imap_writeBlockedTimerId) ;
  clearTimer (cxn->lmtp_readBlockedTimerId) ;
  clearTimer (cxn->lmtp_writeBlockedTimerId) ;

  clearTimer (cxn->imap_sleepTimerId);
  cxn->imap_sleepTimerId = 0;  
  clearTimer (cxn->lmtp_sleepTimerId);  
  cxn->lmtp_sleepTimerId = 0;

  clearTimer (cxn->dosomethingTimerId);

  free (cxn->imap_respBuffer);
  free (cxn->lmtp_respBuffer);

  free (cxn) ;

  if (shutDown)
    {
      /* exit program if that was the last connexion for the last host */
      /* XXX what about if there are ever multiple listeners?
	 XXX    this will be executed if all hosts on only one of the 
	 XXX    listeners have gone */
      time_t now = theTime () ;
      char dateString [30] ;

      strlcpy (dateString,ctime (&now),sizeof (dateString)) ;
      dateString [24] = '\0' ;

      notice ("ME finishing at %s", dateString) ;

      exit (0) ;
    }
}


/******************** PUBLIC FUNCTIONS ****************************/



  /*
   * Create a new Connection.
   * 
   * HOST is the host object we're owned by.
   * IDENT is an identifier to be added to syslog entries so we can tell
   *    what's happening on different connections to the same peer.
   * IPNAME is the name (or ip address) of the remote)
   * MAXTOUT is the maximum amount of time to wait for a response before
   *    considering the remote host dead.
   * PORTNUM is the portnum to contact on the remote end.
   * RESPTIMEOUT is the amount of time to wait for a response from a remote
   *    before considering the connection dead.
   * CLOSEPERIOD is the number of seconds after connecting that the
   *     connections should be closed down and reinitialized (due to problems
   *     with old NNTP servers that hold history files open. Value of 0 means
   *     no close down.
   */

Connection newConnection (Host host,
                          unsigned int ident,
                          const char *ipname,
                          unsigned int artTout UNUSED,
                          unsigned int portNum UNUSED,
                          unsigned int respTimeout,
                          unsigned int closePeriod UNUSED,
                          double lowPassLow UNUSED,
                          double lowPassHigh UNUSED,
			  double lowPassFilter UNUSED)
{
    Connection cxn;
    /* check arguments */

    /* allocate connection structure */
    cxn = xcalloc (1, sizeof(connection_t)) ;

    cxn->ident = ident ;
    cxn->ServerName = xstrdup (ipname) ;
    cxn->myHost = host ;

    /* setup mailfrom user */
    if (gethostname(hostname, MAXHOSTNAMELEN)!=0)
    {
	d_printf(0,"%s gethostname failed\n",ipname);
	return NULL;
    }


    mailfrom_name = concat("news@", hostname, (char *) 0);

    cxn->next = gCxnList ;
    gCxnList = cxn ;
    gCxnCount++ ;

    /* init stuff */
    Initialize(cxn, respTimeout);

    return cxn;
}


/* Causes the Connection to build the network connection. */
bool cxnConnect (Connection cxn)
{
    /* make the lmtp connection */
    if (lmtp_Connect(cxn) != RET_OK) return false;

    if (imap_Connect(cxn) != RET_OK) return false;

    return true;
}


static void QuitIfIdle(Connection cxn)
{
    if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) && 
	(QueueItems(&(cxn->lmtp_todeliver_q))<=0)) {
	lmtp_IssueQuit(cxn);
    }
    if ((cxn->imap_state == IMAP_IDLE_AUTHED) && 
	(QueueItems(&(cxn->imap_controlMsg_q))<=0)) {
	imap_sendQuit(cxn);
    }
}

static void DeleteIfDisconnected(Connection cxn)
{
    /* we want to shut everything down. if both connections disconnected now we can */
    if ((cxn->issue_quit >= 1) &&
	(cxn->lmtp_state == LMTP_DISCONNECTED) &&
	(cxn->imap_state == IMAP_DISCONNECTED))
    {

	switch (cxn->issue_quit)
	{
	case 1:
	    if (cxn->lmtp_state == LMTP_DISCONNECTED)
	    {
		cxn->lmtp_state = LMTP_WAITING;
		cxn->imap_state = IMAP_WAITING;
		cxn->issue_quit = 0;
		hostCxnWaiting (cxn->myHost,cxn) ;  /* tell our Host we're waiting */
	    }
	    break;
	case 2:
	    if (cxn->lmtp_state == LMTP_DISCONNECTED)
	    {
		cxn->issue_quit = 0;
		
		if (imap_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,0);
		if (lmtp_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,1);
	    }
	    break;
	case 3:
	    if (cxn->lmtp_state == LMTP_DISCONNECTED)
	    {
		hostCxnDead (cxn->myHost,cxn) ;
		delConnection(cxn);
	    }
	    break;
	    
	}	
    }
}

  /* puts the connection into the wait state (i.e. waits for an article
     before initiating a connect). Can only be called right after
     newConnection returns, or while the Connection is in the (internal)
     Sleeping state. */
void cxnWait (Connection cxn)
{
    cxn->issue_quit = 1;

    QuitIfIdle(cxn);
}

  /* The Connection will disconnect as if cxnDisconnect were called and then
     it automatically reconnects to the remote. */
void cxnFlush (Connection cxn)
{
    cxn->issue_quit = 2;

    QuitIfIdle(cxn);
}



  /* The Connection sends remaining articles, then issues a QUIT and then
     deletes itself */
void cxnClose (Connection cxn)
{
    d_printf(0,"%s:%d Closing cxn\n",hostPeerName (cxn->myHost), cxn->ident);
    cxn->issue_quit = 3;

    QuitIfIdle(cxn);

    DeleteIfDisconnected(cxn);
}

  /* The Connection drops all queueed articles, then issues a QUIT and then
     deletes itself */
void cxnTerminate (Connection cxn)
{
    d_printf(0,"%s:%d Terminate\n",hostPeerName (cxn->myHost), cxn->ident);

    cxn->issue_quit = 3;    

    /* give any articles back to host in both queues */
    DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
    DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));

    QuitIfIdle(cxn);
}

  /* Blow away the connection gracelessly and immedately clean up */
void cxnNuke (Connection cxn)
{
    d_printf(0,"%s:%d Nuking connection\n",cxn->ServerName, cxn->ident);

    cxn->issue_quit = 4;

    /* give any articles back to host in both queues */
    DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
    DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));

    imap_Disconnect(cxn);
    lmtp_Disconnect(cxn);

    hostCxnDead (cxn->myHost,cxn);    
    delConnection(cxn);
}

/*
 * must
 *   true  - must queue article. Don't try sending
 *   false - queue of article may fail. Try sending
 *
 * Always adds to lmtp queue even if control message
 *
 */

static bool ProcessArticle(Connection cxn, Article art, bool must)
{
    conn_ret result;

    /* Don't accept any articles when we're closing down the connection */
    if (cxn->issue_quit > 1) {
	return false;
    }

    /* if it's a regular message let's add it to the queue */
    result = AddToQueue(&(cxn->lmtp_todeliver_q), art, DELIVER,1,must);

    if (result == RET_EXCEEDS_SIZE) {
	return false;
    }

    if (result != RET_OK)
    {
	d_printf(0,"%s:%d Error adding to delivery queue\n",
		 hostPeerName (cxn->myHost), cxn->ident);
	return must;
    }

    if (must == true) return true;

    switch (cxn->lmtp_state)
	{
	case LMTP_WAITING:
	case LMTP_DISCONNECTED:	    
	    if (cxn->lmtp_sleepTimerId == 0)
		if (lmtp_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,1);
	    break;
	    
	case LMTP_AUTHED_IDLE:
	    lmtp_sendmessage(cxn,art);
	    break;
	default:
	    /* currently doing something */
	    break;
	}
    
    return true;
}

  /* Tells the Connection to take the article and handle its
     transmission. If it can't (due to queue size or whatever), then the
     function returns false. The connection assumes ownership of the
     article if it accepts it (returns true). */
bool cxnTakeArticle (Connection cxn, Article art)
{
    /* if we're closing down always refuse */
    if (cxn->issue_quit == 1) return false;

    return ProcessArticle (cxn,art,false);
}

  /* Tell the Connection to take the article (if it can) for later
     processing. Assumes ownership of it if it takes it. */
bool cxnQueueArticle (Connection cxn, Article art)
{
    return ProcessArticle (cxn,art,true);
}

/* generate a syslog message for the connections activity. Called by Host. */
void cxnLogStats (Connection cxn, bool final)
{
  const char *peerName ;
  time_t now = theTime() ;
  int total, good, bad;

  ASSERT (cxn != NULL) ;

  peerName = hostPeerName (cxn->myHost) ;

  total = cxn->lmtp_succeeded + cxn->lmtp_failed;
  total += cxn->cancel_succeeded + cxn->cancel_failed;
  total += cxn->create_succeeded + cxn->create_failed;
  total += cxn->remove_succeeded + cxn->remove_failed;

  good = cxn->lmtp_succeeded;
  good += cxn->cancel_succeeded;
  good += cxn->create_succeeded;
  good += cxn->remove_succeeded;

  bad = cxn->lmtp_failed;
  bad += cxn->cancel_failed;
  bad += cxn->create_failed;
  bad += cxn->remove_failed;
  notice ("%s:%d %s seconds %ld accepted %d refused %d rejected %d",
          peerName, cxn->ident, (final ? "final" : "checkpoint"),
          (long) (now - cxn->timeCon), total, 0, bad);
  show_stats(cxn);

  if (final) {
      cxn->lmtp_succeeded   = 0;
      cxn->lmtp_failed     = 0;
      cxn->cancel_succeeded = 0;
      cxn->cancel_failed   = 0;
      cxn->create_succeeded = 0;
      cxn->create_failed   = 0;
      cxn->remove_succeeded = 0;
      cxn->remove_failed   = 0;

      if (cxn->timeCon > 0)
        cxn->timeCon = theTime() ;
    }

}

  /* return the number of articles the connection can be given. This lets
     the host shovel in as many as possible. May be zero. */
size_t cxnQueueSpace (Connection cxn)
{
    int lmtpsize;
    int imapsize;

    lmtpsize = QueueSpace(&(cxn->lmtp_todeliver_q));
    imapsize = QueueSpace(&(cxn->imap_controlMsg_q));

    if (lmtpsize >=1) lmtpsize--;
    if (imapsize >=1) imapsize--;

    d_printf(1,"%s:%d Q Space lmtp size = %d state = %d\n",
	     hostPeerName (cxn->myHost), cxn->ident,
	     lmtpsize,cxn->lmtp_state);
    d_printf(1,"%s:%d Q Space imap size = %d state = %d\n",
	     hostPeerName (cxn->myHost), cxn->ident,
	     imapsize,cxn->imap_state); 

    /* return the smaller of our 2 queues */
    if (lmtpsize < imapsize)
	return lmtpsize;
    else
	return imapsize;
}

  /* adjust the mode no-CHECK filter values */
void cxnSetCheckThresholds (Connection cxn,
			    double lowFilter UNUSED,
			    double highFilter UNUSED,
			    double lowPassFilter UNUSED)
{
    d_printf(1,"%s:%d Threshold change. This means nothing to me\n",
	     hostPeerName (cxn->myHost), cxn->ident);
}

/* print some debugging info. */
void gPrintCxnInfo (FILE *fp, unsigned int indentAmt)
{
  char indent [INDENT_BUFFER_SIZE] ;
  unsigned int i ;
  Connection cxn ;

  for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
    indent [i] = ' ' ;
  indent [i] = '\0' ;

  fprintf (fp,"%sGlobal Connection list : (count %d) {\n",
           indent,gCxnCount) ;
  for (cxn = gCxnList ; cxn != NULL ; cxn = cxn->next)
    printCxnInfo (cxn,fp,indentAmt + INDENT_INCR) ;
  fprintf (fp,"%s}\n",indent) ;
}

void printCxnInfo (Connection cxn, FILE *fp, unsigned int indentAmt)
{
  char indent [INDENT_BUFFER_SIZE] ;
  unsigned int i ;
  article_queue_t *artH ;

  for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
    indent [i] = ' ' ;
  indent [i] = '\0' ;

  fprintf (fp,"%sConnection : %p {\n",indent, (void *) cxn) ;
  fprintf (fp,"%s    host : %p\n",indent, (void *) cxn->myHost) ;
  fprintf (fp,"%s    endpoint (imap): %p\n",indent, (void *) cxn->imap_endpoint) ;
  fprintf (fp,"%s    endpoint (lmtp): %p\n",indent, (void *) cxn->lmtp_endpoint) ;
  fprintf (fp,"%s    state (imap) : %s\n",indent, imap_stateToString (cxn->imap_state)) ;
  fprintf (fp,"%s    state (lmtp) : %s\n",indent, lmtp_stateToString (cxn->lmtp_state)) ;
  fprintf (fp,"%s    ident : %d\n",indent,cxn->ident) ;
  fprintf (fp,"%s    ip-name (imap): %s\n", indent, cxn->ServerName) ;
  fprintf (fp,"%s    ip-name (lmtp): %s\n", indent, cxn->ServerName) ;
  fprintf (fp,"%s    port-number (imap) : %d\n",indent,cxn->imap_port) ;
  fprintf (fp,"%s    port-number (lmtp) : %d\n",indent,cxn->lmtp_port) ;

  fprintf (fp,"%s    Issuing Quit : %d\n",indent, cxn->issue_quit) ;

  fprintf (fp,"%s    time-connected (imap) : %ld\n",indent,(long) cxn->imap_timeCon) ;
  fprintf (fp,"%s    time-connected (lmtp) : %ld\n",indent,(long) cxn->lmtp_timeCon) ;
  fprintf (fp,"%s    articles from INN : %d\n",indent,
	   cxn->lmtp_succeeded+
	   cxn->lmtp_failed+
	   cxn->cancel_succeeded+
	   cxn->cancel_failed+
	   cxn->create_succeeded+
	   cxn->create_failed+
	   cxn->remove_succeeded+
	   cxn->remove_failed+
	   QueueSpace(&(cxn->lmtp_todeliver_q))+
	   QueueSpace(&(cxn->imap_controlMsg_q))
	   );
  fprintf(fp,"%s    LMTP STATS: yes: %d no: %d\n",indent, 
	  cxn->lmtp_succeeded, cxn->lmtp_failed);
  fprintf(fp,"%s    control:    yes: %d no: %d\n",indent, 
	  cxn->cancel_succeeded, cxn->cancel_failed);
  fprintf(fp,"%s    create:     yes: %d no: %d\n",indent, 
	  cxn->create_succeeded, cxn->create_failed);
  fprintf(fp,"%s    remove:     yes: %d no: %d\n",indent, 
	  cxn->remove_succeeded, cxn->remove_failed);

  fprintf (fp,"%s    response-timeout : %d\n",indent,cxn->imap_readTimeout) ;
  fprintf (fp,"%s    response-callback : %d\n",indent,cxn->imap_readBlockedTimerId) ;

  fprintf (fp,"%s    write-timeout : %d\n",indent,cxn->imap_writeTimeout) ;
  fprintf (fp,"%s    write-callback : %d\n",indent,cxn->imap_writeBlockedTimerId) ;

  fprintf (fp,"%s    reopen wait : %d\n",indent,cxn->imap_sleepTimeout) ;
  fprintf (fp,"%s    reopen id : %d\n",indent,cxn->imap_sleepTimerId) ;

  fprintf (fp,"%s    IMAP queue {\n",indent) ;
  for (artH = cxn->imap_controlMsg_q.head; artH != NULL ; artH = artH->next)
    printArticleInfo (artH->data.article,fp,indentAmt + INDENT_INCR) ;
  fprintf (fp,"%s    }\n",indent) ;

  fprintf (fp,"%s    LMTP queue {\n",indent) ;
  for (artH = cxn->lmtp_todeliver_q.head ; artH != NULL ; artH = artH->next)
    printArticleInfo (artH->data.control->article,fp,indentAmt + INDENT_INCR) ;
  fprintf (fp,"%s    }\n",indent) ;

  fprintf (fp,"%s}\n",indent) ;
}

/* config file load callback */
int cxnConfigLoadCbk (void *data UNUSED)
{
  long iv ;
  int rval = 1 ;
  FILE *fp = (FILE *) data ;

  if (getInteger (topScope,"max-reconnect-time",&iv,NO_INHERIT))
    {
      if (iv < 1)
        {
          rval = 0 ;
          logOrPrint (LOG_ERR,fp,
                      "ME config: value of %s (%ld) in %s cannot be less"
                      " than 1. Using %ld", "max-reconnect-time",
                      iv,"global scope",(long) MAX_RECON_PER);
          iv = MAX_RECON_PER ;
        }
    }
  else
    iv = MAX_RECON_PER ;
  max_reconnect_period = (unsigned int) iv ;

  if (getInteger (topScope,"initial-reconnect-time",&iv,NO_INHERIT))
    {
      if (iv < 1)
        {
          rval = 0 ;
          logOrPrint (LOG_ERR,fp,
                      "ME config: value of %s (%ld) in %s cannot be less"
                      " than 1. Using %ld", "initial-reconnect-time",
                      iv,"global scope",(long)INIT_RECON_PER);
          iv = INIT_RECON_PER ;
        }
    }
  else
    iv = INIT_RECON_PER ;
  init_reconnect_period = (unsigned int) iv ;

  return rval ;
}

/* check connection state is in cxnWaitingS, cxnConnectingS or cxnIdleS */
bool cxnCheckstate (Connection cxn)
{
    d_printf(5, "%s:%d Being asked to check state\n",
	     hostPeerName (cxn->myHost), cxn->ident);

    /* return false if either connection is doing something */
    if (cxn->imap_state > IMAP_IDLE_AUTHED) return false;
    if (cxn->lmtp_state > LMTP_AUTHED_IDLE) return false;

    return true;
}
