/* lmtpengine.c: LMTP protocol engine
 * $Id: lmtpengine.c,v 1.89 2003/06/25 03:34:01 ken3 Exp $
 *
 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any other legal
 *    details, please contact  
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <com_err.h>
#include <errno.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/wait.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>

#include "assert.h"
#include "util.h"
#include "auth.h"
#include "prot.h"
#include "rfc822date.h"
#include "imapconf.h"
#include "iptostring.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mupdate_err.h"
#include "xmalloc.h"
#include "version.h"

#include "lmtpengine.h"
#include "lmtpstats.h"
#include "tls.h"
#include "telemetry.h"

#define RCPT_GROW 30

/* data per message */
struct Header {
    char *name;
    int ncontents;
    char *contents[1];
};

struct address_data {
    char *user;
    char *all;
    int ignorequota;
    int status;
};

struct clientdata {
    struct protstream *pin;
    struct protstream *pout;
    int fd;

    char clienthost[250];
    char lhlo_param[250];

    sasl_conn_t *conn;

#ifdef HAVE_SSL
    SSL *tls_conn;
#endif /* HAVE_SSL */
    int starttls_done;
};

/* defined in lmtpd.c or lmtpproxyd.c */
extern int deliver_logfd;

extern int saslserver(sasl_conn_t *conn, const char *mech,
		      const char *init_resp, const char *continuation,
		      struct protstream *pin, struct protstream *pout,
		      int *sasl_result, char **success_data);

/* Enable the resetting of a sasl_conn_t */
static int reset_saslconn(sasl_conn_t **conn);

static struct 
{
    char *ipremoteport;
    char *iplocalport;
    sasl_ssf_t ssf;
    char *authid;
} saslprops = {NULL,NULL,0,NULL};

#ifdef USING_SNMPGEN
/* round to nearest 1024 bytes and return number of Kbytes.
 used for SNMP updates. */
static int roundToK(int x)
{
    double rd = (x*1.0)/1024.0;
    int ri = x/1024;
    
    if (rd-ri < 0.5)
	return ri;
    else
	return ri+1;    
}
#else
#define roundToK(x)
#endif /* USING_SNMPGEN */

static void send_lmtp_error(struct protstream *pout, int r)
{
    switch (r) {
    case 0:
	prot_printf(pout, "250 2.1.5 Ok\r\n");
	break;

    case IMAP_IOERROR:
	prot_printf(pout, "451 4.3.0 System I/O error\r\n");
	break;

    case IMAP_SERVER_UNAVAILABLE:
    case MUPDATE_NOCONN:
    case MUPDATE_NOAUTH:
    case MUPDATE_TIMEOUT:
    case MUPDATE_PROTOCOL_ERROR:
	prot_printf(pout, "451 4.4.3 Remote server unavailable\r\n");
	break;

    case IMAP_NOSPACE:
	prot_printf(pout, "451 4.3.1 cannot create file: out of space\r\n");
	break;

    case IMAP_AGAIN:
	prot_printf(pout, "451 4.3.0 transient system error\r\n");
	break;

    case IMAP_PERMISSION_DENIED:
	if (LMTP_LONG_ERROR_MSGS) {
	    prot_printf(pout, 
"550-You do not have permission to post a message to this mailbox.\r\n"
"550-Please contact the owner of this mailbox in order to submit\r\n"
"550-your message, or %s if you believe you\r\n"
"550-received this message in error.\r\n"
"550 5.7.1 Permission denied\r\n", 
			POSTMASTER);
	} else {
	    prot_printf(pout, "550 5.7.1 Permission denied\r\n");
	}
	break;

    case IMAP_QUOTA_EXCEEDED:
	if(config_getswitch("lmtp_overquota_perm_failure",0)) {
	    /* Not Default - Perm Failure */
	    prot_printf(pout, "552 5.2.2 Over quota\r\n");
	} else {
	    /* Default - Temp Failure */
	    prot_printf(pout, "452 4.2.2 Over quota\r\n");
	}
	break;

    case IMAP_MAILBOX_BADFORMAT:
    case IMAP_MAILBOX_NOTSUPPORTED:
	prot_printf(pout, "451 4.2.0 Mailbox has an invalid format\r\n");
	break;

    case IMAP_MAILBOX_MOVED:
	prot_printf(pout, "451 4.2.1 Mailbox Moved\r\n");
	break;

    case IMAP_MESSAGE_CONTAINSNULL:
	prot_printf(pout, "554 5.6.0 Message contains NUL characters\r\n");
	break;

    case IMAP_MESSAGE_CONTAINSNL:
	prot_printf(pout, "554 5.6.0 Message contains bare newlines\r\n");
	break;

    case IMAP_MESSAGE_CONTAINS8BIT:
	prot_printf(pout, "554 5.6.0 Message contains non-ASCII characters in headers\r\n");
	break;

    case IMAP_MESSAGE_BADHEADER:
	prot_printf(pout, "554 5.6.0 Message contains invalid header\r\n");
	break;

    case IMAP_MESSAGE_NOBLANKLINE:
	prot_printf(pout, 
		    "554 5.6.0 Message has no header/body separator\r\n");
	break;

    case IMAP_MAILBOX_NONEXISTENT:
	/* XXX Might have been moved to other server */
	if (LMTP_LONG_ERROR_MSGS) {
	    prot_printf(pout, 
"550-Mailbox unknown.  Either there is no mailbox associated with this\r\n"
"550-name or you do not have authorization to see it.\r\n"
"550 5.1.1 User unknown\r\n");
	} else {
	    prot_printf(pout, "550 5.1.1 User unknown\r\n");
	}
	break;

    case IMAP_PROTOCOL_BAD_PARAMETERS:
	prot_printf(pout, "501 5.5.4 Syntax error in parameters\r\n");
	break;

    case MUPDATE_BADPARAM:
    default:
	/* Some error we're not expecting. */
	prot_printf(pout, "554 5.0.0 Unexpected internal error\r\n");
	break;
    }
}

/* ----- this section defines functions on message_data_t.
   ----- access functions and the like, etc. */

/* returns non-zero on failure */
int msg_new(message_data_t **m)
{
    message_data_t *ret = (message_data_t *) xmalloc(sizeof(message_data_t));
    int i;

    ret->data = NULL;
    ret->f = NULL;
    ret->id = NULL;
    ret->size = 0;
    ret->return_path = NULL;
    ret->rcpt = NULL;
    ret->rcpt_num = 0;

    ret->authuser = NULL;
    ret->authstate = NULL;

    ret->rock = NULL;

    for (i = 0; i < HEADERCACHESIZE; i++)
	ret->cache[i] = NULL;

    *m = ret;
    return 0;
}

void msg_free(message_data_t *m)
{
    int i;

    if (m->data) {
	prot_free(m->data);
    }
    if (m->f) {
	fclose(m->f);
    }
    if (m->id) {
	free(m->id);
    }

    if (m->return_path) {
	free(m->return_path);
    }
    if (m->rcpt) {
	for (i = 0; i < m->rcpt_num; i++) {
	    if (m->rcpt[i]->all) free(m->rcpt[i]->all);
	    if (m->rcpt[i]->user) free(m->rcpt[i]->user);
	    free(m->rcpt[i]);
	}
	free(m->rcpt);
    }

    if (m->authuser) {
	free(m->authuser);
	if (m->authstate) auth_freestate(m->authstate);
    }

    for (i = 0; i < HEADERCACHESIZE; i++) {
	if (m->cache[i]) {
	    int j;

	    free(m->cache[i]->name);
	    for (j = 0; j < m->cache[i]->ncontents; j++) {
		free(m->cache[i]->contents[j]);
	    }

	    free(m->cache[i]);
	}
    }

    free(m);
}

/* hash function used for header cache in struct msg */
static int hashheader(char *header)
{
    int x = 0;
    /* any CHAR except ' ', :, or a ctrl char */
    for (; !iscntrl((int) *header) && (*header != ' ') && (*header != ':'); 
	 header++) {
	x *= 256;
	x += *header;
	x %= HEADERCACHESIZE;
    }
    return x;
}

const char **msg_getheader(message_data_t *m, const char *phead)
{
    char *head;
    const char **ret = NULL;
    int clinit, cl;

    assert(m && phead);

    head = xstrdup(phead);
    lcase(head);

    /* check the cache */
    clinit = cl = hashheader(head);
    while (m->cache[cl] != NULL) {
	if (!strcmp(head, m->cache[cl]->name)) {
	    ret = (const char **) m->cache[cl]->contents;
	    break;
	}
	cl++; /* try next hash bin */
	cl %= HEADERCACHESIZE;
	if (cl == clinit) break; /* gone all the way around */
    }

    free(head);

    return ret;
}

int msg_getsize(message_data_t *m)
{
    return m->size;
}

int msg_getnumrcpt(message_data_t *m)
{
    return m->rcpt_num;
}

const char *msg_getrcpt(message_data_t *m, int rcpt_num)
{
    assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
    return m->rcpt[rcpt_num]->user;
}

const char *msg_getrcptall(message_data_t *m, int rcpt_num)
{
    assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
    return m->rcpt[rcpt_num]->all;
}

int msg_getrcpt_ignorequota(message_data_t *m, int rcpt_num)
{
    assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
    return m->rcpt[rcpt_num]->ignorequota;
}

/* set a recipient status; 'r' should be an IMAP error code that will be
   translated into an LMTP status code */
void msg_setrcpt_status(message_data_t *m, int rcpt_num, int r)
{
    assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
    m->rcpt[rcpt_num]->status = r;
}

void *msg_getrock(message_data_t *m)
{
    return m->rock;
}

void msg_setrock(message_data_t *m, void *rock)
{
    m->rock = rock;
}

/* return a malloc'd string representing the authorized user.
 advance 'strp' over the parameter */
static char *parseautheq(char **strp)
{
    char *ret;
    char *str;
    char *s = *strp;

    if (!strcmp(s, "<>")) {
	*strp = s + 2;
	return NULL;
    }

    ret = (char *) xmalloc(strlen(s)+1);
    ret[0]='\0';
    str = ret;

    if (*s == '<') s++; 	/* we'll be liberal and accept "<foo>" */
    while (1)
    {
	/* hexchar */
	if (*s == '+')
	{
	    int lup;
	    *str = '\0';
	    s++;
	    
	    for (lup=0;lup<2;lup++)
	    {
		if ((*s>='0') && (*s<='9'))
		    (*str) = (*str) & (*s - '0');
		else if ((*s>='A') && (*s<='F'))
		    (*str) = (*str) & (*s - 'A' + 10);
		else {
		    free(ret);
		    *strp = s;
		    return NULL;
		}
		if (lup==0)
		{
		    (*str) = (*str) << 4;
		    s++;
		}		
	    }
	    str++;

	} else if ((*s >= '!') && (*s <='~') && (*s!='+') && (*s!='=')) {
	    /* ascii char */
	    *str = *s;
	    str++;
	} else {
	    /* bad char or end-of-line */
	    break;
	}
	s++;
    }

    *strp = s;
    if (*s && (*s!=' ')) { free(ret); return NULL; }

    *str = '\0';

    /* take off trailing '>' */
    if ((str!=ret) && ( *(str-1)=='>'))
    {
	*(str-1) = '\0';
    }

    return ret;
}

/* return malloc'd string containing the address */
static char *parseaddr(char *s)
{
    char *p, *ret;
    int len;

    p = s;

    if (*p++ != '<') return 0;

    /* at-domain-list */
    while (*p == '@') {
	p++;
	if (*p == '[') {
	    p++;
	    while (isdigit((int) *p) || *p == '.') p++;
	    if (*p++ != ']') return 0;
	}
	else {
	    while (isalnum((int) *p) || *p == '.' || *p == '-') p++;
	}
	if (*p == ',' && p[1] == '@') p++;
	else if (*p == ':' && p[1] != '@') p++;
	else return 0;
    }
    
    /* local-part */
    if (*p == '\"') {
	p++;
	while (*p && *p != '\"') {
	    if (*p == '\\') {
		if (!*++p) return 0;
	    }
	    p++;
	}
	if (!*p++) return 0;
    }
    else {
	while (*p && *p != '@' && *p != '>') {
	    if (*p == '\\') {
		if (!*++p) return 0;
	    }
	    else {
		if (*p <= ' ' || (*p & 128) ||
		    strchr("<>()[]\\,;:\"", *p)) return 0;
	    }
	    p++;
	}
    }

    /* @domain */
    if (*p == '@') {
	p++;
	if (*p == '[') {
	    p++;
	    while (isdigit((int) *p) || *p == '.') p++;
	    if (*p++ != ']') return 0;
	}
	else {
	    while (isalnum((int) *p) || *p == '.' || *p == '-') p++;
	}
    }
    
    if (*p++ != '>') return 0;
    if (*p && *p != ' ') return 0;
    len = p - s;

    ret = xmalloc(len + 1);
    memcpy(ret, s, len);
    ret[len] = '\0';
    return ret;
}

/* clean off the <> from the return path */
void clean_retpath(char *rpath)
{
    int sl;

    /* Remove any angle brackets around return path */
    if (*rpath == '<') {
	sl = strlen(rpath);
	/* use strlen(rpath) so we move the NUL too */
	memmove(rpath, rpath+1, sl);
	sl--; /* string is one shorter now */
	if (rpath[sl-1] == '>') {
	    rpath[sl-1] = '\0';
	}
    }
}

/*
 * Destructively remove any whitespace and 822 comments
 * from string pointed to by 'buf'.  Does not handle continuation header
 * lines.
 */
void
clean822space(buf)
char *buf;
{
    char *from=buf, *to=buf;
    int c;
    int commentlevel = 0;

    while ((c = *from++)!=0) {
	switch (c) {
	case '\r':
	case '\n':
	case '\0':
	    *to = '\0';
	    return;

	case ' ':
	case '\t':
	    continue;

	case '(':
	    commentlevel++;
	    break;

	case ')':
	    if (commentlevel) commentlevel--;
	    break;

	case '\\':
	    if (commentlevel && *from) from++;
	    /* FALL THROUGH */

	default:
	    if (!commentlevel) *to++ = c;
	    break;
	}
    }
}

/* copies the message from fin to fout, massaging accordingly: 
   . newlines are fiddled to \r\n
   . "." terminates 
   . embedded NULs are rejected
   . bare \r are removed
*/
static int copy_msg(struct protstream *fin, FILE *fout)
{
    char buf[8192], *p;
    int r = 0;

    /* -2: Might need room to add a \r\n\0 set */
    while (prot_fgets(buf, sizeof(buf)-2, fin)) {
	p = buf + strlen(buf) - 1;
	if (p < buf) {
	    /* buffer start with a \0 */
	    r = IMAP_MESSAGE_CONTAINSNULL;
	    continue; /* need to eat the rest of the message */
	}
	else if (buf[0] == '\r' && buf[1] == '\0') {
	    /* The message contained \r\0, and fgets is confusing us. */
	    r = IMAP_MESSAGE_CONTAINSNULL;
	    continue; /* need to eat the rest of the message */
	}
	else if (p[0] == '\r') {
	    /*
	     * We were unlucky enough to get a CR just before we ran
	     * out of buffer--put it back.
	     */
	    prot_ungetc('\r', fin);
	    *p = '\0';
	}
	else if (p[0] == '\n' && (p == buf || p[-1] != '\r')) {
	    /* found an \n without a \r */
	    p[0] = '\r';
	    p[1] = '\n';
	    p[2] = '\0';
	}
	else if (p[0] != '\n') {
	    /* line contained a \0 not at the end */
	    r = IMAP_MESSAGE_CONTAINSNULL;
	    continue;
	}

	/* Remove any lone CR characters */
	while ((p = strchr(buf, '\r')) && p[1] != '\n') {
	    /* Src/Target overlap, use memmove */
	    /* strlen(p) will result in copying the NUL byte as well */
	    memmove(p, p+1, strlen(p));
	}
	
	if (buf[0] == '.') {
	    if (buf[1] == '\r' && buf[2] == '\n') {
		/* End of message */
		goto lmtpdot;
	    }
	    /* Remove the dot-stuffing */
	    fputs(buf+1, fout);
	} else {
	    fputs(buf, fout);
	}
    }

    /* wow, serious error---got a premature EOF. */
    return IMAP_IOERROR;

 lmtpdot:
    return r;
}

/* take a list of headers, pull the first one out and return it in
   name and contents.

   copies fin to fout, massaging 

   returns 0 on success, negative on failure */
typedef enum {
    NAME_START,
    NAME,
    COLON,
    BODY_START,
    BODY
} state;

enum {
    NAMEINC = 128,
    BODYINC = 1024
};

/* we don't have to worry about dotstuffing here, since it's illegal
   for a header to begin with a dot!

   returns 0 on success, filling in 'headname' and 'contents' with a static
   pointer (blech).
   on end of headers, returns 0 with NULL 'headname' and NULL 'contents'

   on error, returns < 0
*/
static int parseheader(struct protstream *fin, FILE *fout, 
		       char **headname, char **contents) {
    int c;
    static char *name = NULL, *body = NULL;
    static int namelen = 0, bodylen = 0;
    int off = 0;
    state s = NAME_START;
    int r = 0;
    int reject8bit = config_getswitch("reject8bit", 0);

    if (namelen == 0) {
	namelen += NAMEINC;
	name = (char *) xrealloc(name, namelen * sizeof(char));
    }
    if (bodylen == 0) {
	bodylen += BODYINC;
	body = (char *) xrealloc(body, bodylen * sizeof(char));
    }

    /* there are two ways out of this loop, both via gotos:
       either we successfully read a header (got_header)
       or we hit an error (ph_error) */
    while ((c = prot_getc(fin)) != EOF) { /* examine each character */
	switch (s) {
	case NAME_START:
	    if (c == '.') {
		int peek;

		peek = prot_getc(fin);
		prot_ungetc(peek, fin);
		
		if (peek == '\r' || peek == '\n') {
		    /* just reached the end of message */
		    r = IMAP_MESSAGE_NOBLANKLINE;
		    goto ph_error;
		}
	    }
	    if (c == '\r' || c == '\n') {
		/* just reached the end of headers */
		r = 0;
		goto ph_error;
	    }
	    /* field-name      =       1*ftext
	       ftext           =       %d33-57 / %d59-126         
	                               ; Any character except
				       ;  controls, SP, and
				       ;  ":". */
	    if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
		/* invalid header name */
		r = IMAP_MESSAGE_BADHEADER;
		goto ph_error;
	    }
	    name[0] = tolower(c);
	    off = 1;
	    s = NAME;
	    break;

	case NAME:
	    if (c == ' ' || c == '\t' || c == ':') {
		name[off] = '\0';
		s = (c == ':' ? BODY_START : COLON);
		break;
	    }
	    if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
		r = IMAP_MESSAGE_BADHEADER;
		goto ph_error;
	    }
	    name[off++] = tolower(c);
	    if (off >= namelen - 3) {
		namelen += NAMEINC;
		name = (char *) xrealloc(name, namelen);
	    }
	    break;
	
	case COLON:
	    if (c == ':') {
		s = BODY_START;
	    } else if (c != ' ' && c != '\t') {
		/* i want to avoid confusing dot-stuffing later */
		while (c == '.') {
		    fputc(c, fout);
		    c = prot_getc(fin);
		}
		r = IMAP_MESSAGE_BADHEADER;
		goto ph_error;
	    }
	    break;

	case BODY_START:
	    if (c == ' ' || c == '\t') /* eat the whitespace */
		break;
	    off = 0;
	    s = BODY;
	    /* falls through! */
	case BODY:
	    /* now we want to convert all newlines into \r\n */
	    if (c == '\r' || c == '\n') {
		int peek;

		peek = prot_getc(fin);
		
		fputc('\r', fout);
		fputc('\n', fout);
		/* we should peek ahead to see if it's folded whitespace */
		if (c == '\r' && peek == '\n') {
		    c = prot_getc(fin);
		} else {
		    c = peek; /* single newline seperator */
		}
		if (c != ' ' && c != '\t') {
		    /* this is the end of the header */
		    body[off] = '\0';
		    prot_ungetc(c, fin);
		    goto got_header;
		}
		/* ignore this whitespace, but we'll copy all the rest in */
		break;
	    } else {
		if (c >= 0x80) {
		    if (reject8bit) {
			/* We have been configured to reject all mail of this
			   form. */
			r = IMAP_MESSAGE_CONTAINS8BIT;
			goto ph_error;
		    } else {
			/* We have been configured to munge all mail of this
			   form. */
			c = 'X';
		    }
		}
		/* just an ordinary character */
		body[off++] = c;
		if (off >= bodylen - 3) {
		    bodylen += BODYINC;
		    body = (char *) xrealloc(body, bodylen);
		}
	    }
	}

	/* copy this to the output */
	fputc(c, fout);
    }

    /* if we fall off the end of the loop, we hit some sort of error
       condition */

 ph_error:
    /* put the last character back; we'll copy it later */
    prot_ungetc(c, fin);

    /* and we didn't get a header */
    if (headname != NULL) *headname = NULL;
    if (contents != NULL) *contents = NULL;
    return r;

 got_header:
    if (headname != NULL) *headname = xstrdup(name);
    if (contents != NULL) *contents = xstrdup(body);

    return 0;
}

static int fill_cache(struct protstream *fin, FILE *fout, message_data_t *m)
{
    int r = 0;

    /* let's fill that header cache */
    for (;;) {
	char *name, *body;
	int cl, clinit;

	if ((r = parseheader(fin, fout, &name, &body)) < 0) {
	    break;
	}
	if (!name) {
	    /* reached the end of headers */
	    break;
	}

	/* put it in the hash table */
	clinit = cl = hashheader(name);
	while (m->cache[cl] != NULL && strcmp(name, m->cache[cl]->name)) {
	    cl++;		/* resolve collisions linearly */
	    cl %= HEADERCACHESIZE;
	    if (cl == clinit) break; /* gone all the way around, so bail */
	}

	/* found where to put it, so insert it into a list */
	if (m->cache[cl]) {
	    /* add this body on */
	    m->cache[cl]->contents[m->cache[cl]->ncontents++] = body;

	    /* whoops, won't have room for the null at the end! */
	    if (!(m->cache[cl]->ncontents % 8)) {
		/* increase the size */
		m->cache[cl] = (header_t *)
		    xrealloc(m->cache[cl],sizeof(header_t) +
			     ((8 + m->cache[cl]->ncontents) * sizeof(char *)));
	    }

	    /* have no need of this */
	    free(name);
	} else {
	    /* create a new entry in the hash table */
	    m->cache[cl] = (header_t *) xmalloc(sizeof(header_t) + 
						8 * sizeof(char*));
	    m->cache[cl]->name = name;
	    m->cache[cl]->contents[0] = body;
	    m->cache[cl]->ncontents = 1;
	}

	/* we always want a NULL at the end */
	m->cache[cl]->contents[m->cache[cl]->ncontents] = NULL;
    }

    if (r) {
	/* got a bad header */

	/* flush the remaining output */
	copy_msg(fin, fout);
	/* and return the error */
	return r;
    } else {
	return copy_msg(fin, fout);
    }
}

/*
 * file in the message structure 'm' from 'pin', assuming a dot-stuffed
 * stream a la lmtp.
 *
 * returns 0 on success, imap error code on failure
 */
static int savemsg(struct clientdata *cd,
		   const struct lmtp_func *func,
		   message_data_t *m)
{
    FILE *f;
    struct stat sbuf;
    const char **body;
    int r;
    int nrcpts = m->rcpt_num;
    time_t t;
    char datestr[80];

    /* Copy to spool file */
    f = func->spoolfile(m);
    if (!f) {
	prot_printf(cd->pout, 
		    "451 4.3.%c cannot create temporary file: %s\r\n",
		    (
#ifdef EDQUOT
			errno == EDQUOT ||
#endif
			errno == ENOSPC) ? '1' : '2',
		    error_message(errno));
	return IMAP_IOERROR;
    }

    prot_printf(cd->pout, "354 go ahead\r\n");

    if (m->return_path && func->addretpath) { /* add the return path */
	char *rpath = m->return_path;
	const char *hostname = 0;

	clean_retpath(rpath);
	/* Append our hostname if there's no domain in address */
	hostname = NULL;
	if (!strchr(rpath, '@') && strlen(rpath) > 0) {
	    hostname = config_servername;
	}

	fprintf(f, "Return-Path: <%s%s%s>\r\n",
		rpath, hostname ? "@" : "", hostname ? hostname : "");
    }

    /* add a received header */
    t = time(NULL);
    rfc822date_gen(datestr, sizeof(datestr), t);
    fprintf(f, "Received: from %s (%s)",
	    cd->lhlo_param, cd->clienthost);
    if (m->authuser) {
	const int *ssfp;
	sasl_getprop(cd->conn, SASL_SSF, (const void **) &ssfp);
	fprintf(f, " (authenticated user=%s bits=%d)", m->authuser, *ssfp);
    }
    fprintf(f, "\r\n\tby %s (Cyrus %s) with LMTP",
		config_servername, CYRUS_VERSION);

#ifdef HAVE_SSL
    if (cd->tls_conn) {
	char tls_info[250];

	tls_info[0] = '\0';
	/* grab TLS info for Received: header */
	tls_get_info(cd->tls_conn, tls_info, sizeof(tls_info));
	if (*tls_info) fprintf(f, " (%s)", tls_info);
    }
#endif /* HAVE_SSL */

    fprintf(f, "; %s\r\n", datestr);

    /* add any requested headers */
    if (func->addheaders) {
	fputs(func->addheaders, f);
    }

    /* fill the cache */
    r = fill_cache(cd->pin, f, m);
    if (r) {
	fclose(f);
	if (func->removespool) {
	    /* remove the spool'd message */
	    func->removespool(m);
	}
	while (nrcpts--) {
	    send_lmtp_error(cd->pout, r);
	}
	return r;
    }

    /* now, using our header cache, fill in the data that we want */

    /* first check resent-message-id */
    if ((body = msg_getheader(m, "resent-message-id")) != NULL) {
	m->id = xstrdup(body[0]);
    } else if ((body = msg_getheader(m, "message-id")) != NULL) {
	m->id = xstrdup(body[0]);
    } else {
	m->id = NULL;		/* no message-id */
    }

    if (!m->return_path &&
	(body = msg_getheader(m, "return-path"))) {
	/* let's grab return_path */
	m->return_path = xstrdup(body[0]);
	clean822space(m->return_path);
	clean_retpath(m->return_path);
    }

    fflush(f);
    if (ferror(f)) {
	while (nrcpts--) {
	    prot_printf(cd->pout,
	       "451 4.3.%c cannot copy message to temporary file: %s\r\n",
		   (
#ifdef EDQUOT
		    errno == EDQUOT ||
#endif
		    errno == ENOSPC) ? '1' : '2',
		   error_message(errno));
	}
	fclose(f);
	func->removespool(m);
	return IMAP_IOERROR;
    }

    if (fstat(fileno(f), &sbuf) == -1) {
	while (nrcpts--) {
	    prot_printf(cd->pout,
			"451 4.3.2 cannot stat message temporary file: %s\r\n",
			error_message(errno));
	}
	fclose(f);
	func->removespool(m);
	return IMAP_IOERROR;
    }
    m->size = sbuf.st_size;
    m->f = f;
    m->data = prot_new(fileno(f), 0);

    return 0;
}

/* see if 'addr' exists. if so, fill in 'ad' appropriately.
   on success, return NULL.
   on failure, return the error. */
static int process_recipient(char *addr,
			     int ignorequota,
			     int (*verify_user)(const char *, long,
						struct auth_state *),
			     message_data_t *msg)
{
    char *dest;
    char *user;
    int r, sl;
    address_data_t *ret = (address_data_t *) xmalloc(sizeof(address_data_t));
    int forcedowncase = config_getswitch("lmtp_downcase_rcpt", 0);

    assert(addr != NULL && msg != NULL);

    if (*addr == '<') addr++;
    dest = user = addr;
    
    /* preserve the entire address */
    ret->all = xstrdup(addr);
    sl = strlen(ret->all);
    if (ret->all[sl-1] == '>')
	ret->all[sl-1] = '\0';

    /* now find just the user */
    
    /* Skip at-domain-list */
    if (*addr == '@') {
	addr = strchr(addr, ':');
	if (!addr) {
	    free(ret->all);
	    free(ret);
	    return IMAP_PROTOCOL_BAD_PARAMETERS;
	}
	addr++;
    }
    
    if (*addr == '\"') {
	addr++;
	while (*addr && *addr != '\"') {
	    if (*addr == '\\') addr++;
	    *dest++ = *addr++;
	}
    }
    else {
	if(forcedowncase) {
	    /* We should downcase the localpart up to the first + */
	    while(*addr != '@' && *addr != '>' && *addr != '+') {
		if(*addr == '\\') addr++;
		*dest++ = TOLOWER(*addr++);
	    }
	}

	/* Now finish the remainder of the localpart */
	while (*addr != '@' && *addr != '>') {
	    if (*addr == '\\') addr++;
	    *dest++ = *addr++;
	}
    }
    *dest = '\0';
	
    r = verify_user(user, ignorequota ? -1 : msg->size, msg->authstate);
    if (r) {
	/* we lost */
	free(ret->all);
	free(ret);
	return r;
    }
    ret->user = xstrdup(user);

    ret->ignorequota = ignorequota;

    msg->rcpt[msg->rcpt_num] = ret;

    return 0;
}

static int localauth_mechlist_override(
    void *context __attribute__((unused)), 
    const char *plugin_name __attribute__((unused)),
    const char *option,
    const char **result,
    unsigned *len)
{
    /* If we are doing local auth, we only support EXTERNAL */
    if (strcmp(option,"mech_list")==0)
    {
	*result = "EXTERNAL";
	if (len)
	    *len = strlen(*result);
	return SASL_OK;
    }

    /* if we don't find the option,
       this should percolate to the global getopt */
    return SASL_FAIL;
}

static struct sasl_callback localauth_override_cb[] = {
    { SASL_CB_GETOPT, &localauth_mechlist_override, NULL },
    { SASL_CB_LIST_END, NULL, NULL },
};

void lmtpmode(struct lmtp_func *func,
	      struct protstream *pin, 
	      struct protstream *pout,
	      int fd)
{
    message_data_t *msg = NULL;
    char shutdownfilename[1024];
    int shutdown_fd = -1;
    int max_msgsize;
    char buf[4096];
    char *p;
    int r;
    struct clientdata cd;

    struct sockaddr_in localaddr, remoteaddr;
    int havelocal = 0, haveremote = 0;
    char localip[60], remoteip[60];
    socklen_t salen;

    sasl_ssf_t ssf;
    char *auth_id;
    int plaintext_result;

    int secflags = 0;
    sasl_security_properties_t *secprops = NULL;
    enum {
	EXTERNAL_AUTHED = -1, /* -1: external auth'd, but no AUTH issued */
	NOAUTH = 0,
	DIDAUTH = 1
    } authenticated = NOAUTH;	

    /* setup the clientdata structure */
    cd.pin = pin;
    cd.pout = pout;
    cd.fd = fd;
    cd.clienthost[0] = '\0';
    cd.lhlo_param[0] = '\0';
#ifdef HAVE_SSL
    cd.tls_conn = NULL;
#endif
    cd.starttls_done = 0;

    snprintf(shutdownfilename, sizeof(shutdownfilename), 
	     "%s/msg/shutdown", config_dir);
    max_msgsize = config_getint("maxmessagesize", INT_MAX);

    msg_new(&msg);

    /* don't leak old connections */
    if(saslprops.iplocalport) {
	free(saslprops.iplocalport);
	saslprops.iplocalport = NULL;
    }
    if(saslprops.ipremoteport) {
	free(saslprops.ipremoteport);
	saslprops.ipremoteport = NULL;
    }

    /* determine who we're talking to */
    salen = sizeof(remoteaddr);
    r = getpeername(fd, (struct sockaddr *)&remoteaddr, &salen);
    if (!r && remoteaddr.sin_family == AF_INET) {
	/* connected to an internet socket */
	struct hostent *hp;
	hp = gethostbyaddr((char *)&remoteaddr.sin_addr,
			   sizeof(remoteaddr.sin_addr), AF_INET);
	if (hp != NULL) {
	    strlcpy(cd.clienthost, hp->h_name, sizeof(cd.clienthost) - 30);
	} else {
	    strlcpy(cd.clienthost, inet_ntoa(remoteaddr.sin_addr), 
		    sizeof(cd.clienthost) - 30);
	}
	strlcat(cd.clienthost, " [", sizeof(cd.clienthost));
	strlcat(cd.clienthost, inet_ntoa(remoteaddr.sin_addr), 
		sizeof(cd.clienthost));
	strlcat(cd.clienthost, "]", sizeof(cd.clienthost));

	salen = sizeof(localaddr);
	if (!getsockname(fd, (struct sockaddr *)&localaddr, &salen)) {
	    /* set the ip addresses here */
	    if(iptostring((struct sockaddr *)&localaddr,
                          sizeof(struct sockaddr_in),
			  localip, sizeof(localip)) == 0) {
		havelocal = 1;
                saslprops.iplocalport = xstrdup(localip);
            }
            if(iptostring((struct sockaddr *)&remoteaddr,
                          sizeof(struct sockaddr_in),
			  remoteip, sizeof(remoteip)) == 0) {
		haveremote = 1;
                saslprops.ipremoteport = xstrdup(remoteip);
            }
	} else {
	    fatal("can't get local addr", EC_SOFTWARE);
	}

	syslog(LOG_DEBUG, "connection from %s%s", 
	       cd.clienthost, 
	       func->preauth ? " preauth'd as postman" : "");
    } else {
	/* we're not connected to a internet socket! */
	func->preauth = 1;
	strcpy(cd.clienthost, "[unix socket]");
	syslog(LOG_DEBUG, "lmtp connection preauth'd as postman");
    }

    /* Setup SASL to go.  We need to do this *after* we decide if
     *  we are preauthed or not. */
    if (sasl_server_new("lmtp", NULL, NULL, NULL,
			NULL, (func->preauth ? localauth_override_cb : NULL),
			0, &cd.conn) != SASL_OK) {
	fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
    }

    /* set my allowable security properties */
    /* ANONYMOUS is silly because we allow that anyway */
    secflags = SASL_SEC_NOANONYMOUS;
    plaintext_result = config_getswitch("allowplaintext",1);
    if (!config_getswitch("lmtp_allowplaintext", plaintext_result)) {
	secflags |= SASL_SEC_NOPLAINTEXT;
    }
    secprops = mysasl_secprops(secflags);
    sasl_setprop(cd.conn, SASL_SEC_PROPS, secprops);

    if (func->preauth) {
	authenticated = EXTERNAL_AUTHED;	/* we'll allow commands, 
						   but we still accept
						   the AUTH command */
	ssf = 2;
	auth_id = "postman";
	sasl_setprop(cd.conn, SASL_SSF_EXTERNAL, &ssf);
	sasl_setprop(cd.conn, SASL_AUTH_EXTERNAL, auth_id);
    } else {
	if(havelocal) sasl_setprop(cd.conn, SASL_IPLOCALPORT,  &localip );
	if(haveremote) sasl_setprop(cd.conn, SASL_IPREMOTEPORT, &remoteip);  
    }

    prot_printf(pout, "220 %s LMTP Cyrus %s ready\r\n", 
		config_servername,
		CYRUS_VERSION);

    for (;;) {
    nextcmd:
      signals_poll();

      if (!prot_fgets(buf, sizeof(buf), pin)) {
	  const char *err = prot_error(pin);
	  
	  if (err != NULL) {
	      prot_printf(pout, "421 4.4.1 bye %s\r\n", err);
	      prot_flush(pout);
	  }
	  goto cleanup;
      }
      p = buf + strlen(buf) - 1;
      if (p >= buf && *p == '\n') *p-- = '\0';
      if (p >= buf && *p == '\r') *p-- = '\0';

      /* Only allow LHLO/NOOP/QUIT when there is a shutdown file */
      if (!strchr("LlNnQq", buf[0]) &&
	  (shutdown_fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
	  struct protstream *shutdown_in = prot_new(shutdown_fd, 0);

	  prot_fgets(buf, sizeof(buf), shutdown_in);
	  if ((p = strchr(buf, '\r'))!=NULL) *p = 0;
	  if ((p = strchr(buf, '\n'))!=NULL) *p = 0;

	  prot_printf(pout, "421 4.3.2 %s\r\n", buf);
	  prot_flush(pout);

	  func->shutdown(0);
      }

      switch (buf[0]) {
      case 'a':
      case 'A':
	  if (!strncasecmp(buf, "auth ", 5)) {
	      char mech[128];
	      int sasl_result;
	      const char *user;
	      
	      if (authenticated > 0) {
		  prot_printf(pout,
			      "503 5.5.0 already authenticated\r\n");
		  continue;
	      }
	      if (msg->rcpt_num != 0) {
		  prot_printf(pout,
			      "503 5.5.0 AUTH not permitted now\r\n");
		  continue;
	      }
	      
	      /* ok, what mechanism ? */
	      p = buf + 5;
	      while ((*p != ' ') && (*p != '\0')) {
		  p++;
	      }
	      if (*p == ' ') {
		  *p = '\0';
		  p++;
	      } else {
		  p = NULL;
	      }
	      strlcpy(mech, buf + 5, sizeof(mech));

	      r = saslserver(cd.conn, mech, p, "334 ",
			     pin, pout, &sasl_result, NULL);

	      if (r) {
		  const char *errorstring = NULL;

		  switch (r) {
		  case IMAP_SASL_CANCEL:
		      prot_printf(pout,
				  "501 5.5.4 client canceled authentication\r\n");
		      break;
		  case IMAP_SASL_PROTERR:
		      errorstring = prot_error(pin);

		      prot_printf(pout,
				  "501 5.5.4 Error reading client response: %s\r\n",
				  errorstring ? errorstring : "");
		      break;
		  default:
		      if (sasl_result == SASL_NOMECH) {
			  prot_printf(pout, 
				      "504 Unrecognized authentication type.\r\n");
			  continue;
		      }
		      else {
			  sleep(3);
		  
			  syslog(LOG_ERR, "badlogin: %s %s %s",
				 remoteaddr.sin_family == AF_INET ?
				 inet_ntoa(remoteaddr.sin_addr) :
				 "[unix socket]",
				 mech,
				 sasl_errdetail(cd.conn));
		  
			  snmp_increment_args(AUTHENTICATION_NO, 1,
					      VARIABLE_AUTH, hash_simple(mech), 
					      VARIABLE_LISTEND);

			  prot_printf(pout, "501 5.5.4 %s\r\n",
				      sasl_errstring((r == SASL_NOUSER ?
						      SASL_BADAUTH : r),
						     NULL, NULL));
		      }
		  }

		  reset_saslconn(&cd.conn);
		  continue;
	      }
	      r = sasl_getprop(cd.conn, SASL_USERNAME, (const void **) &user);
	      if (r != SASL_OK) {
		  prot_printf(pout, "501 5.5.4 SASL Error\r\n");
		  reset_saslconn(&cd.conn);
		  goto nextcmd;
	      }

	      /* Create telemetry log */
	      deliver_logfd = telemetry_log(user, pin, pout);

	      /* authenticated successfully! */
	      snmp_increment_args(AUTHENTICATION_YES,1,
				  VARIABLE_AUTH, hash_simple(mech), 
				  VARIABLE_LISTEND);
	      syslog(LOG_NOTICE, "login: %s %s %s%s %s",
		     cd.clienthost, user, mech,
		     cd.starttls_done ? "+TLS" : "", "User logged in");

	      authenticated += 2;
	      prot_printf(pout, "235 Authenticated!\r\n");

	      /* set protection layers */
	      prot_setsasl(pin,  cd.conn);
	      prot_setsasl(pout, cd.conn);
	      continue;
	  }
	  goto syntaxerr;

      case 'd':
      case 'D':
	    if (!strcasecmp(buf, "data")) {
		int delivered = 0;
		int j;

		if (!msg->rcpt_num) {
		    prot_printf(pout, "503 5.5.1 No recipients\r\n");
		    continue;
		}
		/* copy message from input to msg structure */
		r = savemsg(&cd, func, msg);
		if (r) {
		    goto rset;
		}

		if (msg->size > max_msgsize) {
		    prot_printf(pout, 
				"552 5.2.3 Message size (%d) exceeds fixed "
				"maximum message size (%d)\r\n",
				msg->size, max_msgsize);
		    continue;
		}

		snmp_increment(mtaReceivedMessages, 1);
		snmp_increment(mtaReceivedVolume, roundToK(msg->size));
		snmp_increment(mtaReceivedRecipients, msg->rcpt_num);

		/* do delivery, report status */
		r = func->deliver(msg, msg->authuser, msg->authstate);
		for (j = 0; j < msg->rcpt_num; j++) {
		    if (!msg->rcpt[j]->status) delivered++;
		    send_lmtp_error(pout, msg->rcpt[j]->status);
		}

		snmp_increment(mtaTransmittedMessages, delivered);
		snmp_increment(mtaTransmittedVolume, 
			       roundToK(delivered * msg->size));
		goto rset;
	    }
	    goto syntaxerr;

      case 'l':
      case 'L':
	  if (!strncasecmp(buf, "lhlo ", 5)) {
	      unsigned int mechcount;
	      const char *mechs;
	      
	      prot_printf(pout, "250-%s\r\n"
			  "250-8BITMIME\r\n"
			  "250-ENHANCEDSTATUSCODES\r\n"
			  "250-PIPELINING\r\n",
			  config_servername);
	      if (max_msgsize < INT_MAX)
		  prot_printf(pout, "250-SIZE %d\r\n", max_msgsize);
	      else
		  prot_printf(pout, "250-SIZE\r\n");
	      if (tls_enabled("lmtp") && !cd.starttls_done && !authenticated) {
		  prot_printf(pout, "250-STARTTLS\r\n");
	      }
	      if (authenticated <= 0 &&
		  sasl_listmech(cd.conn, NULL, "AUTH ", " ", "", &mechs, 
				NULL, &mechcount) == SASL_OK && 
		  mechcount > 0) {
		  prot_printf(pout,"250-%s\r\n", mechs);
	      }
	      prot_printf(pout, "250 IGNOREQUOTA\r\n");

	      strlcpy(cd.lhlo_param, buf + 5, sizeof(cd.lhlo_param));
	      
	      continue;
	  }
	  goto syntaxerr;
    
      case 'm':
      case 'M':
	    if (!authenticated) {
		if (config_getswitch("soft_noauth", 1)) {
		    prot_printf(pout, "430 Authentication required\r\n");
		} else {
		    prot_printf(pout, "530 Authentication required\r\n");
		}
		continue;
	    }

	    if (!strncasecmp(buf, "mail ", 5)) {
		char *tmp;
		if (msg->return_path) {
		    prot_printf(pout, 
				"503 5.5.1 Nested MAIL command\r\n");
		    continue;
		}
		/* +5 to get past "mail "
		 * +10 to get past "mail from:" */
		if (strncasecmp(buf+5, "from:", 5) != 0 ||
		    !(msg->return_path = parseaddr(buf+10))) {
		    prot_printf(pout, 
				"501 5.5.4 Syntax error in parameters\r\n");
		    continue;
		}
		tmp = buf+10+strlen(msg->return_path);

		/* is any other whitespace allow seperating? */
		while (*tmp == ' ') {
		    tmp++;
		    switch (*tmp) {
		    case 'a': case 'A':
			if (strncasecmp(tmp, "auth=", 5) != 0) {
			    goto badparam;
			}
			tmp += 5;
			msg->authuser = parseautheq(&tmp);
			if (msg->authuser) {
			    msg->authstate = auth_newstate(msg->authuser, NULL);
			} else {
			    /* do we want to bounce mail because of this? */
			    /* i guess not. accept with no auth user */
			    msg->authstate = NULL;
			}
			break;

		    case 'b': case 'B':
			if (strncasecmp(tmp, "body=", 5) != 0) {
			    goto badparam;
			}
			tmp += 5;
			/* just verify it's one of 
			   body-value ::= "7BIT" / "8BITMIME" */
			if (!strncasecmp(tmp, "7bit", 4)) {
			    tmp += 4;
			} else if (!strncasecmp(tmp, "8bitmime", 8)) {
			    tmp += 8;
			} else {
			    prot_printf(pout, 
			      "501 5.5.4 Unrecognized BODY type\r\n");
			    goto nextcmd;
			}
			break;

		    case 's': case 'S':
			if (strncasecmp(tmp, "size=", 5) != 0) {
			    goto badparam;
			}
			tmp += 5;
			/* make sure we have a value */
			if (!isdigit((int) *tmp)) {
				prot_printf(pout, 
					    "501 5.5.2 SIZE requires a value\r\n");
				goto nextcmd;
			}
			msg->size = strtoul(tmp, &p, 10);
			tmp = p;
			/* make sure the value is in range */
			if (errno == ERANGE || msg->size < 0 ||
			    msg->size > max_msgsize) {
			    prot_printf(pout, 
					"552 5.2.3 Message SIZE exceeds fixed "
					"maximum message size (%d)\r\n",
					max_msgsize);
			    goto nextcmd;
			}
			break;

		    default: 
		    badparam:
			prot_printf(pout, 
				    "501 5.5.4 Unrecognized parameters\r\n");
			goto nextcmd;
		    }
		} 
		if (*tmp != '\0') {
		    prot_printf(pout, 
				"501 5.5.4 Syntax error in parameters\r\n");  
		    continue;
		}

		prot_printf(pout, "250 2.1.0 ok\r\n");
		continue;
	    }
	    goto syntaxerr;

      case 'n':
      case 'N':
	    if (!strcasecmp(buf, "noop")) {
		prot_printf(pout,"250 2.0.0 ok\r\n");
		continue;
	    }
	    goto syntaxerr;

      case 'q':
      case 'Q':
	    if (!strcasecmp(buf, "quit")) {
		prot_printf(pout,"221 2.0.0 bye\r\n");
		prot_flush(pout);
		goto cleanup;
	    }
	    goto syntaxerr;
	    
      case 'r':
      case 'R':
	    if (!strncasecmp(buf, "rcpt ", 5)) {
		char *rcpt = NULL;
		int ignorequota = 0;
		char *tmp;

		if (!msg->return_path) {
		    prot_printf(pout, "503 5.5.1 Need MAIL command\r\n");
		    continue;
		}
		if (!(msg->rcpt_num % RCPT_GROW)) { /* time to alloc more */
		    msg->rcpt = (address_data_t **)
			xrealloc(msg->rcpt, (msg->rcpt_num + RCPT_GROW + 1) * 
				 sizeof(address_data_t *));
		}
		/* +5 to get past "rcpt "
		 * +8 to get past "rcpt to:" */
		if (strncasecmp(buf+5, "to:", 3) != 0 ||
		    !(rcpt = parseaddr(buf+8))) {
		    prot_printf(pout,
				"501 5.5.4 Syntax error in parameters\r\n");
		    continue;
		}

		tmp = buf+8+strlen(rcpt);
		while (*tmp == ' ') {
		    tmp++;
		    switch (*tmp) {
		    case 'i': case 'I':
			if (strncasecmp(tmp, "ignorequota", 12) != 0) {
			    goto badrparam;
			}
			tmp += 12;
			ignorequota = 1;
			break;

		    default: 
		    badrparam:
			prot_printf(pout, 
				    "501 5.5.4 Unrecognized parameters\r\n");
			goto nextcmd;
		    }
		} 
		if (*tmp != '\0') {
		    prot_printf(pout, 
				"501 5.5.4 Syntax error in parameters\r\n");  
		    continue;
		}

		r = process_recipient(rcpt,
				      ignorequota,
				      func->verify_user,
				      msg);
		if (rcpt) free(rcpt); /* malloc'd in parseaddr() */
		if (r) {
		    send_lmtp_error(pout, r);
		    continue;
		}
		msg->rcpt_num++;
		msg->rcpt[msg->rcpt_num] = NULL;
		prot_printf(pout, "250 2.1.5 ok\r\n");
		continue;
	    }
	    else if (!strcasecmp(buf, "rset")) {
		prot_printf(pout, "250 2.0.0 ok\r\n");

	      rset:
		if (msg) msg_free(msg);
		msg_new(&msg);
		
		continue;
	    }
	    goto syntaxerr;

      case 's':
      case 'S':
#ifdef HAVE_SSL
	    if (!strcasecmp(buf, "starttls") && tls_enabled("lmtp") &&
		!func->preauth) { /* don't need TLS for preauth'd connect */
		int *layerp;
		sasl_ssf_t ssf;
		char *auth_id;

		/* SASL and openssl have different ideas
		   about whether ssf is signed */
		layerp = &ssf;

		if (cd.starttls_done == 1) {
		    prot_printf(pout, "454 4.3.3 %s\r\n", 
				"Already successfully executed STARTTLS");
		    continue;
		}
		if (msg->rcpt_num != 0) {
		    prot_printf(pout,
				"503 5.5.0 STARTTLS not permitted now\r\n");
		    continue;
		}

		r=tls_init_serverengine("lmtp",
					5,   /* depth to verify */
					1,   /* can client auth? */
					1);   /* TLS only? */

		if (r == -1) {

		    syslog(LOG_ERR, "[lmtpd] error initializing TLS");

		    prot_printf(pout, "454 4.3.3 %s\r\n", "Error initializing TLS");
		    continue;
		}

		prot_printf(pout, "220 %s\r\n", "Begin TLS negotiation now");
		/* must flush our buffers before starting tls */
		prot_flush(pout);
  
		r=tls_start_servertls(0, /* read */
				      1, /* write */
				      layerp,
				      &auth_id,
				      &(cd.tls_conn));

		/* if error */
		if (r==-1) {
		    prot_printf(pout, "454 4.3.3 STARTTLS failed\r\n");
		    syslog(LOG_NOTICE, "[lmtpd] STARTTLS failed: %s", cd.clienthost);
		    continue;
		}

		/* tell SASL about the negotiated layer */
		r=sasl_setprop(cd.conn, SASL_SSF_EXTERNAL, &ssf);
		if (r != SASL_OK)
		    fatal("sasl_setprop(SASL_SSF_EXTERNAL) failed: STARTTLS",
			  EC_TEMPFAIL);
		saslprops.ssf = ssf;

		r=sasl_setprop(cd.conn, SASL_AUTH_EXTERNAL, auth_id);
		if (r != SASL_OK)
		    fatal("sasl_setprop(SASL_AUTH_EXTERNAL) failed: STARTTLS",
			  EC_TEMPFAIL);
		if(saslprops.authid) {
		    free(saslprops.authid);
		    saslprops.authid = NULL;
		}
		if(auth_id)
		    saslprops.authid = xstrdup(auth_id);		

		/* tell the prot layer about our new layers */
		prot_settls(pin, cd.tls_conn);
		prot_settls(pout, cd.tls_conn);

		cd.starttls_done = 1;

		continue;
	    }
#endif /* HAVE_SSL*/
	    goto syntaxerr;

      case 'v':
      case 'V':
	    if (!strncasecmp(buf, "vrfy ", 5)) {
		prot_printf(pout,
			    "252 2.3.3 try RCPT to attempt delivery\r\n");
		continue;
	    }
	    goto syntaxerr;

      default:
      syntaxerr:
	    prot_printf(pout, "500 5.5.2 Syntax error\r\n");
	    continue;
      }
    }

 cleanup:
    /* free resources and return; this connection has been closed */

    if (msg) msg_free(msg);

    /* security */
    if (cd.conn) sasl_dispose(&cd.conn);

    cd.starttls_done = 0;
#ifdef HAVE_SSL
    if (cd.tls_conn) {
	tls_reset_servertls(&cd.tls_conn);
	cd.tls_conn = NULL;
    }
#endif
}

/************** client-side LMTP ****************/

enum {
    CAPA_PIPELINING  = 1 << 0,
    CAPA_AUTH        = 1 << 1,
    CAPA_IGNOREQUOTA = 1 << 2
};

struct lmtp_conn {
    char *host;
    int sock;
    struct protstream *pin, *pout;
    sasl_conn_t *saslconn;

    /* lmtp specific properties */
    int capability;
    char *mechs;
};

#define ISGOOD(r) (((r) / 100) == 2)
#define TEMPFAIL(r) (((r) / 100) == 4)
#define PERMFAIL(r) (((r) / 100) == 5)
#define ISCONT(s) (s && (s[3] == '-'))

static int revconvert_lmtp(const char *code)
{
    int c = atoi(code);
    switch (c) {
    case 250:
    case 251:
	return 0;
    case 451:
	if (code[4] == '4' && code[6] == '3') {
	    if (code[8] == '0') {
		return IMAP_IOERROR;
	    } else if (code[8] == '1') {
		return IMAP_NOSPACE;
	    } else {
		return IMAP_IOERROR;
	    }
	}
	else if (code[4] == '4' && code [6] == '4') {
	    return IMAP_SERVER_UNAVAILABLE;
	}
	else {
	    return IMAP_IOERROR;
	}
    case 452:
	return IMAP_QUOTA_EXCEEDED;
    case 550:
	if (code[4] == '5' && code[6] == '7') {
	    return IMAP_PERMISSION_DENIED;
	} else if (code[4] == '5' && code[6] == '1') {
	    return IMAP_MAILBOX_NONEXISTENT;
	}
	return IMAP_PERMISSION_DENIED;
    case 552:
	if (code[6] == '2') {
	    return IMAP_QUOTA_EXCEEDED;
	} else if (code[6] == '3') {
	    return IMAP_MESSAGE_TOO_LARGE;
	}
	return IMAP_QUOTA_EXCEEDED;
    case 554:
	return IMAP_MESSAGE_BADHEADER; /* sigh, pick one */

    default:
	if (ISGOOD(c)) return 0;
	else if (TEMPFAIL(c)) return IMAP_AGAIN;
	else if (PERMFAIL(c)) return IMAP_PROTOCOL_ERROR;
	else return IMAP_AGAIN;
    }
}

static int ask_code(const char *s)
{
    int ret = 0;
    
    if (s==NULL) return -1;

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

    /* check to make sure 0-2 are digits */
    if ((isdigit((int) s[0])==0) ||
	(isdigit((int) s[1])==0) ||
	(isdigit((int) s[2])==0))
    {
	return -1;
    }

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

static void chop(char *s)
{
    char *p;

    assert(s);
    p = s + strlen(s) - 1;
    if (p[0] == '\n') {
	*p-- = '\0';
    }
    if (p >= s && p[0] == '\r') {
	*p-- = '\0';
    }
}

static int mysasl_getauthline(struct protstream *p, char **line, 
			      unsigned int *linelen)
{
    char buf[2096];
    char *str = (char *) buf;
    
    if (!prot_fgets(str, sizeof(buf), p)) {
	return SASL_FAIL;
    }
    if (str[0] == '2') { return SASL_OK; }
    if (str[0] == '5') { return SASL_BADAUTH; }
    if (str[0] != '3') { return SASL_BADPROT; }
    else {
	size_t len;
	str += 4; /* jump past the "334 " */

	len = strlen(str) + 1;

	*line = xmalloc(strlen(str) + 1);
	if (*str != '\r') {	/* decode it */
	    int r;
	    
	    r = sasl_decode64(str, strlen(str), *line, len, linelen);
	    if (r != SASL_OK) {
		return r;
	    }
	    
	    return SASL_CONTINUE;
	} else {		/* blank challenge */
	    *line = NULL;
	    *linelen = 0;

	    return SASL_CONTINUE;
	}
    }
}

/* getlastresp reads from 'pin' until we get an LMTP that isn't a continuation.
   it puts it in 'buf', which must be at least 'len' big.

   '*code' will contain the integer three digit response code.
   if a read failed, '*code == 400', a temporary failure. 

   returns an IMAP error code. */
static int getlastresp(char *buf, int len, int *code, struct protstream *pin)
{
    do {
	if (!prot_fgets(buf, len, pin)) {
	    *code = 400;
	    return IMAP_SERVER_UNAVAILABLE;
	}
    } while (ISCONT(buf));
    *code = ask_code(buf);

    return 0;
}

/* perform authentication against connection 'conn'
   returns the SMTP error code from the AUTH attempt */
static int do_auth(struct lmtp_conn *conn)
{
    int r;
    const int AUTH_ERROR = 420, AUTH_OK = 250;
    sasl_security_properties_t *secprops = NULL;
    struct sockaddr_in saddr_l;
    struct sockaddr_in saddr_r;
    socklen_t addrsize;
    char buf[2048];
    char *in;
    const char *out;
    unsigned int inlen, outlen;
    const char *mechusing;
    unsigned b64len;
    char localip[60], remoteip[60];

    secprops = mysasl_secprops(0);
    r = sasl_setprop(conn->saslconn, SASL_SEC_PROPS, secprops);
    if (r != SASL_OK) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: could not sasl_setprop the security properties");
	
	return AUTH_ERROR;
    }

    /* set the IP addresses */
    addrsize=sizeof(struct sockaddr_in);
    if (getpeername(conn->sock, (struct sockaddr *)&saddr_r, &addrsize) != 0) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: getpeername() failed");
	return AUTH_ERROR;
    }
    
    addrsize=sizeof(struct sockaddr_in);
    if (getsockname(conn->sock, (struct sockaddr *)&saddr_l,&addrsize)!=0) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: getsockname() failed");
	return AUTH_ERROR;
    }
    

    if (iptostring((struct sockaddr *)&saddr_r,
		   sizeof(struct sockaddr_in),
		   remoteip, sizeof(remoteip)) != 0) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: iptostring() (remote) failed");
	return AUTH_ERROR;
    }
    
    if (iptostring((struct sockaddr *)&saddr_l,
		   sizeof(struct sockaddr_in),
		   localip, sizeof(localip)) != 0) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: iptostring() (local) failed");	
	return AUTH_ERROR;
    }

    r = sasl_setprop(conn->saslconn, SASL_IPLOCALPORT, localip);
    if (r != SASL_OK) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: sasl_setprop(SASL_IPLOCALPORT) failed");
	return AUTH_ERROR;
    }
    
    r = sasl_setprop(conn->saslconn, SASL_IPREMOTEPORT, remoteip);
    if (r != SASL_OK) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: sasl_setprop(SASL_IPREMOTEPORT) failed");
	return AUTH_ERROR;
    }

    /* we now do the actual SASL exchange */
    r = sasl_client_start(conn->saslconn, 
			  conn->mechs,
			  NULL, &out, &outlen, &mechusing);
    if ((r != SASL_OK) && (r != SASL_CONTINUE)) {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: sasl_client_start failed (%s)",
	       sasl_errdetail(conn->saslconn));
	return AUTH_ERROR;
    }

    if (out == NULL) {
	prot_printf(conn->pout, "AUTH %s\r\n", mechusing);
    } else {
	/* send initial challenge */
	r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len);
	if (r != SASL_OK) {
	    syslog(LOG_ERR,
		   "lmtpengine do_auth: sasl_encode64[1] failed (%s)",
		   sasl_errstring(r, NULL, NULL));
	    return AUTH_ERROR;
	}
	
	prot_printf(conn->pout, "AUTH %s %s\r\n", mechusing, buf);
    }

    in = NULL;
    inlen = 0;
    r = mysasl_getauthline(conn->pin, &in, &inlen);
    while (r == SASL_CONTINUE) {
	r = sasl_client_step(conn->saslconn, in, inlen, NULL, &out, &outlen);
	if (in) { 
	    free(in);
	}
	if (r != SASL_OK && r != SASL_CONTINUE) {
	    syslog(LOG_ERR,
		   "lmtpengine do_auth: sasl_client_step failed (%s)",
		   sasl_errdetail(conn->saslconn));

	    return AUTH_ERROR;
	}

	r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len);
	if (r != SASL_OK) {
	    syslog(LOG_ERR,
		   "lmtpengine do_auth: sasl_encode64[2] failed (%s)",
		   sasl_errstring(r, NULL, NULL));
	    return AUTH_ERROR;
	}

	prot_write(conn->pout, buf, b64len);
	prot_printf(conn->pout, "\r\n");

	r = mysasl_getauthline(conn->pin, &in, &inlen);
    }

    if (r == SASL_OK) {
	prot_setsasl(conn->pin, conn->saslconn);
	prot_setsasl(conn->pout, conn->saslconn);

	/* success */
	return AUTH_OK;
    } else {
	syslog(LOG_ERR,
	       "lmtpengine do_auth: failed to authenticate");

	/* don't bounce the message just because *we* can't authenticate */
	return AUTH_ERROR;
    }
}

/* establish connection, LHLO, and AUTH if possible */
int lmtp_connect(const char *phost, 
		 sasl_callback_t *cb, 
		 struct lmtp_conn **ret)
{
    int sock = -1;
    char *host = xstrdup(phost);
    struct lmtp_conn *conn;
    char buf[8192];
    int code;
    int unix_socket = 0;
    
    assert(host);
    assert(ret);

    if (host[0] == '/') {
	struct sockaddr_un addr;

	/* open unix socket */
	if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
	    syslog(LOG_ERR, "socket() failed %m");
	    goto errsock;
	}
	addr.sun_family = AF_UNIX;
	strlcpy(addr.sun_path, host, sizeof(addr.sun_path));
	if (connect(sock, (struct sockaddr *) &addr, 
		    sizeof(addr.sun_family) + strlen(addr.sun_path) + 1) < 0) {
	    syslog(LOG_ERR, "connect(%s) failed: %m", addr.sun_path);
	    goto errsock;
	}

	/* set that we are preauthed */
	unix_socket = 1;

	/* change host to 'config_servername' */
	free(host);
	host = xstrdup(config_servername);
    } else {
	struct hostent *hp;
	struct sockaddr_in addr;
	struct servent *service;
	char *p;

	p = strchr(host, ':');
	if (p) {
	    *p++ = '\0';
	} else {
	    p = "lmtp";
	}

	if ((hp = gethostbyname(host)) == NULL) {
	    syslog(LOG_ERR, "gethostbyname(%s) failed", host);
	    goto errsock;
	}

	/* open inet socket */
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	    syslog(LOG_ERR, "socket() failed: %m");
	    goto errsock;
	}

	addr.sin_family = AF_INET;
	memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
	service = getservbyname(p, "tcp");
	if (service) {
	    addr.sin_port = service->s_port;
	} else {
	    int pn = atoi(p);
	    if (pn == 0) {
		syslog(LOG_ERR, "couldn't find valid lmtp port");
		goto errsock;
	    }
	    addr.sin_port = htons(pn);
	}

	if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
	    syslog(LOG_ERR, "connect(%s:%s) failed: %m", host, p);
	    goto errsock;
	}	    
    }
    
    conn = xmalloc(sizeof(struct lmtp_conn));
    conn->host = host;
    conn->sock = sock;
    conn->capability = 0;
    conn->mechs = NULL;
    conn->saslconn = NULL;
    /* setup prot layers */
    conn->pin = prot_new(sock, 0);
    conn->pout = prot_new(sock, 1);
    prot_setflushonread(conn->pin, conn->pout);

    /* read greeting */
    getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
    if (!ISGOOD(code)) goto done;

    /* LHLO */
    prot_printf(conn->pout, "LHLO %s\r\n", config_servername);
    /* read responses */
    for (;;) {
	if (prot_fgets(buf, sizeof(buf), conn->pin)) {
	    code = ask_code(buf);
	    if (code == 250) {
		chop(buf);
		/* check capability */
		if (!strcasecmp(buf + 4, "PIPELINING")) {
		    conn->capability |= CAPA_PIPELINING;
		}
		if (!strncasecmp(buf + 4, "AUTH ", 5)) {
		    conn->capability |= CAPA_AUTH;
		    /* save mechanisms for later */
		    conn->mechs = xstrdup(buf + 9);
		}
		if (!strcasecmp(buf + 4, "IGNOREQUOTA")) {
		    conn->capability |= CAPA_IGNOREQUOTA;
		}
	    }

	    if (ISCONT(buf) && ISGOOD(code)) {
		continue;
	    } else {
		break;
	    }
	}
	/* can't read response */
	code = 400; 
	break;
    }
    /* check status code */
    if (!ISGOOD(code)) goto done;

    /* AUTH (but only if we're not preauthed as postman!) */
    if (!unix_socket && (conn->capability & CAPA_AUTH) && (conn->mechs)) {
	sasl_client_new("lmtp", host, NULL, NULL, cb, 0, &conn->saslconn);
	code = do_auth(conn);
    }

 done:
    if (ISGOOD(code)) {
	/* return connection */
	*ret = conn;
	return 0;
    } else {
	/* not a successful connection; tear it down and return failure */
	if (conn) {
	    if (conn->host) free(conn->host);
	    if (conn->mechs) free(conn->mechs);
	    if (conn->saslconn) sasl_dispose(&conn->saslconn);
	    if (conn->sock) close(conn->sock);
	    free(conn);
	}
	return IMAP_SERVER_UNAVAILABLE;
    }

 errsock:
    /* error during connection */
    if (sock != -1) close(sock);
	
    free(host);
    return IMAP_IOERROR;
}

static void pushmsg(struct protstream *in, struct protstream *out,
		    int isdotstuffed)
{
    char buf[8192], *p;
    int lastline_hadendline = 1;

    /* -2: Might need room to add a \r\n\0 set */
    while (prot_fgets(buf, sizeof(buf)-2, in)) {
	/* dot stuff */
	if (!isdotstuffed && (lastline_hadendline == 1) && (buf[0]=='.')) {
	    prot_putc('.', out);
	}
	p = buf + strlen(buf) - 1;
	if (*p == '\n') {
	    if (p == buf || p[-1] != '\r') {
		p[0] = '\r';
		p[1] = '\n';
		p[2] = '\0';
	    }
	    lastline_hadendline = 1;
	}
	else if (*p == '\r') {
	    if (buf[0] == '\r' && buf[1] == '\0') {
		/* The message contained \r\0, and fgets is confusing us.
		   XXX ignored
		 */
		lastline_hadendline = 1;
	    } else {
		/*
		 * We were unlucky enough to get a CR just before we ran
		 * out of buffer--put it back.
		 */
		prot_ungetc('\r', in);
		*p = '\0';
		lastline_hadendline = 0;
	    }
	} else {
	    lastline_hadendline = 0;
	}

	/* Remove any lone CR characters */
	while ((p = strchr(buf, '\r')) && p[1] != '\n') {
	    /* Src/Target overlap, use memmove */
	    /* strlen(p) will result in copying the NUL byte as well */
	    memmove(p, p+1, strlen(p));
	}

	prot_write(out, buf, strlen(buf));
    }

    if (!isdotstuffed) {
	/* signify end of message */
	prot_printf(out, "\r\n.\r\n");
    }
}

int lmtp_runtxn(struct lmtp_conn *conn, struct lmtp_txn *txn)
{
    int j, code, r = 0;
    char buf[8192];
    int onegood;

    assert(conn && txn);
    /* pipelining v. no pipelining? */

    /* here's the straightforward non-pipelining version */

    /* rset */
    prot_printf(conn->pout, "RSET\r\n");
    r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
    if (!ISGOOD(code)) {
	goto failall;
    }

    /* mail from */
    if (!txn->from) {
	prot_printf(conn->pout, "MAIL FROM:<>");
    } else if (txn->from[0] == '<') {
	prot_printf(conn->pout, "MAIL FROM:%s", txn->from);
    } else {
	prot_printf(conn->pout, "MAIL FROM:<%s>", txn->from);
    }
    if (conn->capability & CAPA_AUTH) {
	prot_printf(conn->pout, " AUTH=%s", 
		    txn->auth && txn->auth[0] ? txn->auth : "<>");
    }
    prot_printf(conn->pout, "\r\n");
    r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
    if (!ISGOOD(code)) {
	goto failall;
    }

    /* rcpt to */
    onegood = 0;
    for (j = 0; j < txn->rcpt_num; j++) {
	prot_printf(conn->pout, "RCPT TO:<%s>", txn->rcpt[j].addr);
	if (txn->rcpt[j].ignorequota && (conn->capability & CAPA_IGNOREQUOTA)) {
	    prot_printf(conn->pout, " IGNOREQUOTA");
	}
	prot_printf(conn->pout, "\r\n");
	r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
	if (r) {
	    goto failall;
	}
	txn->rcpt[j].r = revconvert_lmtp(buf);
	if (ISGOOD(code)) {
	    onegood = 1;
	    txn->rcpt[j].result = RCPT_GOOD;
	} else if (TEMPFAIL(code)) {
	    txn->rcpt[j].result = RCPT_TEMPFAIL;
	} else if (PERMFAIL(code)) {
	    txn->rcpt[j].result = RCPT_PERMFAIL;
	} else {
	    /* yikes?!? */
	    code = 400;
	    goto failall;
	}
    }
    if (!onegood) {
	/* all recipients failed! */
	return 0;
    }

    /* data */
    prot_printf(conn->pout, "DATA\r\n");
    r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
    if (r) {
	goto failall;
    }
    if (code != 354) {
	/* erg? */
	if (ISGOOD(code)) code = 400;
	r = IMAP_PROTOCOL_ERROR;
	goto failall;
    }

    /* send the data, dot-stuffing as needed */
    pushmsg(txn->data, conn->pout, txn->isdotstuffed);

    /* read the response codes, one for each accepted RCPT TO */
    for (j = 0; j < txn->rcpt_num; j++) {
	if (txn->rcpt[j].result == RCPT_GOOD) {
	    /* expecting a status code for this recipient */
	    r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
	    if (r) {
		/* technically, some recipients might've succeeded here, 
		   but we'll be paranoid */
		goto failall;
	    }
	    txn->rcpt[j].r = revconvert_lmtp(buf);
	    if (ISGOOD(code)) {
		onegood = 1;
		txn->rcpt[j].result = RCPT_GOOD;
	    } else if (TEMPFAIL(code)) {
		txn->rcpt[j].result = RCPT_TEMPFAIL;
	    } else if (PERMFAIL(code)) {
		txn->rcpt[j].result = RCPT_PERMFAIL;
	    } else {
		/* yikes?!? */
		txn->rcpt[j].result = RCPT_TEMPFAIL;
	    }
	}
    }
    
    /* done with txn */
    return 0;
    
 failall:
    /* something fatal happened during the transaction; we should assign
       'code' to all recipients and return */
    for (j = 0; j < txn->rcpt_num; j++) {
	if (ISGOOD(code)) {
	    txn->rcpt[j].r = 0;
	    txn->rcpt[j].result = RCPT_GOOD;
	} else if (TEMPFAIL(code)) {
	    txn->rcpt[j].r = IMAP_AGAIN;
	    txn->rcpt[j].result = RCPT_TEMPFAIL;
	} else if (PERMFAIL(code)) {
	    txn->rcpt[j].r = IMAP_PROTOCOL_ERROR;
	    txn->rcpt[j].result = RCPT_PERMFAIL;
	} else {
	    /* code should have been a valid number */
	    abort();
	}
    }

    /* return overall error code already set */
    return r;
}

/* send a NOOP to the conn to verify it's still ok */
int lmtp_verify_conn(struct lmtp_conn *conn)
{
    char buf[8192];
    int r = 0;
    int code = 0;

    /* noop me */
    prot_printf(conn->pout, "NOOP\r\n");
    r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
    if (!r && !ISGOOD(code)) {
	r = IMAP_SERVER_UNAVAILABLE;
    }

    return r;
}

int lmtp_disconnect(struct lmtp_conn *conn)
{
    /* quit */
    prot_printf(conn->pout, "QUIT\r\n");
    /* wait for any response */
    prot_getc(conn->pin);

    /* close connection */
    close(conn->sock);

    /* free 'conn' */
    free(conn->host);
    prot_free(conn->pin);
    prot_free(conn->pout);
    if (conn->saslconn) sasl_dispose(&conn->saslconn);
    if (conn->mechs) free(conn->mechs);

    return 0;
}

/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(sasl_conn_t **conn) 
{
    int ret, secflags, plaintext_result;
    sasl_security_properties_t *secprops = NULL;

    sasl_dispose(conn);
    /* do initialization typical of service_main */
    ret = sasl_server_new("lmtp", config_servername,
                         NULL, NULL, NULL,
                         NULL, 0, conn);
    if(ret != SASL_OK) return ret;

    if(saslprops.ipremoteport)
       ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
                          saslprops.ipremoteport);
    if(ret != SASL_OK) return ret;
    
    if(saslprops.iplocalport)
       ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
                          saslprops.iplocalport);
    if(ret != SASL_OK) return ret;
    
    secflags = SASL_SEC_NOANONYMOUS;

    plaintext_result = config_getswitch("allowplaintext", 1);
    if (!config_getswitch("lmtp_allowplaintext", plaintext_result)) {
	secflags |= SASL_SEC_NOPLAINTEXT;
    }
    secprops = mysasl_secprops(secflags);
    ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
    if(ret != SASL_OK) return ret;
    /* end of service_main initialization excepting SSF */

    /* If we have TLS/SSL info, set it */
    if(saslprops.ssf) {
       ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
    }
    if(ret != SASL_OK) return ret;

    if(saslprops.authid) {
       ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
       if(ret != SASL_OK) return ret;
    }
    /* End TLS/SSL Info */

    return SASL_OK;
}
