/* proxyd.c -- IMAP server proxy for Cyrus Murder
 *
 * Copyright (c) 1998-2000 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.
 */

/* $Id: proxyd.c,v 1.130 2002/06/24 16:28:48 rjs3 Exp $ */

#undef PROXY_IDLE

#include <config.h>

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

#include <sasl/sasl.h>
#include <sasl/saslutil.h>

#include "prot.h"

#include "acl.h"
#include "annotate.h"
#include "util.h"
#include "auth.h"
#include "map.h"
#include "imapconf.h"
#include "tls.h"
#include "version.h"
#include "charset.h"
#include "imparse.h"
#include "iptostring.h"
#include "mkgmtime.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mboxname.h"
#include "mailbox.h"
#include "mupdate-client.h"
#include "xmalloc.h"
#include "mboxlist.h"
#include "imapurl.h"
#include "pushstats.h"
#include "telemetry.h"
#include "backend.h"

/* PROXY STUFF */
/* we want a list of our outgoing connections here and which one we're
   currently piping */
#define IDLE_TIMEOUT (5 * 60)

static const int ultraparanoid = 1; /* should we kick after every operation? */
static unsigned int proxyd_cmdcnt;

static int referral_kick = 0; /* kick after next command recieved, for
				 referrals that are likely to change the
				 mailbox list */

/* all subscription commands go to the backend server containing the
   user's inbox */
struct backend *backend_inbox = NULL;

/* the current server most commands go to */
struct backend *backend_current = NULL;

/* our cached connections */
struct backend **backend_cached = NULL;

/* has the client issued an RLIST or RLSUB? */
static int supports_referrals;

/* -------- from imapd ---------- */

extern int optind;
extern char *optarg;

extern int errno;

/* global state */
static char shutdownfilename[1024];
static int imaps = 0;
static sasl_ssf_t extprops_ssf = 0;

/* per-user session state */
struct protstream *proxyd_out = NULL;
struct protstream *proxyd_in = NULL;
char proxyd_clienthost[250] = "[local]";
time_t proxyd_logtime;
char *proxyd_userid = NULL;
struct auth_state *proxyd_authstate = 0;
int proxyd_userisadmin;
sasl_conn_t *proxyd_saslconn; /* the sasl connection context to the client */
int proxyd_starttls_done = 0; /* have we done a successful starttls yet? */
#ifdef HAVE_SSL
static SSL *tls_conn;
#endif /* HAVE_SSL */

/* current namespace */
static struct namespace proxyd_namespace;

void shutdown_file(int fd);
void motd_file(int fd);
void shut_down(int code);
void fatal(const char *s, int code);

void proxyd_downserver(struct backend *s);

void cmdloop(void);
void cmd_login(char *tag, char *user);
void cmd_authenticate(char *tag, char *authtype);
void cmd_noop(char *tag, char *cmd);
void cmd_capability(char *tag);
void cmd_append(char *tag, char *name);
void cmd_select(char *tag, char *cmd, char *name);
void cmd_close(char *tag);
void cmd_fetch(char *tag, char *sequence, int usinguid);
void cmd_partial(char *tag, char *msgno, char *data,
		 char *start, char *count);
void cmd_store(char *tag, char *sequence, char *operation, int usinguid);
void cmd_search(char *tag, int usinguid);
void cmd_sort(char *tag, int usinguid);
void cmd_thread(char *tag, int usinguid);
void cmd_copy(char *tag, char *sequence, char *name, int usinguid);
void cmd_expunge(char *tag, char *sequence);
void cmd_create(char *tag, char *name, char *partition);
void cmd_delete(char *tag, char *name);
void cmd_rename(char *tag, char *oldname, char *newname, char *partition);
void cmd_find(char *tag, char *namespace, char *pattern);
void cmd_list(char *tag, int subscribed, char *reference, char *pattern);
void cmd_changesub(char *tag, char *namespace, char *name, int add);
void cmd_getacl(char *tag, char *name, int oldform);
void cmd_listrights(char *tag, char *name, char *identifier);
void cmd_myrights(char *tag, char *name, int oldform);
void cmd_setacl(char *tag, char *name, char *identifier, char *rights);
void cmd_getquota(char *tag, char *name);
void cmd_getquotaroot(char *tag, char *name);
void cmd_setquota(char *tag, char *quotaroot);
void cmd_status(char *tag, char *name);
void cmd_getuids(char *tag, char *startuid);
void cmd_unselect(char* tag);
void cmd_namespace(char* tag);
void cmd_reconstruct(char *tag, char *name);

void cmd_id(char* tag);
struct idparamlist {
    char *field;
    char *value;
    struct idparamlist *next;
};
extern void id_getcmdline(int argc, char **argv);
extern void id_response(struct protstream *pout);
void id_appendparamlist(struct idparamlist **l, char *field, char *value);
void id_freeparamlist(struct idparamlist *l);

void cmd_idle(char* tag);
char idle_nomailbox(char* tag, int idle_period, struct buf *arg);
char idle_passthrough(char* tag, int idle_period, struct buf *arg);
char idle_simulate(char* tag, int idle_period, struct buf *arg);

void cmd_starttls(char *tag, int imaps);

#ifdef ENABLE_X_NETSCAPE_HACK
void cmd_netscape (char* tag);
#endif

#ifdef ENABLE_ANNOTATEMORE
void cmd_getannotation(char* tag);
void cmd_setannotation(char* tag);

int getannotatefetchdata(char *tag,
			 struct strlist **entries, struct strlist **attribs);
int getannotatestoredata(char *tag, struct entryattlist **entryatts);

void annotate_response(struct entryattlist *l);

void appendstrlist(struct strlist **l, char *s);
void freestrlist(struct strlist *l);
void appendattvalue(struct attvaluelist **l, char *attrib, char *value);
void freeattvalues(struct attvaluelist *l);
#endif /* ENABLE_ANNOTATEMORE */

void printstring (const char *s);
void printastring (const char *s);

static int mailboxdata(char *name, int matchlen, int maycreate, void *rock);
static int listdata(char *name, int matchlen, int maycreate, void *rock);
static void mstringdata(char *cmd, char *name, int matchlen, int maycreate);

static int mlookup(const char *name, char **pathp, 
		   char **aclp, void *tid);

/* 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};

#define BUFGROWSIZE 100

/* proxy support functions */
enum {
    PROXY_NOCONNECTION = -1,
    PROXY_OK = 0,
    PROXY_NO = 1,
    PROXY_BAD = 2
};

static void proxyd_gentag(char *tag)
{
    sprintf(tag, "PROXY%d", proxyd_cmdcnt++);
}

/* pipe_until_tag() reads from 's->in' until the tagged response
   starting with 'tag' appears.  it returns the result of the
   tagged command, and sets 's->last_result' with the tagged line. */

/* 's->last_result' assumes that tagged responses are:
   a) short
   b) don't contain literals
   
   IMAP grammar allows both, unfortunately */

/* force_notfatal says to not fatal() if we lose connection to backend_current
 * even though it is in 95% of the cases, a good idea... */
static int pipe_until_tag(struct backend *s, char *tag, int force_notfatal)
{
    char buf[2048];
    char eol[128];
    int sl;
    int cont = 0, last = 0, r = -1;
    int taglen = strlen(tag);

    s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
    
    /* the only complication here are literals */
    while (!last || cont) {
	/* if 'cont' is set, we're looking at the continuation to a very
	   long line.
	   if 'last' is set, we've seen the tag we're looking for, we're
	   just reading the end of the line, and we shouldn't echo it. */
	if (!cont) eol[0] = '\0';

	if (!prot_fgets(buf, sizeof(buf), s->in)) {
	    /* uh oh */
	    if(s == backend_current && !force_notfatal)
		fatal("Lost connection to selected backend", EC_UNAVAILABLE);
	    proxyd_downserver(s);
	    return PROXY_NOCONNECTION;
	}
	if (!cont && buf[taglen] == ' ' && !strncmp(tag, buf, taglen)) {
	    strlcpy(s->last_result, buf + taglen + 1, LAST_RESULT_LEN);
	    /* guarantee that 's->last_result' has \r\n\0 at the end */
	    s->last_result[LAST_RESULT_LEN - 3] = '\r';
	    s->last_result[LAST_RESULT_LEN - 2] = '\n';
	    s->last_result[LAST_RESULT_LEN - 1] = '\0';
	    switch (buf[taglen + 1]) {
	    case 'O': case 'o':
		r = PROXY_OK;
		break;
	    case 'N': case 'n':
		r = PROXY_NO;
		break;
	    case 'B': case 'b':
		r = PROXY_BAD;
		break;
	    default: /* huh? no result? */
		if(s == backend_current && !force_notfatal)
		    fatal("Lost connection to selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		r = PROXY_NOCONNECTION;
		break;
	    }

	    last = 1;
	}
	
	sl = strlen(buf);
	if (sl == (sizeof(buf) - 1)) { /* only got part of a line */
	    /* we save the last 64 characters in case it has important
	       literal information */
	    strcpy(eol, buf + sl - 64);

	    /* write out this part, but we have to keep reading until we
	       hit the end of the line */
	    if (!last) prot_write(proxyd_out, buf, sl);
	    cont = 1;
	    continue;
	} else {		/* we got the end of the line */
	    int i;
	    int litlen = 0, islit = 0;

	    if (!last) prot_write(proxyd_out, buf, sl);

	    /* now we have to see if this line ends with a literal */
	    if (sl < 64) {
		strcat(eol, buf);
	    } else {
		strcat(eol, buf + sl - 63);
	    }

	    /* eol now contains the last characters from the line; we want
	       to see if we've hit a literal */
	    i = strlen(eol);
	    if (eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
		/* possible literal */
		i -= 4;
		while (i > 0 && eol[i] != '{' && isdigit((int) eol[i])) {
		    i--;
		}
		if (eol[i] == '{') {
		    islit = 1;
		    litlen = atoi(eol + i + 1);
		}
	    }

	    /* copy the literal over */
	    if (islit) {
		while (litlen > 0) {
		    int j = (litlen > sizeof(buf) ? sizeof(buf) : litlen);
		    
		    j = prot_read(s->in, buf, j);
		    if(!j) {
			/* EOF or other error */
			return -1;
		    }
		    if (!last) prot_write(proxyd_out, buf, j);
		    litlen -= j;
		}

		/* none of our saved information has any relevance now */
		eol[0] = '\0';
		
		/* have to keep going for the end of the line */
		cont = 1;
		continue;
	    }
	}

	/* ok, let's read another line */
	cont = 0;
    }

    return r;
}

static int pipe_including_tag(struct backend *s, char *tag, int force_notfatal)
{
    int r;

    r = pipe_until_tag(s, tag, force_notfatal);
    switch (r) {
    case PROXY_OK:
    case PROXY_NO:
    case PROXY_BAD:
	prot_printf(proxyd_out, "%s %s", tag, s->last_result);
	break;
    case PROXY_NOCONNECTION:
	/* don't have to worry about downing the server, since
	 * pipe_until_tag does that for us */
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, 
		    error_message(IMAP_SERVER_UNAVAILABLE));
	break;
    }
    return r;
}

static int pipe_to_end_of_response(struct backend *s, int force_notfatal)
{
    char buf[2048];
    char eol[128];
    int sl;
    int cont = 1, r = PROXY_OK;

    s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
    
    eol[0]='\0';

    /* the only complication here are literals */
    while (cont) {
	/* if 'cont' is set, we're looking at the continuation to a very
	   long line. */
	if (!prot_fgets(buf, sizeof(buf), s->in)) {
	    /* uh oh */
	    if(s == backend_current && !force_notfatal)
		fatal("Lost connection to selected backend", EC_UNAVAILABLE);
	    proxyd_downserver(s);
	    return PROXY_NOCONNECTION;
	}
	
	sl = strlen(buf);
	if (sl == (sizeof(buf) - 1)) { /* only got part of a line */
	    /* we save the last 64 characters in case it has important
	       literal information */
	    strcpy(eol, buf + sl - 64);

	    /* write out this part, but we have to keep reading until we
	       hit the end of the line */
	    prot_write(proxyd_out, buf, sl);
	    cont = 1;
	    continue;
	} else {		/* we got the end of the line */
	    int i;
	    int litlen = 0, islit = 0;

	    prot_write(proxyd_out, buf, sl);

	    /* now we have to see if this line ends with a literal */
	    if (sl < 64) {
		strcat(eol, buf);
	    } else {
		strcat(eol, buf + sl - 63);
	    }

	    /* eol now contains the last characters from the line; we want
	       to see if we've hit a literal */
	    i = strlen(eol);
	    if (eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
		/* possible literal */
		i -= 4;
		while (i > 0 && eol[i] != '{' && isdigit((int) eol[i])) {
		    i--;
		}
		if (eol[i] == '{') {
		    islit = 1;
		    litlen = atoi(eol + i + 1);
		}
	    }

	    /* copy the literal over */
	    if (islit) {
		while (litlen > 0) {
		    int j = (litlen > sizeof(buf) ? sizeof(buf) : litlen);
		    
		    j = prot_read(s->in, buf, j);
		    if(!j) {
			/* EOF or other error */
			return -1;
		    }
		    prot_write(proxyd_out, buf, j);
		    litlen -= j;
		}

		/* none of our saved information has any relevance now */
		eol[0] = '\0';
		
		/* have to keep going for the end of the line */
		cont = 1;
		continue;
	    }
	}

	/* ok, if we're here, we're done */
	cont = 0;
    }

    return r;
}


/* copy our current input to 's' until we hit a true EOL.

   'optimistic_literal' is how happy we should be about assuming
   that a command will go through by converting synchronizing literals of
   size less than optimistic_literal to nonsync

   returns 0 on success, <0 on big failure, >0 on full command not sent */
static int pipe_command(struct backend *s, int optimistic_literal)
{
    char buf[2048];
    char eol[128];
    int sl;

    s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
    
    eol[0] = '\0';

    /* again, the complication here are literals */
    for (;;) {
	if (!prot_fgets(buf, sizeof(buf), proxyd_in)) {
	    /* uh oh */
	    return -1;
	}

	sl = strlen(buf);
	if (sl == (sizeof(buf) - 1)) { /* only got part of a line */
	    strcpy(eol, buf + sl - 64);

	    /* and write this out, except for what we've saved */
	    prot_write(s->out, buf, sl - 64);
	    continue;
	} else {
	    int i, nonsynch = 0, islit = 0, litlen = 0;

	    if (sl < 64) {
		strcat(eol, buf);
	    } else {
		/* write out what we have, and copy the last 64 characters
		   to eol */
		prot_printf(s->out, "%s", eol);
		prot_write(s->out, buf, sl - 64);
		strcpy(eol, buf + sl - 64);
	    }

	    /* now determine if eol has a literal in it */
	    i = strlen(eol);
	    if (eol[i-1] == '\n' && eol[i-2] == '\r' && eol[i-3] == '}') {
		/* possible literal */
		i -= 4;
		if (eol[i] == '+') {
		    nonsynch = 1;
		    i--;
		}
		while (i > 0 && eol[i] != '{' && isdigit((int) eol[i])) {
		    i--;
		}
		if (eol[i] == '{') {
		    islit = 1;
		    litlen = atoi(eol + i + 1);
		}
	    }

	    if (islit) {
		if (nonsynch) {
		    prot_write(s->out, eol, strlen(eol));
		} else if (!nonsynch && (litlen <= optimistic_literal)) {
		    prot_printf(proxyd_out, "+ i am an optimist\r\n");
		    prot_write(s->out, eol, strlen(eol) - 3);
		    /* need to insert a + to turn it into a nonsynch */
		    prot_printf(s->out, "+}\r\n");
		} else {
		    /* we do a standard synchronizing literal */
		    prot_write(s->out, eol, strlen(eol));
		    /* but here the game gets tricky... */
		    prot_fgets(buf, sizeof(buf), s->in);
		    /* but for now we cheat */
		    prot_write(proxyd_out, buf, strlen(buf));
		    if (buf[0] != '+' && buf[1] != ' ') {
			/* char *p = strchr(buf, ' '); */
			/* strncpy(s->last_result, p + 1, LAST_RESULT_LEN);*/

			/* stop sending command now */
			return 1;
		    }
		}

		/* gobble literal and sent it onward */
		while (litlen > 0) {
		    int j = (litlen > sizeof(buf) ? sizeof(buf) : litlen);

		    j = prot_read(proxyd_in, buf, j);
		    if(!j) {
			/* EOF or other error */
			return -1;
		    }
		    prot_write(s->out, buf, j);
		    litlen -= j;
		}

		eol[0] = '\0';
		
		/* have to keep going for the send of the command */
		continue;
	    } else {
		/* no literal, so we're done! */
		prot_write(s->out, eol, strlen(eol));

		return 0;
	    }
	}
    }
}

/* This handles piping of the LSUB command, because we have to figure out
 * what mailboxes actually exist before passing them to the end user.
 *
 * It is also needed if we are doing a FIND MAILBOXES, for that we do an
 * LSUB on the backend anyway, because the semantics of FIND do not allow
 * it to return nonexistant mailboxes (RFC1176), but we need to really dumb
 * down the response when this is the case.
 */
static int pipe_lsub(struct backend *s, char *tag, int force_notfatal,
		     int for_find) 
{
    int taglen = strlen(tag);
    int c;
    int r = PROXY_OK;
    int exist_r;
    char mailboxname[MAX_MAILBOX_PATH + 1];
    static struct buf tagb, cmd, sep, name;
    int cur_flags_size = 64;
    char *flags = xmalloc(cur_flags_size);

    const char *end_strip_flags[] = { " \\NonExistent)", "\\NonExistent)",
				      " \\Noselect)", "\\Noselect)",
				      NULL };
    const char *mid_strip_flags[] = { "\\NonExistent ",
				      "\\Noselect ",
				      NULL 
				    };

    assert(s);
    assert(s->timeout);
    
    s->timeout->mark = time(NULL) + IDLE_TIMEOUT;

    while(1) {
	c = getword(s->in, &tagb);

	if(c == EOF) {
	    if(s == backend_current && !force_notfatal)
		fatal("Lost connection to selected backend", EC_UNAVAILABLE);
	    proxyd_downserver(s);
	    free(flags);
	    return PROXY_NOCONNECTION;
	}

	if(!strncmp(tag, tagb.s, taglen)) {
	    char buf[2048];
	    if(!prot_fgets(buf, sizeof(buf), s->in)) {
		if(s == backend_current && !force_notfatal)
		    fatal("Lost connection to selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		free(flags);
		return PROXY_NOCONNECTION;
	    }	
	    /* Got the end of the response */
	    strlcpy(s->last_result, buf, LAST_RESULT_LEN);
	    /* guarantee that 's->last_result' has \r\n\0 at the end */
	    s->last_result[LAST_RESULT_LEN - 3] = '\r';
	    s->last_result[LAST_RESULT_LEN - 2] = '\n';
	    s->last_result[LAST_RESULT_LEN - 1] = '\0';
	    switch (buf[0]) {
	    case 'O': case 'o':
		r = PROXY_OK;
		break;
	    case 'N': case 'n':
		r = PROXY_NO;
		break;
	    case 'B': case 'b':
		r = PROXY_BAD;
		break;
	    default: /* huh? no result? */
		if(s == backend_current && !force_notfatal)
		    fatal("Lost connection to selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		r = PROXY_NOCONNECTION;
		break;
	    }
	    break; /* we're done */
	}

	c = getword(s->in, &cmd);

	if(c == EOF) {
	    if(s == backend_current && !force_notfatal)
		fatal("Lost connection to selected backend", EC_UNAVAILABLE);
	    proxyd_downserver(s);
	    free(flags);
	    return PROXY_NOCONNECTION;
	}

	if(strncasecmp("LSUB", cmd.s, 4)) {
	    prot_printf(proxyd_out, "%s %s ", tagb.s, cmd.s);
	    r = pipe_to_end_of_response(s, force_notfatal);
	    if(r != PROXY_OK) {
		free(flags);
		return r;
	    }
	} else {
	    /* build up the response bit by bit */
	    int i = 0;
	    char *p;

	    c = prot_getc(s->in);
	    while(c != ')' && c != EOF) {
		flags[i++] = c;
		if(i == cur_flags_size) {
		    /* expand our buffer */
		    cur_flags_size *= 2;
		    flags = xrealloc(flags, cur_flags_size);
		}
		c = prot_getc(s->in);
	    }

	    if(c != EOF) {
		/* terminate string */
		flags[i++] = ')';
		if(i == cur_flags_size) {
		    /* expand our buffer */
		    cur_flags_size *= 2;
		    flags = xrealloc(flags, cur_flags_size);
		}
		flags[i] = '\0';
		/* get the next character */
 		c = prot_getc(s->in);
	    }
	    
	    if(c != ' ') {
		if(s == backend_current && !force_notfatal)
		    fatal("Bad LSUB response from selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		free(flags);
		return PROXY_NOCONNECTION;
	    }

	    /* Check for flags that we should remove
	     * (e.g. NoSelect, NonExistent) */
	    for(i=0; end_strip_flags[i]; i++) {
		p = strstr(flags, end_strip_flags[i]);
		if(p) {
		    *p = ')';
		    *(p+1) = '\0';
		}
	    }

	    for(i=0; mid_strip_flags[i]; i++) {
		p = strstr(flags, mid_strip_flags[i]);
		while(p) {
		    strcpy(p, p + 13);
		    p = strstr(flags, mid_strip_flags[i]);
		}
	    }
		
	    /* Get separator */
	    c = getastring(s->in, s->out, &sep);

	    if(c != ' ') {
		if(s == backend_current && !force_notfatal)
		    fatal("Bad LSUB response from selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		free(flags);
		return PROXY_NOCONNECTION;
	    }

	    /* Get name */
	    c = getastring(s->in, s->out, &name);

	    if(c == '\r') c = prot_getc(s->in);
	    if(c != '\n') {
		if(s == backend_current && !force_notfatal)
		    fatal("Bad LSUB response from selected backend",
			  EC_UNAVAILABLE);
		proxyd_downserver(s);
		free(flags);
		return PROXY_NOCONNECTION;
	    }

	    /* lookup name */
	    exist_r = 1;
	    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
							name.s,
							proxyd_userid,
							mailboxname);
	    if (!r) {
		int mbtype;
		exist_r = mboxlist_detail(mailboxname, &mbtype,
					  NULL, NULL, NULL, NULL);
		if(!exist_r && (mbtype & MBTYPE_RESERVE))
		    exist_r = IMAP_MAILBOX_RESERVED;
	    } else {
		/* skip this one */
		syslog(LOG_ERR, "could not convert %s to internal form",
		       name.s);
		continue;
	    }

	    /* send our response */
	    /* we need to set \Noselect if it's not in our mailboxes.db */
	    if(!for_find) {
		if(!exist_r) {
		    prot_printf(proxyd_out, "* LSUB %s \"%s\" ",
				flags, sep.s);
		} else {
		    prot_printf(proxyd_out, "* LSUB (\\Noselect) \"%s\" ",
				sep.s);
		}

		printstring(name.s);
		prot_printf(proxyd_out, "\r\n");

	    } else if(for_find && !exist_r) {
		/* Note that it has to exist for a find response */

		/* xxx what happens if name.s isn't an atom? */
		prot_printf(proxyd_out, "* MAILBOX %s\r\n",name.s);
	    }
	}
    } /* while(1) */

    free(flags);
    return r;
}

void proxyd_downserver(struct backend *s)
{
    if (!s || !s->timeout) {
	/* already disconnected */
	return;
    }

    /* need to logout of server */
    downserver(s);

    if(s == backend_inbox) backend_inbox = NULL;
    if(s == backend_current) backend_current = NULL;

    /* remove the timeout */
    prot_removewaitevent(proxyd_in, s->timeout);
    s->timeout = NULL;
}

struct prot_waitevent *backend_timeout(struct protstream *s,
				       struct prot_waitevent *ev, void *rock)
{
    struct backend *be = (struct backend *) rock;

    if (be != backend_current) {
	/* server is not our current server, and idle too long.
	 * down the backend server (removes the event as a side-effect)
	 */
	proxyd_downserver(be);
	return NULL;
    }
    else {
	/* it will timeout in IDLE_TIMEOUT seconds from now */
	ev->mark = time(NULL) + IDLE_TIMEOUT;
	return ev;
    }
}

/* return the connection to the server */
struct backend *proxyd_findserver(char *server)
{
    int i = 0;
    struct backend *ret = NULL;

    while (backend_cached && backend_cached[i]) {
	if (!strcmp(server, backend_cached[i]->hostname)) {
	    /* xxx do we want to ping/noop the server here? */
	    ret = backend_cached[i];
	    break;
	}
	i++;
    }

    if (!ret || !ret->timeout) {
	/* need to (re)establish connection to server or create one */
	ret = findserver(ret, server, proxyd_userid);
	if(!ret) return NULL;

	/* add the timeout */
	ret->timeout = prot_addwaitevent(proxyd_in, time(NULL) + IDLE_TIMEOUT,
					 backend_timeout, ret);
    }

    ret->timeout->mark = time(NULL) + IDLE_TIMEOUT;

    /* insert server in list of cached connections */
    if (!backend_cached[i]) {
	backend_cached = (struct backend **) 
	    xrealloc(backend_cached, (i + 2) * sizeof(struct backend *));
	backend_cached[i] = ret;
	backend_cached[i + 1] = NULL;
    }

    return ret;
}

static void kick_mupdate(void)
{
    char buf[2048];
    struct sockaddr_un srvaddr;
    int s, r;
    int len;
    
    s = socket(AF_UNIX, SOCK_STREAM, 0);
    if (s == -1) {
	syslog(LOG_ERR, "socket: %m");
	return;
    }

    strncpy(buf, config_dir, sizeof(buf));
    strncat(buf, FNAME_MUPDATE_TARGET_SOCK, sizeof(buf));
    memset((char *)&srvaddr, 0, sizeof(srvaddr));
    srvaddr.sun_family = AF_UNIX;
    strcpy(srvaddr.sun_path, buf);
    len = sizeof(srvaddr.sun_family) + strlen(srvaddr.sun_path) + 1;

    r = connect(s, (struct sockaddr *)&srvaddr, len);
    if (r == -1) {
	syslog(LOG_ERR, "kick_mupdate: can't connect to target: %m");
	close(s);
	return;
    }

    r = read(s, &buf, sizeof(buf));
    if (r <= 0) {
	syslog(LOG_ERR, "kick_mupdate: can't read from target: %m");
	close(s);
	return;
    }

    /* if we got here, it's been kicked */
    close(s);
    return;
}

/* proxy mboxlist_lookup; on misses, it asks the listener for this
   machine to make a roundtrip to the master mailbox server to make
   sure it's up to date */
static int mlookup(const char *name, char **pathp, 
		   char **aclp, void *tid)
{
    int r;

    if(pathp) *pathp = NULL;

    r = mboxlist_lookup(name, pathp, aclp, tid);
    if (r == IMAP_MAILBOX_NONEXISTENT) {
	kick_mupdate();
	r = mboxlist_lookup(name, pathp, aclp, tid);
    }

    /* xxx hide the fact that we are storing partitions */
    if(pathp && *pathp) {
	char *c;
	c = strchr(*pathp, '!');
	if(c) *c = '\0';
    }
    return r;
}

static struct backend *proxyd_findinboxserver(void)
{
    char inbox[MAX_MAILBOX_NAME];
    int r;
    char *server = NULL;
    struct backend *s = NULL;

    if (strlen(proxyd_userid) > MAX_MAILBOX_NAME - 30) return NULL;
    strcpy(inbox, "user.");
    strcat(inbox, proxyd_userid);
	
    r = mlookup(inbox, &server, NULL, NULL);
    if (!r) {
	s = proxyd_findserver(server);
    }

    return s;
}

/* proxyd_refer() issues a referral to the client. */
static void proxyd_refer(const char *tag,
			 const char *server,
			 const char *mailbox)
{
    char url[MAX_MAILBOX_PATH];

    if(!strcmp(proxyd_userid, "anonymous")) {
	imapurl_toURL(url, server, mailbox, "ANONYMOUS");
    } else {
	imapurl_toURL(url, server, mailbox, "*");
    }
    
    prot_printf(proxyd_out, "%s NO [REFERRAL %s] Remote mailbox.\r\n", 
		tag, url);
}

/*
 * acl_ok() checks to see if the the inbox for 'user' grants the 'a'
 * right to the principal 'auth_identity'. Returns 1 if so, 0 if not.
 */
static int acl_ok(const char *user, const char *auth_identity)
{
    char *acl;
    char inboxname[1024];
    int r;
    struct auth_state *authstate;

    if (strchr(user, '.') || strlen(user)+6 >= sizeof(inboxname)) return 0;

    strcpy(inboxname, "user.");
    strcat(inboxname, user);

    if (!(authstate = auth_newstate(auth_identity, (char *)0)) ||
	mlookup(inboxname, (char **)0, &acl, NULL)) {
	r = 0;  /* Failed so assume no proxy access */
    }
    else {
	r = (cyrus_acl_myrights(authstate, acl) & ACL_ADMIN) != 0;
    }
    if (authstate) auth_freestate(authstate);
    return r;
}

/* should we allow users to proxy?  return SASL_OK if yes,
   SASL_BADAUTH otherwise */
static int mysasl_authproc(sasl_conn_t *conn,
			   void *context,
			   const char *requested_user, unsigned rlen,
			   const char *auth_identity, unsigned alen,
			   const char *def_realm, unsigned urlen,
			   struct propctx *propctx)
{
    const char *val;
    char *realm;

    /* check if remote realm */
    if ((realm = strchr(auth_identity, '@'))!=NULL) {
	realm++;
	val = config_getstring("loginrealms", "");
	while (*val) {
	    if (!strncasecmp(val, realm, strlen(realm)) &&
		(!val[strlen(realm)] || isspace((int) val[strlen(realm)]))) {
		break;
	    }
	    /* not this realm, try next one */
	    while (*val && !isspace((int) *val)) val++;
	    while (*val && isspace((int) *val)) val++;
	}
	if (!*val) {
	    sasl_seterror(conn, 0, "cross-realm login %s denied",
			  auth_identity);
	    return SASL_BADAUTH;
	}
    }

    proxyd_authstate = auth_newstate(auth_identity, NULL);

    /* ok, is auth_identity an admin? */
    proxyd_userisadmin = authisa(proxyd_authstate, "imap", "admins");

    if (strcmp(auth_identity, requested_user)) {
	/* we want to authenticate as a different user; we'll allow this
	   if we're an admin or if we've allowed ACL proxy logins */
	int use_acl = config_getswitch("loginuseacl", 0);

	if (proxyd_userisadmin ||
	    (use_acl && acl_ok(requested_user, auth_identity)) ||
	    authisa(proxyd_authstate, "imap", "proxyservers")) {
	    /* proxy ok! */

	    proxyd_userisadmin = 0;	/* no longer admin */
	    auth_freestate(proxyd_authstate);
	    
	    proxyd_authstate = auth_newstate(requested_user, NULL);
	} else {
	    sasl_seterror(conn, 0, "user %s is not allowed to proxy", auth_identity);
	    
	    auth_freestate(proxyd_authstate);
	    
	    return SASL_BADAUTH;
	}
    }

    return SASL_OK;
}

static struct sasl_callback mysasl_cb[] = {
    { SASL_CB_GETOPT, &mysasl_config, NULL },
    { SASL_CB_PROXY_POLICY, &mysasl_authproc, NULL },
    { SASL_CB_CANON_USER, &mysasl_canon_user, NULL },   
    { SASL_CB_LIST_END, NULL, NULL }
};

extern void setproctitle_init(int argc, char **argv, char **envp);
extern int proc_register(const char *progname, const char *clienthost, 
			 const char *userid, const char *mailbox);
extern void proc_cleanup(void);

/*
 * run once when process is forked;
 * MUST NOT exit directly; must return with non-zero error code
 */
int service_init(int argc, char **argv, char **envp)
{
    int opt;
    int r;

    config_changeident("proxyd");
    if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
    setproctitle_init(argc, argv, envp);

    /* set signal handlers */
    signals_set_shutdown(&shut_down);
    signals_add_handlers();
    signal(SIGPIPE, SIG_IGN);

    /* set the SASL allocation functions */
    sasl_set_alloc((sasl_malloc_t *) &xmalloc, 
		   (sasl_calloc_t *) &calloc, 
		   (sasl_realloc_t *) &xrealloc, 
		   (sasl_free_t *) &free);

    /* load the SASL plugins */
    if ((r = sasl_server_init(mysasl_cb, "Cyrus")) != SASL_OK) {
	syslog(LOG_ERR, "SASL failed initializing: sasl_server_init(): %s", 
	       sasl_errstring(r, NULL, NULL));
	return EC_SOFTWARE;
    }

    if ((r = sasl_client_init(NULL)) != SASL_OK) {
	syslog(LOG_ERR, "SASL failed initializing: sasl_client_init(): %s", 
	       sasl_errstring(r, NULL, NULL));
	return EC_SOFTWARE;
    }

    /* open the mboxlist, we'll need it for real work */
    mboxlist_init(0);
    mboxlist_open(NULL);

    while ((opt = getopt(argc, argv, "C:Dsp:")) != EOF) {
	switch (opt) {
	case 'C': /* alt config file - handled by service::main() */
	    break;
	case 'D': /* ext. debugger - handled by service::main() */
 	    break;
	case 's': /* imaps (do starttls right away) */
	    imaps = 1;
	    if (!tls_enabled("imap")) {
		syslog(LOG_ERR, "imaps: required OpenSSL options not present");
		fatal("imaps: required OpenSSL options not present",
		      EC_CONFIG);
	    }
	    break;
	case 'p': /* external protection */
	    extprops_ssf = atoi(optarg);
	    break;
	default:
	    break;
	}
    }

    return 0;
}

static void proxyd_reset(void) 
{
    int i;
    
    proc_cleanup();

    /* close backend connections */
    i = 0;
    while (backend_cached[i]) {
	proxyd_downserver(backend_cached[i]);
	free(backend_cached[i]);
	i++;
    }
    free(backend_cached);
    backend_cached = NULL;
    backend_inbox = backend_current = NULL;

    /* Cleanup file descriptors. note: after last call to proxyd_downserver */
    if(proxyd_in) {
	prot_NONBLOCK(proxyd_in);
	prot_fill(proxyd_in);
	
	prot_free(proxyd_in);
    }
    if(proxyd_out) {
	prot_flush(proxyd_out);
	prot_free(proxyd_out);
    }
    
    proxyd_in = proxyd_out = NULL;
    close(0);
    close(1);
    close(2);
    
    /* Cleanup Globals */
    proxyd_cmdcnt = 0;
    supports_referrals = 0;
    proxyd_userisadmin = 0;
    proxyd_starttls_done = 0;
    proxyd_logtime = 0;

    strcpy(proxyd_clienthost, "[local]");

    if(proxyd_userid) {
	free(proxyd_userid);
	proxyd_userid = NULL;
    }
    if(proxyd_authstate) {
	auth_freestate(proxyd_authstate);
	proxyd_authstate = NULL;
    }

    /* Cleanup SASL */
    if(proxyd_saslconn) {
	sasl_dispose(&proxyd_saslconn);
	proxyd_saslconn = NULL;
    }
    if(saslprops.iplocalport) {
	free(saslprops.iplocalport);
	saslprops.iplocalport = NULL;
    }
    if(saslprops.ipremoteport) {
	free(saslprops.ipremoteport);
	saslprops.ipremoteport = NULL;
    }
    if(saslprops.authid) {
	free(saslprops.authid);
	saslprops.authid = NULL;
    }
    saslprops.ssf = 0;

#ifdef HAVE_SSL
    if (tls_conn) {
	if (tls_reset_servertls(&tls_conn) == -1) {
	    fatal("tls_reset() failed", EC_TEMPFAIL);
	}
	tls_conn = NULL;
    }
#endif
}

int service_main(int argc, char **argv, char **envp)
{
    socklen_t salen;
    struct hostent *hp;
    struct sockaddr_in proxyd_localaddr, proxyd_remoteaddr;
    char localip[60], remoteip[60];
    int timeout;
    int proxyd_haveaddr = 0;
    sasl_security_properties_t *secprops = NULL;

    signals_poll();

#ifdef ID_SAVE_CMDLINE
    /* get command line args for use in ID before getopt mangles them */
    id_getcmdline(argc, argv);
#endif

    proxyd_in = prot_new(0, 0);
    proxyd_out = prot_new(1, 1);

    /* Find out name of client host */
    salen = sizeof(proxyd_remoteaddr);
    if (getpeername(0, (struct sockaddr *)&proxyd_remoteaddr, &salen) == 0 &&
	proxyd_remoteaddr.sin_family == AF_INET) {
	hp = gethostbyaddr((char *)&proxyd_remoteaddr.sin_addr,
			   sizeof(proxyd_remoteaddr.sin_addr), AF_INET);
	if (hp != NULL) {
	    strncpy(proxyd_clienthost,hp->h_name,sizeof(proxyd_clienthost)-30);
	    proxyd_clienthost[sizeof(proxyd_clienthost)-30] = '\0';
	} else {
	    proxyd_clienthost[0] = '\0';
	}
	strcat(proxyd_clienthost, "[");
	strcat(proxyd_clienthost, inet_ntoa(proxyd_remoteaddr.sin_addr));
	strcat(proxyd_clienthost, "]");
	salen = sizeof(proxyd_localaddr);
	if (getsockname(0, (struct sockaddr *)&proxyd_localaddr,
			&salen) == 0) {
	    if(iptostring((struct sockaddr *)&proxyd_remoteaddr,
			  sizeof(struct sockaddr_in), remoteip, 60) == 0
	       && iptostring((struct sockaddr *)&proxyd_localaddr,
			     sizeof(struct sockaddr_in), localip, 60) == 0) {
		proxyd_haveaddr = 1;
	    }
	}
    }

    /* create the SASL connection */
    /* Make a SASL connection and setup some properties for it */
    /* other params should be filled in */
    if (sasl_server_new("imap", config_servername, 
			NULL,
			(proxyd_haveaddr ? localip : NULL),
			(proxyd_haveaddr ? remoteip : NULL),
			NULL, 0, 
			&proxyd_saslconn) != SASL_OK) {
	fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL); 
    }

    if(proxyd_haveaddr) {
        saslprops.ipremoteport = xstrdup(remoteip);
        saslprops.iplocalport = xstrdup(localip);
    }    

    secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
    sasl_setprop(proxyd_saslconn, SASL_SEC_PROPS, secprops);
    sasl_setprop(proxyd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf);

    proc_register("proxyd", proxyd_clienthost, (char *)0, (char *)0);

    /* Set inactivity timer */
    timeout = config_getint("timeout", 30);
    if (timeout < 30) timeout = 30;
    prot_settimeout(proxyd_in, timeout*60);
    prot_setflushonread(proxyd_in, proxyd_out);

    /* setup the cache */
    backend_cached = xmalloc(sizeof(struct backend *));
    backend_cached[0] = NULL;

    /* we were connected on imaps port so we should do 
       TLS negotiation immediately */
    if (imaps == 1) cmd_starttls(NULL, 1);

    cmdloop();
    
    /* cleanup */    
    prot_flush(proxyd_out);
    proxyd_reset();
    
    /* return to service another connection */
    return 0;
}

/* called if 'service_init()' was called but not 'service_main()' */
void service_abort(int error)
{
    shut_down(error);
}

/*
 * found a motd file; spit out message and return
 */
void motd_file(int fd)
{
    struct protstream *motd_in;
    char buf[1024];
    char *p;

    motd_in = prot_new(fd, 0);

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

    for(p = buf; *p == '['; p++); /* can't have [ be first char, sigh */
    prot_printf(proxyd_out, "* OK [ALERT] %s\r\n", p);
}

/*
 * Found a shutdown file: Spit out an untagged BYE and shut down
 */
void shutdown_file(int fd)
{
    struct protstream *shutdown_in;
    char buf[1024];
    char *p;

    shutdown_in = prot_new(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;

    for (p = buf; *p == '['; p++); /* can't have [ be first char, sigh */
    prot_printf(proxyd_out, "* BYE [ALERT] %s\r\n", p);

    shut_down(0);
}

/*
 * Cleanly shut down and exit
 */
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
    int i;

    proc_cleanup();

    i = 0;
    while (backend_cached && backend_cached[i]) {
	proxyd_downserver(backend_cached[i]);
	free(backend_cached[i]);
	i++;
    }

    mboxlist_close();
    mboxlist_done();

    if (proxyd_in) {
	prot_NONBLOCK(proxyd_in);
	prot_fill(proxyd_in);

	prot_free(proxyd_in);
    }

    if (proxyd_out) {
	prot_flush(proxyd_out);

	prot_free(proxyd_out);
    }

#ifdef HAVE_SSL
    tls_shutdown_serverengine();
#endif

    exit(code);
}

void fatal(const char *s, int code)
{
    static int recurse_code = 0;

    if (recurse_code) {
	/* We were called recursively. Just give up */
	proc_cleanup();
	exit(recurse_code);
    }
    recurse_code = code;
    if (proxyd_out) {
	prot_printf(proxyd_out, "* BYE Fatal error: %s\r\n", s);
	prot_flush(proxyd_out);
    }
    shut_down(code);
}

/*
 * Top-level command loop parsing
 */
void cmdloop()
{
    int fd;
    char motdfilename[1024];
    char hostname[MAXHOSTNAMELEN+1];
    int c;
    int usinguid, havepartition, havenamespace, oldform;
    static struct buf tag, cmd, arg1, arg2, arg3, arg4;
    char *p;
    const char *err;

    sprintf(shutdownfilename, "%s/msg/shutdown", config_dir);

    gethostname(hostname, sizeof(hostname));
    prot_printf(proxyd_out,
		"* OK %s Cyrus IMAP4 Murder %s server ready\r\n", hostname,
		CYRUS_VERSION);

    sprintf(motdfilename, "%s/msg/motd", config_dir);
    if ((fd = open(motdfilename, O_RDONLY, 0)) != -1) {
	motd_file(fd);
	close(fd);
    }

    for (;;) {
	if (! proxyd_userisadmin &&
	    (fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
	    shutdown_file(fd);
	}

	signals_poll();

	/* Parse tag */
	c = getword(proxyd_in, &tag);
	if (c == EOF) {
	    err = prot_error(proxyd_in);
	    if (err) {
		syslog(LOG_WARNING, "PROTERR: %s", err);
		prot_printf(proxyd_out, "* BYE %s\r\n", err);
	    }
	    return;
	}
	if (c != ' ' || !imparse_isatom(tag.s) || 
	    (tag.s[0] == '*' && !tag.s[1])) {
	    prot_printf(proxyd_out, "* BAD Invalid tag\r\n");
	    eatline(proxyd_in, c);
	    continue;
	}

	/* Parse command name */
	c = getword(proxyd_in, &cmd);
	if (!cmd.s[0]) {
	    prot_printf(proxyd_out, "%s BAD Null command\r\n", tag.s);
	    eatline(proxyd_in, c);
	    continue;
	}
	if (islower((unsigned char) cmd.s[0])) cmd.s[0] = toupper(cmd.s[0]);
	for (p = &cmd.s[1]; *p; p++) {
	    if (isupper((unsigned char) *p)) *p = tolower(*p);
	}

	/* if we need to force a kick, do so */
	if(referral_kick) {
	    kick_mupdate();
	    referral_kick = 0;
	}
	
	/* Only Authenticate/Login/Logout/Noop/Starttls 
	   allowed when not logged in */
	if (!proxyd_userid && !strchr("ALNCIS", cmd.s[0])) goto nologin;
    	switch (cmd.s[0]) {
	case 'A':
	    if (!strcmp(cmd.s, "Authenticate")) {
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg1);
		if (!imparse_isatom(arg1.s)) {
		    prot_printf(proxyd_out, 
				"%s BAD Invalid authenticate mechanism\r\n", 
				tag.s);
		    eatline(proxyd_in, c);
		    continue;
		}
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		
		if (proxyd_userid) {
		    prot_printf(proxyd_out, 
				"%s BAD Already authenticated\r\n", tag.s);
		    continue;
		}
		cmd_authenticate(tag.s, arg1.s);
	    }
	    else if (!proxyd_userid) goto nologin;
	    else if (!strcmp(cmd.s, "Append")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;

		cmd_append(tag.s, arg1.s);
	    }
	    else goto badcmd;
	    break;

	case 'B':
	    if (!strcmp(cmd.s, "Bboard")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;

		cmd_select(tag.s, cmd.s, arg1.s);
	    }
	    else goto badcmd;
	    break;

	case 'C':
	    if (!strcmp(cmd.s, "Capability")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_capability(tag.s);
	    }
	    else if (!proxyd_userid) goto nologin;
	    else if (!strcmp(cmd.s, "Check")) {
		if (!backend_current) goto nomailbox;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_noop(tag.s, cmd.s);
	    }
	    else if (!strcmp(cmd.s, "Copy")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    copy:
		c = getword(proxyd_in, &arg1);
		if (c == '\r') goto missingargs;
		if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;

		cmd_copy(tag.s, arg1.s, arg2.s, usinguid);
	    }
	    else if (!strcmp(cmd.s, "Create")) {
		havepartition = 0;
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == ' ') {
		    havepartition = 1;
		    c = getword(proxyd_in, &arg2);
		    if (!imparse_isatom(arg2.s)) goto badpartition;
		}
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_create(tag.s, arg1.s, havepartition ? arg2.s : 0);
	    }
	    else if (!strcmp(cmd.s, "Close")) {
		if (!backend_current) goto nomailbox;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_close(tag.s);
	    }
	    else goto badcmd;
	    break;

	case 'D':
	    if (!strcmp(cmd.s, "Delete")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_delete(tag.s, arg1.s);
	    }
	    else if (!strcmp(cmd.s, "Deleteacl")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (!strcasecmp(arg1.s, "mailbox")) {
		    if (c != ' ') goto missingargs;
		    c = getastring(proxyd_in, proxyd_out, &arg1);
		}
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_setacl(tag.s, arg1.s, arg2.s, (char *)0);
	    }
	    else goto badcmd;
	    break;

	case 'E':
	    if (!strcmp(cmd.s, "Expunge")) {
		if (!backend_current) goto nomailbox;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_expunge(tag.s, 0);
	    }
	    else if (!strcmp(cmd.s, "Examine")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;

		cmd_select(tag.s, cmd.s, arg1.s);
	    }
	    else goto badcmd;
	    break;

	case 'F':
	    if (!strcmp(cmd.s, "Fetch")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    fetch:
		c = getword(proxyd_in, &arg1);
		if (c == '\r') goto missingargs;
		if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
		cmd_fetch(tag.s, arg1.s, usinguid);
	    }
	    else if (!strcmp(cmd.s, "Find")) {
		c = getword(proxyd_in, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_find(tag.s, arg1.s, arg2.s);
	    }
	    else goto badcmd;
	    break;

	case 'G':
	    if (!strcmp(cmd.s, "Getacl")) {
		oldform = 0;
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (!strcasecmp(arg1.s, "mailbox")) {
		    oldform = 1;
		    if (c != ' ') goto missingargs;
		    c = getastring(proxyd_in, proxyd_out, &arg1);
		}
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_getacl(tag.s, arg1.s, oldform);
	    }
#ifdef ENABLE_ANNOTATEMORE
	    else if (!strcmp(cmd.s, "Getannotation")) {
		if (c != ' ') goto missingargs;

		cmd_getannotation(tag.s);
	    }
#endif
	    else if (!strcmp(cmd.s, "Getquota")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_getquota(tag.s, arg1.s);
	    }
	    else if (!strcmp(cmd.s, "Getquotaroot")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_getquotaroot(tag.s, arg1.s);
	    }
	    else goto badcmd;
	    break;

	case 'I':
	    if (!strcmp(cmd.s, "Id")) {
		if (c != ' ') goto missingargs;
		cmd_id(tag.s);
	    }
	    else if (!proxyd_userid) goto nologin;
	    else if (!strcmp(cmd.s, "Idle")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_idle(tag.s);
	    }
	    else goto badcmd;
	    break;

	case 'L':
	    if (!strcmp(cmd.s, "Login")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if(c != ' ') goto missingargs;

		cmd_login(tag.s, arg1.s);
	    }
	    else if (!strcmp(cmd.s, "Logout")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		
		prot_printf(proxyd_out, 
			    "* BYE %s\r\n", error_message(IMAP_BYE_LOGOUT));
		prot_printf(proxyd_out, "%s OK %s\r\n", 
			    tag.s, error_message(IMAP_OK_COMPLETED));

		return;
	    }
	    else if (!proxyd_userid) goto nologin;
	    else if (!strcmp(cmd.s, "List")) {
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_list(tag.s, 0, arg1.s, arg2.s);
	    }
	    else if (!strcmp(cmd.s, "Lsub")) {
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_list(tag.s, 1, arg1.s, arg2.s);
	    }
	    else if (!strcmp(cmd.s, "Listrights")) {
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_listrights(tag.s, arg1.s, arg2.s);
	    }
	    else goto badcmd;
	    break;

	case 'M':
	    if (!strcmp(cmd.s, "Myrights")) {
		oldform = 0;
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (!strcasecmp(arg1.s, "mailbox")) {
		    oldform = 1;
		    if (c != ' ') goto missingargs;
		    c = getastring(proxyd_in, proxyd_out, &arg1);
		}
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_myrights(tag.s, arg1.s, oldform);
	    }
	    else goto badcmd;
	    break;

	case 'N':
	    if (!strcmp(cmd.s, "Noop")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_noop(tag.s, cmd.s);
	    }
#ifdef ENABLE_X_NETSCAPE_HACK
	    else if (!strcmp(cmd.s, "Netscape")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_netscape(tag.s);
	    }
#endif
	    else if (!proxyd_userid) goto nologin;
	    else if (!strcmp(cmd.s, "Namespace")) {
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_namespace(tag.s);
	    }
	    else goto badcmd;
	    break;

	case 'P':
	    if (!strcmp(cmd.s, "Partial")) {
		if (!backend_current) goto nomailbox;
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg1);
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg2);
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg3);
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg4);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_partial(tag.s, arg1.s, arg2.s, arg3.s, arg4.s);
	    }
	    else goto badcmd;
	    break;

	case 'R':
	    if (!strcmp(cmd.s, "Rename")) {
		havepartition = 0;
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == EOF) goto missingargs;
		if (c == ' ') {
		    havepartition = 1;
		    c = getword(proxyd_in, &arg3);
		    if (!imparse_isatom(arg3.s)) goto badpartition;
		}
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_rename(tag.s, arg1.s, arg2.s, havepartition ? arg3.s : 0);
	    }
	    else if (!strcmp(cmd.s, "Rlist")) {
		supports_referrals = 1;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_list(tag.s, 0, arg1.s, arg2.s);
	    }
	    else if (!strcmp(cmd.s, "Rlsub")) {
		supports_referrals = 1;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_list(tag.s, 1, arg1.s, arg2.s);
	    } else if(!strcmp(cmd.s, "Reconstruct")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if(c == ' ') {
		    /* Optional RECURSEIVE argument */
		    c = getword(proxyd_in, &arg2);
		    if(!imparse_isatom(arg2.s))
			goto extraargs;
		    else if(strcasecmp(arg2.s, "RECURSIVE"))
			goto extraargs;
		    /* we ignore the argument, because proxyd does not care */
		}
		if(c == '\r') c = prot_getc(proxyd_in);
		if(c != '\n') goto extraargs;
		cmd_reconstruct(tag.s, arg1.s);
	    }
	    else goto badcmd;
	    break;
	    
	case 'S':
	    if (!strcmp(cmd.s, "Starttls")) {
		if (!tls_enabled("imap")) {
		    /* we don't support starttls */
		    goto badcmd;
		}

		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;

		/* if we've already done SASL fail */
		if (proxyd_userid != NULL) {
		    prot_printf(proxyd_out, 
		     "%s BAD Can't Starttls after authentication\r\n", tag.s);
		    continue;
		}
		
		/* check if already did a successful tls */
		if (proxyd_starttls_done == 1) {
		    prot_printf(proxyd_out, 
				"%s BAD Already did a successful Starttls\r\n",
				tag.s);
		    continue;
		}
		cmd_starttls(tag.s, 0);	

		continue;
	    }

	    if (!proxyd_userid) {
		goto nologin;
	    } else if (!strcmp(cmd.s, "Store")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    store:
		c = getword(proxyd_in, &arg1);
		if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
		c = getword(proxyd_in, &arg2);
		if (c != ' ') goto badsequence;
		cmd_store(tag.s, arg1.s, arg2.s, usinguid);
	    }
	    else if (!strcmp(cmd.s, "Select")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;

		cmd_select(tag.s, cmd.s, arg1.s);
	    }
	    else if (!strcmp(cmd.s, "Search")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    search:
		cmd_search(tag.s, usinguid);
	    }
	    else if (!strcmp(cmd.s, "Subscribe")) {
		if (c != ' ') goto missingargs;
		havenamespace = 0;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == ' ') {
		    havenamespace = 1;
		    c = getastring(proxyd_in, proxyd_out, &arg2);
		}
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		if (havenamespace) {
		    cmd_changesub(tag.s, arg1.s, arg2.s, 1);
		}
		else {
		    cmd_changesub(tag.s, (char *)0, arg1.s, 1);
		}
	    }		
	    else if (!strcmp(cmd.s, "Setacl")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (!strcasecmp(arg1.s, "mailbox")) {
		    if (c != ' ') goto missingargs;
		    c = getastring(proxyd_in, proxyd_out, &arg1);
		}
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg2);
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg3);
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_setacl(tag.s, arg1.s, arg2.s, arg3.s);
	    }
#ifdef ENABLE_ANNOTATEMORE
	    else if (!strcmp(cmd.s, "Setannotation")) {
		if (c != ' ') goto missingargs;

		cmd_setannotation(tag.s);
	    }
#endif
	    else if (!strcmp(cmd.s, "Setquota")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		cmd_setquota(tag.s, arg1.s);
	    }
	    else if (!strcmp(cmd.s, "Sort")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    sort:
		cmd_sort(tag.s, usinguid);
	    }
	    else if (!strcmp(cmd.s, "Status")) {
		if (c != ' ') goto missingargs;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c != ' ') goto missingargs;
		cmd_status(tag.s, arg1.s);
	    }
	    else goto badcmd;
	    break;

	case 'T':
	    if (!strcmp(cmd.s, "Thread")) {
		if (!backend_current) goto nomailbox;
		usinguid = 0;
		if (c != ' ') goto missingargs;
	    thread:
		cmd_thread(tag.s, usinguid);
	    }
	    else goto badcmd;
	    break;

	case 'U':
	    if (!strcmp(cmd.s, "Uid")) {
		if (!backend_current) goto nomailbox;
		usinguid = 1;
		if (c != ' ') goto missingargs;
		c = getword(proxyd_in, &arg1);
		if (c != ' ') goto missingargs;
		lcase(arg1.s);
		if (!strcmp(arg1.s, "fetch")) {
		    goto fetch;
		}
		else if (!strcmp(arg1.s, "store")) {
		    goto store;
		}
		else if (!strcmp(arg1.s, "search")) {
		    goto search;
		}
		else if (!strcmp(arg1.s, "sort")) {
		    goto sort;
		}
		else if (!strcmp(arg1.s, "thread")) {
		    goto thread;
		}
		else if (!strcmp(arg1.s, "copy")) {
		    goto copy;
		}
		else if (!strcmp(arg1.s, "expunge")) {
		    c = getword(proxyd_in, &arg1);
		    if (!imparse_issequence(arg1.s)) goto badsequence;
		    if (c == '\r') c = prot_getc(proxyd_in);
		    if (c != '\n') goto extraargs;
		    cmd_expunge(tag.s, arg1.s);
		}
		else {
		    prot_printf(proxyd_out, 
				"%s BAD Unrecognized UID subcommand\r\n", 
				tag.s);
		    eatline(proxyd_in, c);
		}
	    }
	    else if (!strcmp(cmd.s, "Unsubscribe")) {
		if (c != ' ') goto missingargs;
		havenamespace = 0;
		c = getastring(proxyd_in, proxyd_out, &arg1);
		if (c == ' ') {
		    havenamespace = 1;
		    c = getastring(proxyd_in, proxyd_out, &arg2);
		}
		if (c == EOF) goto missingargs;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		if (havenamespace) {
		    cmd_changesub(tag.s, arg1.s, arg2.s, 0);
		}
		else {
		    cmd_changesub(tag.s, (char *)0, arg1.s, 0);
		}
	    }		
	    else if (!strcmp(cmd.s, "Unselect")) {
		if (!backend_current) goto nomailbox;
		if (c == '\r') c = prot_getc(proxyd_in);
		if (c != '\n') goto extraargs;
		cmd_unselect(tag.s);
	    }
	    else goto badcmd;
	    break;

	default:
	badcmd:
	    prot_printf(proxyd_out, "%s BAD Unrecognized command\r\n", tag.s);
	    eatline(proxyd_in, c);
	}

	continue;

    nologin:
	prot_printf(proxyd_out, "%s BAD Please login first\r\n", tag.s);
	eatline(proxyd_in, c);
	continue;

    nomailbox:
	prot_printf(proxyd_out, "%s BAD Please select a mailbox first\r\n", 
		    tag.s);
	eatline(proxyd_in, c);
	continue;

    missingargs:
	prot_printf(proxyd_out, "%s BAD Missing required argument to %s\r\n", 
		    tag.s, cmd.s);
	eatline(proxyd_in, c);
	continue;

    extraargs:
	prot_printf(proxyd_out, "%s BAD Unexpected extra arguments to %s\r\n",
		    tag.s, cmd.s);
	eatline(proxyd_in, c);
	continue;

    badsequence:
	prot_printf(proxyd_out, "%s BAD Invalid sequence in %s\r\n", 
		    tag.s, cmd.s);
	eatline(proxyd_in, c);
	continue;

    badpartition:
	prot_printf(proxyd_out, "%s BAD Invalid partition name in %s\r\n",
		    tag.s, cmd.s);
	eatline(proxyd_in, c);
	continue;
    }
}

/*
 * Perform a LOGIN command
 */
void cmd_login(char *tag, char *user)
{
    char *canon_user;
    char c;
    struct buf passwdbuf;
    char *passwd;
    char *reply = 0;
    const char *val;
    char buf[MAX_MAILBOX_PATH];
    char *p;
    int plaintextloginpause;
    int r;

    if (proxyd_userid) {
	eatline(proxyd_in, ' ');
	prot_printf(proxyd_out, "%s BAD Already logged in\r\n", tag);
	return;
    }

    canon_user = auth_canonifyid(user, 0);

    if (!canon_user) {
	syslog(LOG_NOTICE, "badlogin: %s plaintext %s invalid user",
	       proxyd_clienthost, beautify_string(user));
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, 
		    error_message(IMAP_INVALID_USER));
	return;
    }

    /* possibly disallow login */
    if ((proxyd_starttls_done == 0) &&
	(config_getswitch("allowplaintext", 1) == 0) &&
	strcmp(canon_user, "anonymous") != 0) {
	eatline(proxyd_in, ' ');
	prot_printf(proxyd_out, "%s NO Login only available under a layer\r\n",
		    tag);
	return;
    }

    memset(&passwdbuf,0,sizeof(struct buf));
    c = getastring(proxyd_in, proxyd_out, &passwdbuf);

    if(c == '\r') c = prot_getc(proxyd_in);
    if (c != '\n') {
	freebuf(&passwdbuf);
	prot_printf(proxyd_out,
		    "%s BAD Unexpected extra arguments to LOGIN\r\n",
		    tag);
	eatline(proxyd_in, c);
	return;
    }

    passwd = passwdbuf.s;

    if (!strcmp(canon_user, "anonymous")) {
	if (config_getswitch("allowanonymouslogin", 0)) {
	    passwd = beautify_string(passwd);
	    if (strlen(passwd) > 500) passwd[500] = '\0';
	    syslog(LOG_NOTICE, "login: %s anonymous %s",
		   proxyd_clienthost, passwd);
	    reply = "Anonymous access granted";
	    proxyd_userid = xstrdup("anonymous");
	}
	else {
	    syslog(LOG_NOTICE, "badlogin: %s anonymous login refused",
		   proxyd_clienthost);
	    prot_printf(proxyd_out, "%s NO %s\r\n", tag,
		   error_message(IMAP_ANONYMOUS_NOT_PERMITTED));
	    freebuf(&passwdbuf);
	    return;
	}
    }
    else if ((r = sasl_checkpass(proxyd_saslconn,
				 canon_user,
				 strlen(canon_user),
				 passwd,
				 strlen(passwd)))!=SASL_OK) {
	const char *errorstring = sasl_errstring(r, NULL, NULL);
	if (reply) {
	    syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s",
		   proxyd_clienthost, canon_user, reply);
	}
	/* Apply penalty only if not under layer */
	if (proxyd_starttls_done == 0)
	    sleep(3);
	if (errorstring) {
	    prot_printf(proxyd_out, "%s NO Login failed: %s\r\n", 
			tag, errorstring);
	} else {
	    prot_printf(proxyd_out, "%s NO Login failed.", tag);
	}
	freebuf(&passwdbuf);
	return;
    }
    else {
	proxyd_userid = xstrdup(canon_user);

	syslog(LOG_NOTICE, "login: %s %s plaintext%s %s", proxyd_clienthost,
	       canon_user, proxyd_starttls_done ? "+TLS" : "", 
	       reply ? reply : "");

	plaintextloginpause = config_getint("plaintextloginpause", 0);
	if (plaintextloginpause) {

	    /* Apply penalty only if not under layer */
	    if (proxyd_starttls_done == 0)
		sleep(plaintextloginpause);
	}
    }
    

    proxyd_authstate = auth_newstate(canon_user, (char *)0);

    val = config_getstring("admins", "");
    while (*val) {
	for (p = (char *)val; *p && !isspace((int) *p); p++);
	strlcpy(buf, val, p - val);
	buf[p-val] = 0;
	if (auth_memberof(proxyd_authstate, buf)) {
	    proxyd_userisadmin = 1;
	    break;
	}
	val = p;
	while (*val && isspace((int) *val)) val++;
    }

    if (!reply) reply = "User logged in";

    prot_printf(proxyd_out, "%s OK %s\r\n", tag, reply);

    /* Create telemetry log */
    telemetry_log(proxyd_userid, proxyd_in, proxyd_out);

    /* Set namespace */
    if ((r = mboxname_init_namespace(&proxyd_namespace, proxyd_userisadmin)) != 0) {
	syslog(LOG_ERR, error_message(r));
	fatal(error_message(r), EC_CONFIG);
    }

    freebuf(&passwdbuf);
    return;
}

/*
 * Perform an AUTHENTICATE command
 */
void cmd_authenticate(char *tag, char *authtype)
{
    int sasl_result;
    static struct buf clientin;
    int clientinlen=0;
    
    const char *serverout;
    unsigned int serveroutlen;
    
    const char *errorstring = NULL;
    const char *userid_buf;
    
    const int *ssfp;
    char *ssfmsg=NULL;

    int r;

    sasl_result = sasl_server_start(proxyd_saslconn, authtype,
				    NULL, 0,
				    &serverout, &serveroutlen);    

    /* sasl_server_start will return SASL_OK or SASL_CONTINUE on success */

    while (sasl_result == SASL_CONTINUE)
    {
      char c;

      /* print the message to the user */
      printauthready(proxyd_out, serveroutlen, (unsigned char *)serverout);

      c = prot_getc(proxyd_in);
      if(c == '*') {
         eatline(proxyd_in,c);
         prot_printf(proxyd_out,
                     "%s NO Client canceled authentication\r\n", tag);
         reset_saslconn(&proxyd_saslconn);
         return;
      } else {
         prot_ungetc(c, proxyd_in);
      }

      /* get string from user */
      clientinlen = getbase64string(proxyd_in, &clientin);
      if (clientinlen == -1) {
	reset_saslconn(&proxyd_saslconn);
	prot_printf(proxyd_out, "%s BAD Invalid base64 string\r\n", tag);
	return;
      }

      sasl_result = sasl_server_step(proxyd_saslconn,
				     clientin.s,
				     clientinlen,
				     &serverout, &serveroutlen);
    }


    /* failed authentication */
    if (sasl_result != SASL_OK)
    {
	/* convert the sasl error code to a string */
	errorstring = sasl_errstring(sasl_result, NULL, NULL);
      
	syslog(LOG_NOTICE, "badlogin: %s %s %s",
	       proxyd_clienthost, authtype, sasl_errdetail(proxyd_saslconn));
	
	sleep(3);

	reset_saslconn(&proxyd_saslconn);
	if (errorstring) {
	    prot_printf(proxyd_out, "%s NO %s\r\n", tag, errorstring);
	} else {
	    prot_printf(proxyd_out, "%s NO Error authenticating\r\n", tag);
	}

	return;
    }

    /* successful authentication */

    /* get the userid from SASL --- already canonicalized from
     * mysasl_authproc()
     */
    sasl_result = sasl_getprop(proxyd_saslconn, SASL_USERNAME,
			       (const void **)&userid_buf);
    proxyd_userid = xstrdup(userid_buf);

    if (sasl_result!=SASL_OK)
    {
	prot_printf(proxyd_out, "%s NO weird SASL error %d SASL_USERNAME\r\n", 
		    tag, sasl_result);
	syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", 
	       sasl_result);
	reset_saslconn(&proxyd_saslconn);
	return;
    }

    proc_register("proxyd", proxyd_clienthost, proxyd_userid, (char *)0);

    syslog(LOG_NOTICE, "login: %s %s %s%s %s", 
	   proxyd_clienthost, proxyd_userid,
	   authtype, proxyd_starttls_done ? "+TLS" : "", "User logged in");

    sasl_getprop(proxyd_saslconn, SASL_SSF, (const void **) &ssfp);

    if (proxyd_starttls_done) {
	switch(*ssfp) {
	case 0: ssfmsg = "tls protection"; break;
	case 1: ssfmsg = "tls plus integrity protection"; break;
	default: ssfmsg = "tls plus privacy protection"; break;
	}
    } else {
	switch(*ssfp) {
	case 0: ssfmsg="no protection"; break;
	case 1: ssfmsg="integrity protection"; break;
	default: ssfmsg="privacy protection"; break;
	}
    }

    prot_printf(proxyd_out, "%s OK Success (%s)\r\n", tag,ssfmsg);
    prot_flush(proxyd_out);

    prot_setsasl(proxyd_in,  proxyd_saslconn);
    prot_setsasl(proxyd_out, proxyd_saslconn);

    /* Create telemetry log */
    telemetry_log(proxyd_userid, proxyd_in, proxyd_out);

    /* Set namespace */
    if ((r = mboxname_init_namespace(&proxyd_namespace, proxyd_userisadmin)) != 0) {
	syslog(LOG_ERR, error_message(r));
	fatal(error_message(r), EC_CONFIG);
    }

    return;
}

/*
 * Perform a NOOP command
 */
void cmd_noop(char *tag, char *cmd)
{
    if (backend_current) {
	prot_printf(backend_current->out, "%s %s\r\n", tag, cmd);
	pipe_including_tag(backend_current, tag, 0);
    } else {
	prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		    error_message(IMAP_OK_COMPLETED));
    }
}

/*
 * Parse and perform an ID command.
 *
 * the command has been parsed up to the parameter list.
 *
 * we only allow one ID in non-authenticated state from a given client.
 * we only allow MAXIDFAILED consecutive failed IDs from a given client.
 * we only record MAXIDLOG ID responses from a given client.
 */
void cmd_id(char *tag)
{
    static int did_id = 0;
    static int failed_id = 0;
    static int logged_id = 0;
    int error = 0;
    int c = EOF, npair = 0;
    static struct buf arg, field;
    struct idparamlist *params = 0;

    /* check if we've already had an ID in non-authenticated state */
    if (!proxyd_userid && did_id) {
	prot_printf(proxyd_out,
		    "%s NO Only one Id allowed in non-authenticated state\r\n",
		    tag);
	eatline(proxyd_in, c);
	return;
    }

    /* check if we've had too many failed IDs in a row */
    if (failed_id >= MAXIDFAILED) {
	prot_printf(proxyd_out, "%s NO Too many (%u) invalid Id commands\r\n",
		    tag, failed_id);
	eatline(proxyd_in, c);
	return;
    }

    /* ok, accept parameter list */
    c = getword(proxyd_in, &arg);
    /* check for "NIL" or start of parameter list */
    if (strcasecmp(arg.s, "NIL") && c != '(') {
	prot_printf(proxyd_out, "%s BAD Invalid parameter list in Id\r\n", tag);
	eatline(proxyd_in, c);
	failed_id++;
	return;
    }

    /* parse parameter list */
    if (c == '(') {
	for (;;) {
	    if (c == ')') {
		/* end of string/value pairs */
		break;
	    }

	    /* get field name */
	    c = getstring(proxyd_in, proxyd_out, &field);
	    if (c != ' ') {
		prot_printf(proxyd_out,
			    "%s BAD Invalid/missing field name in Id\r\n",
			    tag);
		error = 1;
		break;
	    }

	    /* get field value */
	    c = getnstring(proxyd_in, proxyd_out, &arg);
	    if (c != ' ' && c != ')') {
		prot_printf(proxyd_out,
			    "%s BAD Invalid/missing value in Id\r\n",
			    tag);
		error = 1;
		break;
	    }

	    /* ok, we're anal, but we'll still process the ID command */
	    if (strlen(field.s) > MAXIDFIELDLEN) {
		prot_printf(proxyd_out, 
			    "%s BAD field longer than %u octets in Id\r\n",
			    tag, MAXIDFIELDLEN);
		error = 1;
		break;
	    }
	    if (strlen(arg.s) > MAXIDVALUELEN) {
		prot_printf(proxyd_out,
			    "%s BAD value longer than %u octets in Id\r\n",
			    tag, MAXIDVALUELEN);
		error = 1;
		break;
	    }
	    if (++npair > MAXIDPAIRS) {
		prot_printf(proxyd_out,
			    "%s BAD too many (%u) field-value pairs in ID\r\n",
			    tag, MAXIDPAIRS);
		error = 1;
		break;
	    }
	    
	    /* ok, we're happy enough */
	    id_appendparamlist(&params, field.s, arg.s);
	}

	if (error || c != ')') {
	    /* erp! */
	    eatline(proxyd_in, c);
	    id_freeparamlist(params);
	    failed_id++;
	    return;
	}
	c = prot_getc(proxyd_in);
    }

    /* check for CRLF */
    if (c == '\r') c = prot_getc(proxyd_in);
    if (c != '\n') {
	prot_printf(proxyd_out,
		    "%s BAD Unexpected extra arguments to Id\r\n", tag);
	eatline(proxyd_in, c);
	id_freeparamlist(params);
	failed_id++;
	return;
    }

    /* log the client's ID string.
       eventually this should be a callback or something. */
    if (npair && logged_id < MAXIDLOG) {
	char logbuf[MAXIDLOGLEN + 1] = "";
	struct idparamlist *pptr;

	for (pptr = params; pptr; pptr = pptr->next) {
	    /* should we check for and format literals here ??? */
	    snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
		     " \"%s\" ", pptr->field);
	    if (!strcmp(pptr->value, "NIL"))
		snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
			 "NIL");
	    else
		snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
			"\"%s\"", pptr->value);
	}

	syslog(LOG_INFO, "client id:%s", logbuf);

	logged_id++;
    }

    id_freeparamlist(params);

    /* spit out our ID string.
       eventually this might be configurable. */
    if (config_getswitch("imapidresponse", 1)) {
	id_response(proxyd_out);

	/* add info about the backend */
	if (backend_current)
	    prot_printf(proxyd_out, " \"backend-url\" \"imap://%s\"",
			backend_current->hostname);
	else
	    prot_printf(proxyd_out, " \"backend-url\" NIL");

	prot_printf(proxyd_out, ")\r\n");
    }
    else
	prot_printf(proxyd_out, "* ID NIL\r\n");

    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));

    failed_id = 0;
    did_id = 1;
}

/*
 * Append the 'field' / 'value' pair to the idparamlist 'l'.
 */
void id_appendparamlist(struct idparamlist **l, char *field, char *value)
{
    struct idparamlist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct idparamlist *)xmalloc(sizeof(struct idparamlist));
    (*tail)->field = xstrdup(field);
    (*tail)->value = xstrdup(value);
    (*tail)->next = 0;
}

/*
 * Free the idparamlist 'l'
 */
void id_freeparamlist(struct idparamlist *l)
{
    struct idparamlist *n;

    while (l) {
	n = l->next;
	free(l->field);
	free(l->value);
	l = n;
    }
}

/*
 * Perform an IDLE command
 */
void cmd_idle(char *tag)
{
#ifdef PROXY_IDLE
    static int idle_period = -1;
    int c;
    static struct buf arg;

    /* get polling period */
    if (idle_period == -1) {
      idle_period = config_getint("imapidlepoll", 60);
      if (idle_period < 1) idle_period = 1;
    }

    if (!backend_current) {
	c = idle_nomailbox(tag, idle_period, &arg);
    } else if (CAPA(backend_current, IDLE)) {
	c = idle_passthrough(tag, idle_period, &arg);
    } else {
	c = idle_simulate(tag, idle_period, &arg);
    }

    if (c != EOF) {
	if (!strcasecmp(arg.s, "Done") &&
	    (c = (c == '\r') ? prot_getc(proxyd_in) : c) == '\n') {
	    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
			error_message(IMAP_OK_COMPLETED));
	}
	else {
	    prot_printf(proxyd_out, 
			"%s BAD Invalid Idle continuation\r\n", tag);
	    eatline(proxyd_in, c);
	}
    }
#else
    prot_printf(proxyd_out, "%s NO idle disabled\r\n", tag);
#endif
}

/* Check for alerts */ 
struct prot_waitevent *idle_getalerts(struct protstream *s,
				      struct prot_waitevent *ev, void *rock)
{
    int idle_period = *((int *) rock);
    int fd;

    if (! proxyd_userisadmin &&
	(fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {

	/* if we're actually running IDLE on the be, terminate it */
	if (backend_current && CAPA(backend_current, IDLE))
	    prot_printf(backend_current->out, "DONE\r\n");

	shutdown_file(fd);
    }

    ev->mark = time(NULL) + idle_period;
    return ev;
}

/* Run IDLE in the authenticated state (no mailbox) */
char idle_nomailbox(char *tag, int idle_period, struct buf *arg)
{
    struct prot_waitevent *idle_event;
    int c;

    /* Tell client we are idling and waiting for end of command */
    prot_printf(proxyd_out, "+ go ahead\r\n");
    prot_flush(proxyd_out);

    /* Setup an event function to check for alerts */
    idle_event = prot_addwaitevent(proxyd_in,
				   time(NULL) + idle_period,
				   idle_getalerts, &idle_period);

    /* Get continuation data */
    c = getword(proxyd_in, arg);

    /* Remove the event function */
    prot_removewaitevent(proxyd_in, idle_event);

    return c;
}

/* Get unsolicited untagged responses from the backend
   assumes that untagged responses are:
   a) short
   b) don't contain literals
*/
struct prot_waitevent *idle_getresp(struct protstream *s,
				    struct prot_waitevent *ev, void *rock)
{
    char buf[2048];

    prot_NONBLOCK(backend_current->in);
    while (prot_fgets(buf, sizeof(buf), backend_current->in)) {
	prot_write(proxyd_out, buf, strlen(buf));
	prot_flush(proxyd_out);
    }
    prot_BLOCK(backend_current->in);

    return idle_getalerts(s, ev, rock);
}

/* Run IDLE on the backend */
char idle_passthrough(char *tag, int idle_period, struct buf *arg)
{
    struct prot_waitevent *idle_event;
    int c;
    char buf[2048];

    /* Start IDLE on backend */
    prot_printf(backend_current->out, "%s IDLE\r\n", tag);
    if (!prot_fgets(buf, sizeof(buf), backend_current->in)) {

	/* if we received nothing from the backend, fail */
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, 
		    error_message(IMAP_SERVER_UNAVAILABLE));
	return EOF;
    }
    if (buf[0] != '+') {

	/* if we received anything but a continuation response,
	   spit out what we received and quit */
	prot_write(proxyd_out, buf, strlen(buf));
	return EOF;
    }

    /* Tell client we are idling and waiting for end of command */
    prot_printf(proxyd_out, "+ go ahead\r\n");
    prot_flush(proxyd_out);

    /* Setup an event function to get responses from the idling backend */
    idle_event = prot_addwaitevent(proxyd_in,
				   time(NULL) + idle_period,
				   idle_getresp, &idle_period);

    /* Get continuation data */
    c = getword(proxyd_in, arg);

    /* Remove the event function */
    prot_removewaitevent(backend_current->in, idle_event);

    /* Either the client timed out, or gave us a continuation.
       In either case we're done, so terminate IDLE on backend */
    prot_printf(backend_current->out, "DONE\r\n");
    pipe_until_tag(backend_current, tag, 0);

    return c;
}

/* Poll the backend for updates */
static struct prot_waitevent *idle_poll(struct protstream *s,
				 struct prot_waitevent *ev, void *rock)
{
    char mytag[128];
	
    proxyd_gentag(mytag);
    prot_printf(backend_current->out, "%s Noop\r\n", mytag);
    pipe_until_tag(backend_current, mytag, 0);
    prot_flush(proxyd_out);

    return idle_getalerts(s, ev, rock);
}

/* Simulate IDLE by polling the backend */
char idle_simulate(char *tag, int idle_period, struct buf *arg)
{
    struct prot_waitevent *idle_event;
    int c;

    /* Tell client we are idling and waiting for end of command */
    prot_printf(proxyd_out, "+ go ahead\r\n");
    prot_flush(proxyd_out);

    /* Setup an event function to poll the backend for updates */
    idle_event = prot_addwaitevent(proxyd_in,
				   time(NULL) + idle_period,
				   idle_poll, &idle_period);

    /* Get continuation data */
    c = getword(proxyd_in, arg);

    /* Remove the event function */
    prot_removewaitevent(proxyd_in, idle_event);

    return c;
}

/*
 * Perform a CAPABILITY command
 */
void cmd_capability(char *tag)
{
    const char *sasllist; /* the list of SASL mechanisms */
    unsigned mechcount;

    if (backend_current) {
	char mytag[128];
	
	proxyd_gentag(mytag);
	/* do i want to do a NOOP for every operation? */
	prot_printf(backend_current->out, "%s Noop\r\n", mytag);
	pipe_until_tag(backend_current, mytag, 0);
    }
    prot_printf(proxyd_out, "* CAPABILITY ");
    prot_printf(proxyd_out, CAPABILITY_STRING);

#ifdef PROXY_IDLE
    prot_printf(proxyd_out, " IDLE");
#endif

    if (tls_enabled("imap")) {
	prot_printf(proxyd_out, " STARTTLS");
    }
    if (!proxyd_starttls_done && !config_getswitch("allowplaintext", 1)) {
	prot_printf(proxyd_out, " LOGINDISABLED");	
    }

    if (sasl_listmech(proxyd_saslconn, NULL, 
		      "AUTH=", " AUTH=", "",
		      &sasllist,
		      NULL, &mechcount) == SASL_OK && mechcount > 0) {
	prot_printf(proxyd_out, " %s", sasllist);      
    } else {
	/* else don't show anything */
    }

#ifdef ENABLE_ANNOTATEMORE
    prot_printf(proxyd_out, " ANNOTATEMORE");
#endif

#ifdef ENABLE_X_NETSCAPE_HACK
    prot_printf(proxyd_out, " X-NETSCAPE");
#endif

    prot_printf(proxyd_out, "\r\n");

    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}

/*
 * Parse and perform an APPEND command.
 * The command has been parsed up to and including
 * the mailbox name.
 */
void cmd_append(char *tag, char *name)
{
    int r;
    char mailboxname[MAX_MAILBOX_PATH + 1];
    char *newserver;
    struct backend *s = NULL;

    /* we want to pipeline this whole command through to the server that
       has name on it, and then do a noop on our current server */
    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) {
	r = mlookup(mailboxname, &newserver, NULL, NULL);
    }
    if (!r && supports_referrals) { 
	proxyd_refer(tag, newserver, mailboxname);
	/* Eat the argument */
	eatline(proxyd_in, prot_getc(proxyd_in));
	return;
    }
    if (!r) {
	s = proxyd_findserver(newserver);
	if (!s) r = IMAP_SERVER_UNAVAILABLE;
    }
    if (!r) {
	prot_printf(s->out, "%s Append {%d+}\r\n%s ", tag, strlen(name), name);
	if (!pipe_command(s, 16384)) {
	    pipe_until_tag(s, tag, 0);
	}
    } else {
	eatline(proxyd_in, prot_getc(proxyd_in));
    }

    if (backend_current && backend_current != s) {
	char mytag[128];

	proxyd_gentag(mytag);
	
	prot_printf(backend_current->out, "%s Noop\r\n", mytag);
	pipe_until_tag(backend_current, mytag, 0);
    }

    if (r) {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    } else {
	/* we're allowed to reference last_result since the noop, if
	   sent, went to a different server */
	prot_printf(proxyd_out, "%s %s", tag, s->last_result);
    }
}

/*
 * Perform a SELECT/EXAMINE/BBOARD command
 */
void cmd_select(char *tag, char *cmd, char *name)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r = 0;
    char *newserver;
    struct backend *backend_next = NULL;

    if (cmd[0] == 'B') {
	/* BBoard namespace is empty */
	r = IMAP_MAILBOX_NONEXISTENT;
    }
    else {
	r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						    proxyd_userid, mailboxname);
    }

    if (!r) r = mlookup(mailboxname, &newserver, NULL, NULL);
    if (!r && supports_referrals) { 
	proxyd_refer(tag, newserver, mailboxname);
	return;
    }

    if (!r) {
	backend_next = proxyd_findserver(newserver);
	if (!backend_next) r = IMAP_SERVER_UNAVAILABLE;
    }

    if (backend_current && backend_current != backend_next) {
	char mytag[128];

	/* switching servers; flush old server output */
	proxyd_gentag(mytag);
	prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
	/* do not fatal() here, because we don't really care about this
	 * server anymore anyway */
	pipe_until_tag(backend_current, mytag, 1);
    }
    backend_current = backend_next;

    if (r) {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
	return;
    }

    prot_printf(backend_current->out, "%s %s {%d+}\r\n%s\r\n", tag, cmd, 
		strlen(name), name);
    switch (pipe_including_tag(backend_current, tag, 0)) {
    case PROXY_OK:
	proc_register("proxyd", proxyd_clienthost, proxyd_userid, mailboxname);
	syslog(LOG_DEBUG, "open: user %s opened %s on %s", proxyd_userid, name,
	       newserver);
	break;
    default:
	syslog(LOG_DEBUG, "open: user %s failed to open %s", proxyd_userid,
	       name);
	/* not successfully selected */
	backend_current = NULL;
	break;
    }
}
	  
/*
 * Perform a CLOSE command
 */
void cmd_close(char *tag)
{
    assert(backend_current != NULL);
    
    prot_printf(backend_current->out, "%s Close\r\n", tag);
    /* xxx do we want this to say OK if the connection is gone?
     * saying NO is clearly wrong, hense the fatal request. */
    pipe_including_tag(backend_current, tag, 0);
    backend_current = NULL;
}

/*
 * Perform an UNSELECT command -- for some support of IMAP proxy.
 * Just like close except no expunge.
 */
void cmd_unselect(char *tag)
{
    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s Unselect\r\n", tag);
    /* xxx do we want this to say OK if the connection is gone?
     * saying NO is clearly wrong, hense the fatal request. */
    pipe_including_tag(backend_current, tag, 0);
    backend_current = NULL;
}

/*
 * Parse and perform a FETCH/UID FETCH command
 * The command has been parsed up to and including
 * the sequence
 */
void cmd_fetch(char *tag, char *sequence, int usinguid)
{
    char *cmd = usinguid ? "UID Fetch" : "Fetch";

    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence);
    if (!pipe_command(backend_current, 65536)) {
	pipe_including_tag(backend_current, tag, 0);
    }
}

/*
 * Perform a PARTIAL command
 */
void cmd_partial(char *tag, char *msgno, char *data, char *start, char *count)
{
    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s Partial %s %s %s %s\r\n",
		tag, msgno, data, start, count);
    pipe_including_tag(backend_current, tag, 0);
}

/*
 * Parse and perform a STORE/UID STORE command
 * The command has been parsed up to and including
 * the FLAGS/+FLAGS/-FLAGS
 */
void cmd_store(char *tag, char *sequence, char *operation, int usinguid)
{
    const char *cmd = usinguid ? "UID Store" : "Store";

    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s %s %s %s ",
		tag, cmd, sequence, operation);
    if (!pipe_command(backend_current, 65536)) {
	pipe_including_tag(backend_current, tag, 0);
    }
}

void cmd_search(char *tag, int usinguid)
{
    const char *cmd = usinguid ? "UID Search" : "Search";

    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s %s ", tag, cmd);
    if (!pipe_command(backend_current, 65536)) {
	pipe_including_tag(backend_current, tag, 0);
    }
}

void cmd_sort(char *tag, int usinguid)
{
    char *cmd = usinguid ? "UID Sort" : "Sort";

    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s %s ", tag, cmd);
    if (!pipe_command(backend_current, 65536)) {
	pipe_including_tag(backend_current, tag, 0);
    }
}

void cmd_thread(char *tag, int usinguid)
{
    char *cmd = usinguid ? "UID Thread" : "Thread";

    assert(backend_current != NULL);

    prot_printf(backend_current->out, "%s %s ", tag, cmd);
    if (!pipe_command(backend_current, 65536)) {
	pipe_including_tag(backend_current, tag, 0);
    }
}

static int chomp(struct protstream *p, char *s)
{
    int c = prot_getc(p);

    while (*s) {
	if (tolower(c) != tolower(*s)) { break; }
	s++;
	c = prot_getc(p);
    }
    if (*s) {
	if (c != EOF) prot_ungetc(c, p);
	c = EOF;
    }
    return c;
}

/* read characters from 'p' until 'end' is seen */
static char *grab(struct protstream *p, char end)
{
    int alloc = BUFGROWSIZE, cur = 0;
    int c = -1;
    char *ret = (char *) xmalloc(alloc);

    ret[0] = '\0';
    while ((c = prot_getc(p)) != end) {
	if (c == EOF) break;
	if (cur == alloc - 1) {
	    alloc += BUFGROWSIZE;
	    ret = xrealloc(ret, alloc);

	}
	ret[cur++] = c;
    }
    if (cur) ret[cur] = '\0';

    return ret;
}

/* remove \Recent from the flags */
static char *editflags(char *flags)
{
    char *p;

    p = flags;
    while ((p = strchr(p, '\\')) != NULL) {
	if (!strncasecmp(p + 1, "recent", 6)) {
	    if (p[7] == ' ') {
		/* shift everything over so that \recent vanishes */
		char *q;
		
		q = p + 8;
		while (*q) {
		    *p++ = *q++;
		}
		*p = '\0';
	    } else if (p[7] == '\0') {
		/* last flag in line */
		*p = '\0';
	    } else {
		/* not really \recent, i guess */
		p++;
	    }
	} else {
	    p++;
	}
    }

    return flags;
}

/*
 * Perform a COPY/UID COPY command
 */    
void cmd_copy(char *tag, char *sequence, char *name, int usinguid)
{
    char *server;
    char *cmd = usinguid ? "UID Copy" : "Copy";
    struct backend *s = NULL;
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r;

    assert(backend_current != NULL);

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);
    if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
    if (!r) s = proxyd_findserver(server);

    if (!s) {
	/* no such mailbox or other problem */
	r = mboxlist_createmailboxcheck(mailboxname, 0, 0, proxyd_userisadmin, 
					proxyd_userid, proxyd_authstate,
					NULL, NULL);
	if(!r && server) {
	    char *c;
	    c = strchr(server, '!');
	    if(c) *c = '\0';
	}
	prot_printf(proxyd_out, "%s NO %s%s\r\n", tag,
		    r == 0 ? "[TRYCREATE] " : "", error_message(r));
    } else if (s == backend_current) {
	/* this is the easy case */
	prot_printf(backend_current->out, "%s %s %s {%d+}\r\n%s\r\n",
		    tag, cmd, sequence, strlen(name), name);
	pipe_including_tag(backend_current, tag, 0);
    } else {
	char mytag[128];
	struct d {
	    char *idate;
	    char *flags;
	    int seqno, uid;
	    struct d *next;
	} *head, *p, *q;
	int c;

	/* this is the hard case; we have to fetch the messages and append
	   them to the other mailbox */

	/* find out what the flags & internaldate for this message are */
	proxyd_gentag(mytag);
	prot_printf(backend_current->out, 
		    "%s %s %s (Flags Internaldate)\r\n", 
		    tag, usinguid ? "Uid Fetch" : "Fetch", sequence);
	head = (struct d *) xmalloc(sizeof(struct d));
	head->flags = NULL; head->idate = NULL;
	head->seqno = head->uid = 0;
	head->next = NULL;
	p = head;
	/* read all the responses into the linked list */
	for (/* each FETCH response */;;) {
	    int seqno = 0, uidno = 0;
	    char *flags = NULL, *idate = NULL;

	    /* read a line */
	    c = prot_getc(backend_current->in);
	    if (c != '*') break;
	    c = prot_getc(backend_current->in);
	    if (c != ' ') { /* protocol error */ c = EOF; break; }
	    
	    /* read seqno */
	    seqno = 0;
	    while (isdigit(c = prot_getc(backend_current->in))) {
		seqno *= 10;
		seqno += c - '0';
	    }
	    if (seqno == 0 || c != ' ') {
		/* we suck and won't handle this case */
		c = EOF; break;
	    }
	    c = chomp(backend_current->in, "fetch (");
	    if (c == EOF) {
		c = chomp(backend_current->in, "exists\r");
		if (c == '\n') { /* got EXISTS response */
		    prot_printf(proxyd_out, "* %d EXISTS\r\n", seqno);
		    continue;
		}
	    }
	    if (c == EOF) {
		c = chomp(backend_current->in, "recent\r");
		if (c == '\n') { /* got RECENT response */
		    prot_printf(proxyd_out, "* %d RECENT\r\n", seqno);
		    continue;
		}
	    }
	    /* huh, don't get this response */
	    if (c == EOF) break;
	    for (/* each fetch item */;;) {
		/* looking at the first character in an item */
		switch (c) {
		case 'f': case 'F': /* flags? */
		    c = chomp(backend_current->in, "lags");
		    if (c != ' ') { c = EOF; }
		    else c = prot_getc(backend_current->in);
		    if (c != '(') { c = EOF; }
		    else {
			flags = grab(backend_current->in, ')');
			c = prot_getc(backend_current->in);
		    }
		    break;
		case 'i': case 'I': /* internaldate? */
		    c = chomp(backend_current->in, "nternaldate");
		    if (c != ' ') { c = EOF; }
		    else c = prot_getc(backend_current->in);
		    if (c != '"') { c = EOF; }
		    else {
			idate = grab(backend_current->in, '"');
			c = prot_getc(backend_current->in);
		    }
		    break;
		case 'u': case 'U': /* uid */
		    c = chomp(backend_current->in, "id");
		    if (c != ' ') { c = EOF; }
		    else {
			uidno = 0;
			while (isdigit(c = prot_getc(backend_current->in))) {
			    uidno *= 10;
			    uidno += c - '0';
			}
		    }
		    break;
		default: /* hmm, don't like the smell of it */
		    c = EOF;
		    break;
		}
		/* looking at either SP seperating items or a RPAREN */
		if (c == ' ') { c = prot_getc(backend_current->in); }
		else if (c == ')') break;
		else { c = EOF; break; }
	    }
	    /* if c == EOF we have either a protocol error or a situation
	       we can't handle, and we should die. */
	    if (c == ')') c = prot_getc(backend_current->in);
	    if (c == '\r') c = prot_getc(backend_current->in);
	    if (c != '\n') { c = EOF; break; }

	    /* if we're missing something, we should echo */
	    if (!flags || !idate) {
		char sep = '(';
		prot_printf(proxyd_out, "* %d FETCH ", seqno);
		if (uidno) {
		    prot_printf(proxyd_out, "%cUID %d", sep, uidno);
		    sep = ' ';
		}
		if (flags) {
		    prot_printf(proxyd_out, "%cFLAGS %s", sep, flags);
		    sep = ' ';
		}
		if (idate) {
		    prot_printf(proxyd_out, "%cINTERNALDATE %s", sep, flags);
		    sep = ' ';
		}
		prot_printf(proxyd_out, ")\r\n");
		continue;
	    }

	    /* add to p->next */
	    p->next = xmalloc(sizeof(struct d));
	    p = p->next;
	    p->idate = idate;
	    p->flags = editflags(flags);
	    p->uid = uidno;
	    p->seqno = seqno;
	    p->next = NULL;
	}
	if (c != EOF) {
	    prot_ungetc(c, backend_current->in);

	    /* we should be looking at the tag now */
	    pipe_until_tag(backend_current, tag, 0);
	}
	if (c == EOF) {
	    /* uh oh, we're not happy */
	    fatal("Lost connection to selected backend", EC_UNAVAILABLE);
	}

	/* start the append */
	prot_printf(s->out, "%s Append %s", tag, name);
	prot_printf(backend_current->out, "%s %s %s (Rfc822.peek)\r\n",
		    mytag, usinguid ? "Uid Fetch" : "Fetch", sequence);
	for (/* each FETCH response */;;) {
	    int seqno = 0, uidno = 0;

	    /* read a line */
	    c = prot_getc(backend_current->in);
	    if (c != '*') break;
	    c = prot_getc(backend_current->in);
	    if (c != ' ') { /* protocol error */ c = EOF; break; }
	    
	    /* read seqno */
	    seqno = 0;
	    while (isdigit(c = prot_getc(backend_current->in))) {
		seqno *= 10;
		seqno += c - '0';
	    }
	    if (seqno == 0 || c != ' ') {
		/* we suck and won't handle this case */
		c = EOF; break;
	    }
	    c = chomp(backend_current->in, "fetch (");
	    if (c == EOF) {
		c = chomp(backend_current->in, "exists\r");
		if (c == '\n') { /* got EXISTS response */
		    prot_printf(proxyd_out, "* %d EXISTS\r\n", seqno);
		    continue;
		}
	    }
	    if (c == EOF) {
		c = chomp(backend_current->in, "recent\r");
		if (c == '\n') { /* got RECENT response */
		    prot_printf(proxyd_out, "* %d RECENT\r\n", seqno);
		    continue;
		}
	    }
	    /* huh, don't get this response */
	    if (c == EOF) break;
	    /* find seqno in the list */
	    p = head;
	    while (p->next && seqno != p->next->seqno) p = p->next;
	    if (!p->next) break;
	    q = p->next;
	    p->next = q->next;
	    for (/* each fetch item */;;) {
		int sz = 0;

		switch (c) {
		case 'u': case 'U':
		    c = chomp(backend_current->in, "id");
		    if (c != ' ') { c = EOF; }
		    else {
			uidno = 0;
			while (isdigit(c = prot_getc(backend_current->in))) {
			    uidno *= 10;
			    uidno += c - '0';
			}
		    }
		    break;

		case 'r': case 'R':
		    c = chomp(backend_current->in, "fc822");
		    if (c == ' ') c = prot_getc(backend_current->in);
		    if (c != '{') c = EOF;
		    else {
			sz = 0;
			while (isdigit(c = prot_getc(backend_current->in))) {
			    sz *= 10;
			    sz += c - '0';
			}
		    }
		    if (c == '}') c = prot_getc(backend_current->in);
		    if (c == '\r') c = prot_getc(backend_current->in);
		    if (c != '\n') c = EOF;

		    if (c != EOF) {
			/* append p to s->out */
			prot_printf(s->out, " (%s) \"%s\" {%d+}\r\n", 
				    q->flags, q->idate, sz);
			while (sz) {
			    char buf[2048];
			    int j = (sz > sizeof(buf) ? sizeof(buf) : sz);

			    j = prot_read(backend_current->in, buf, j);
			    if(!j) break;
			    prot_write(s->out, buf, j);
			    sz -= j;
			}
			c = prot_getc(backend_current->in);
		    }

		    break; /* end of case */
		default:
		    c = EOF;
		    break;
		}
		/* looking at either SP seperating items or a RPAREN */
		if (c == ' ') { c = prot_getc(backend_current->in); }
		else if (c == ')') break;
		else { c = EOF; break; }
	    }

	    /* if c == EOF we have either a protocol error or a situation
	       we can't handle, and we should die. */
	    if (c == ')') c = prot_getc(backend_current->in);
	    if (c == '\r') c = prot_getc(backend_current->in);
	    if (c != '\n') { c = EOF; break; }

	    /* free q */
	    free(q->idate);
	    free(q->flags);
	    free(q);
	}
	if (c != EOF) {
	    char *appenduid, *b;
	    int res;

	    /* pushback the first character of the tag we're looking at */
	    prot_ungetc(c, backend_current->in);

	    /* nothing should be left in the linked list */
	    assert(head->next == NULL);

	    /* ok, finish the append; we need the UIDVALIDITY and UIDs
	       to return as part of our COPYUID response code */
	    prot_printf(s->out, "\r\n");

	    /* should be looking at 'mytag' on 'backend_current', 
	       'tag' on 's' */
	    pipe_until_tag(backend_current, mytag, 0);
	    res = pipe_until_tag(s, tag, 0);

	    if (res == PROXY_OK) {
		appenduid = strchr(s->last_result, '[');
		/* skip over APPENDUID */
		appenduid += strlen("[appenduid ");
		b = strchr(appenduid, ']');
		*b = '\0';
		prot_printf(proxyd_out, "%s OK [COPYUID %s] %s\r\n", tag,
			    appenduid, error_message(IMAP_OK_COMPLETED));
	    } else {
		prot_printf(proxyd_out, "%s %s", tag, s->last_result);
	    }
	} else {
	    /* abort the append */
	    prot_printf(s->out, " {0}\r\n");
	    pipe_until_tag(backend_current, mytag, 0);
	    pipe_until_tag(s, tag, 0);
	    
	    /* report failure */
	    prot_printf(proxyd_out, "%s NO inter-server COPY failed\r\n", tag);
	}

	/* free dynamic memory */
	while (head) {
	    p = head;
	    head = head->next;
	    if (p->idate) free(p->idate);
	    if (p->flags) free(p->flags);
	    free(p);
	}
    }
}    

/*
 * Perform an EXPUNGE command
 * sequence == NULL if this isn't a UID EXPUNGE
 */
void cmd_expunge(char *tag, char *sequence)
{
    assert(backend_current != NULL);

    if (sequence) {
	prot_printf(backend_current->out, "%s UID Expunge %s\r\n", tag,
		    sequence);
    } else {
	prot_printf(backend_current->out, "%s Expunge\r\n", tag);
    }
    pipe_including_tag(backend_current, tag, 0);
}    

/*
 * Perform a CREATE command
 */
void cmd_create(char *tag, char *name, char *server)
{
    struct backend *s = NULL;
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r = 0, res;
    char *acl = NULL;

    if (server && !proxyd_userisadmin) {
	r = IMAP_PERMISSION_DENIED;
    }

    if (name[0] && name[strlen(name)-1] == proxyd_namespace.hier_sep) {
	/* We don't care about trailing hierarchy delimiters. */
	name[strlen(name)-1] = '\0';
    }

    if (!r)
	r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						    proxyd_userid, mailboxname);

    if (!r && !server) {
	r = mboxlist_createmailboxcheck(mailboxname, 0, 0, proxyd_userisadmin,
					proxyd_userid, proxyd_authstate,
					&acl, &server);
	if(!r && server) {
	    char *c;
	    c = strchr(server, '!');
	    if(c) *c = '\0';
	}
    }
    if (!r && server) {
	s = proxyd_findserver(server);
	if (!s) r = IMAP_SERVER_UNAVAILABLE;
    }
    if (!r) {
	if (!CAPA(s, MUPDATE)) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    
	    /* reserve mailbox on ACAP */
	    acapmbox_new(&mboxdata, s->hostname, mailboxname);
	    r = acapmbox_create(acaphandle, &mboxdata);
	    if (r) {
		syslog(LOG_ERR, "ACAP: unable to reserve %s: %s\n",
		       name, error_message(r));
	    }
#endif
	}
    }

    if (!r) {
	/* ok, send the create to that server */
	prot_printf(s->out, "%s CREATE {%d+}\r\n%s\r\n", 
		    tag, strlen(name), name);
	res = pipe_including_tag(s, tag, 0);
	tag = "*";		/* can't send another tagged response */
	
	if (!CAPA(s, MUPDATE)) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    
	    switch (res) {
	    case PROXY_OK:
		/* race condition here, but not much we can do about it */
		mboxdata.acl = acl;

		/* commit mailbox */
		r = acapmbox_markactive(acaphandle, &mboxdata);
		if (r) {
		    syslog(LOG_ERR, "ACAP: unable to commit %s: %s\n",
			   mailboxname, error_message(r));
		}
		break;
		
	    default:
		/* abort mailbox */
		r = acapmbox_delete(acaphandle, mailboxname);
		if (r) {
		    syslog(LOG_ERR, "ACAP: unable to unreserve %s: %s\n", 
			   mailboxname, error_message(r));
		}
		break;
	    }
#endif
	}
	/* make sure we've seen the update */
	if (ultraparanoid && res == PROXY_OK) kick_mupdate();
    }
    
    if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}

/*
 * Perform a DELETE command
 */
void cmd_delete(char *tag, char *name)
{
    int r, res;
    char *server;
    struct backend *s = NULL;
    char mailboxname[MAX_MAILBOX_NAME+1];

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
    if (!r && supports_referrals) { 
	proxyd_refer(tag, server, mailboxname);
	referral_kick = 1;
	return;
    }

    if (!r) {
	s = proxyd_findserver(server);
	if (!s) r = IMAP_SERVER_UNAVAILABLE;
    }

    if (!r) {
	prot_printf(s->out, "%s DELETE {%d+}\r\n%s\r\n", 
		    tag, strlen(name), name);
	res = pipe_including_tag(s, tag, 0);
	tag = "*";		/* can't send another tagged response */

	if (!CAPA(s, MUPDATE) && res == PROXY_OK) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    
	    /* delete mailbox from acap server */
	    r = acapmbox_delete(acaphandle, mailboxname);
	    if (r) {
		syslog(LOG_ERR, 
		       "ACAP: can't delete mailbox entry %s: %s",
		       name, error_message(r));
	    }
#endif
	}

	/* make sure we've seen the update */
	if (ultraparanoid && res == PROXY_OK) kick_mupdate();
    }

    if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}	

/*
 * Perform a RECONSTRUCT command
 */
void cmd_reconstruct(char *tag, char *name)
{
    int r = 0;
    char mailboxname[MAX_MAILBOX_NAME+1];
    char *server = NULL;

    if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
    else {
	r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
						    name,
						    proxyd_userid,
						    mailboxname);
    }

    if(!r)
	r = mlookup(mailboxname, &server, NULL, NULL);

    if(!r) {
	proxyd_refer(tag, server, mailboxname);
    } else {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    }
}	

/*
 * Perform a RENAME command
 */
void cmd_rename(char *tag, char *oldname, char *newname, char *partition)
{
    int r = 0, res;
    char *server;
    char oldmailboxname[MAX_MAILBOX_NAME+1];
    char newmailboxname[MAX_MAILBOX_NAME+1];
    struct backend *s = NULL;
    char *acl = NULL;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, oldname,
						proxyd_userid, oldmailboxname);
    if (!r) (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, newname,
						    proxyd_userid, newmailboxname);
    if (!r) r = mlookup(oldmailboxname, &server, &acl, NULL);
    if (!r) {
	s = proxyd_findserver(server);
	if (!s) r = IMAP_SERVER_UNAVAILABLE;
    }

    /* Cross Server Rename */
    if (!r && partition) {
	char *destpart;
	
	if(strcmp(oldname, newname)) {
	    prot_printf(s->out,
			"%s NO Cross-server move w/rename not supported\r\n",
			tag);
	    return;
	}

	/* dest partition? */

	destpart = strchr(partition,'!');
	if(destpart) {
	    strcpy(newmailboxname,partition);
	    newmailboxname[destpart-partition]='\0';
	    destpart++;
	    /* <tag> XFER <name> <dest server> <dest partition> */
	    prot_printf(s->out,
			"%s XFER {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n", 
			tag, strlen(oldname), oldname,
			strlen(newmailboxname), newmailboxname,
			strlen(destpart), destpart);
	} else {
	    /* <tag> XFER <name> <dest server> */
	    prot_printf(s->out, "%s XFER {%d+}\r\n%s {%d+}\r\n%s\r\n", 
			tag, strlen(oldname), oldname,
			strlen(partition), partition);
	}
	
	res = pipe_including_tag(s, tag, 0);

	/* make sure we've seen the update */
	if (ultraparanoid && res == PROXY_OK) kick_mupdate();

	return;
    }

    if (!r) {
	if (!CAPA(s, MUPDATE)) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    
	    /* reserve new mailbox */
	    acapmbox_new(&mboxdata, s->hostname, newmailboxname);
	    r = acapmbox_create(acaphandle, &mboxdata);
 	    if (r) {
		syslog(LOG_ERR, "ACAP: unable to reserve %s: %s\n",
		       newmailboxname, error_message(r));
	    }
#endif
	}

	prot_printf(s->out, "%s RENAME {%d+}\r\n%s {%d+}\r\n%s\r\n", 
		    tag, strlen(oldname), oldname,
		    strlen(newname), newname);
	res = pipe_including_tag(s, tag, 0);
	tag = "*";		/* can't send another tagged response */
	
	if (!CAPA(s, MUPDATE)) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    
	    switch (res) {
	    case PROXY_OK:
		/* commit new mailbox */
		mboxdata.acl = acl;
		r = acapmbox_markactive(acaphandle, &mboxdata);
		if (r) {
		    syslog(LOG_ERR, "ACAP: unable to commit %s: %s\n",
			   oldmailboxname, error_message(r));
		}

		/* delete old mailbox */
		r = acapmbox_delete(acaphandle, oldmailboxname);
		if (r) {
		    syslog(LOG_ERR, "ACAP: unable to delete %s: %s\n", 
			   oldmailboxname, error_message(r));
		}
		break;
		
	    default:
		/* abort new mailbox */
		r = acapmbox_delete(acaphandle, newmailboxname);
		if (r) {
		    syslog(LOG_ERR, "ACAP: unable to unreserve %s: %s\n", 
			   newmailboxname, error_message(r));
		}
		break;
	    }
#endif
	}
	/* make sure we've seen the update */
	if (res == PROXY_OK) kick_mupdate();
    }

    if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}

/*
 * Perform a FIND command
 */
void cmd_find(char *tag, char *namespace, char *pattern)
{
    char *p;
    lcase(namespace);

    for (p = pattern; *p; p++) {
	if (*p == '%') *p = '?';
    }

    /* Translate any separators in pattern */
    mboxname_hiersep_tointernal(&proxyd_namespace, pattern);

    if (!strcasecmp(namespace, "mailboxes")) {
	if (!backend_inbox) {
	    backend_inbox = proxyd_findinboxserver();
	}

	if (backend_inbox) {
	    prot_printf(backend_inbox->out, 
			"%s Lsub \"\" {%d+}\r\n%s\r\n",
			tag, strlen(pattern), pattern);
	    pipe_lsub(backend_inbox, tag, 0, 1);
	} else {		/* user doesn't have an INBOX */
	    /* noop */
	}
    } else if (!strcasecmp(namespace, "all.mailboxes")) {
	(*proxyd_namespace.mboxlist_findall)(&proxyd_namespace, pattern,
					     proxyd_userisadmin, proxyd_userid,
					     proxyd_authstate, mailboxdata,
					     NULL);
    } else if (!strcasecmp(namespace, "bboards")
	       || !strcasecmp(namespace, "all.bboards")) {
	;
    } else {
	prot_printf(proxyd_out, "%s BAD Invalid FIND subcommand\r\n", tag);
	return;
    }

    if (backend_current) {
	char mytag[128];

	proxyd_gentag(mytag);

	prot_printf(backend_current->out, "%s Noop\r\n", mytag);
	pipe_until_tag(backend_current, mytag, 0);
    }

    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}

/*
 * Perform a LIST or LSUB command
 * LISTs we do locally
 * LSUBs we farm out
 */
void cmd_list(char *tag, int subscribed, char *reference, char *pattern)
{
    char *buf = NULL;
    int patlen = 0;
    int reflen = 0;
    static int ignorereference = -1;

    /* Ignore the reference argument?
       (the behavior in 1.5.10 & older) */
    if (ignorereference == -1) {
	ignorereference = config_getswitch("ignorereference", 0);
    }

    /* Reset state in mstringdata */
    mstringdata(NULL, NULL, 0, 0);
    
    if (!pattern[0] && !subscribed) {
	/* Special case: query top-level hierarchy separator */
	prot_printf(proxyd_out, "* LIST (\\Noselect) \"%c\" \"\"\r\n",
		    proxyd_namespace.hier_sep);
    } else if (subscribed) {	/* do an LSUB command; contact our INBOX */
	if (!backend_inbox) {
	    backend_inbox = proxyd_findinboxserver();
	}

	if (backend_inbox) {
	    prot_printf(backend_inbox->out, 
			"%s Lsub {%d+}\r\n%s {%d+}\r\n%s\r\n",
			tag, strlen(reference), reference,
			strlen(pattern), pattern);
	    pipe_lsub(backend_inbox, tag, 0, 0);
	} else {		/* user doesn't have an INBOX */
	    /* noop */
	}
    } else {			/* do a LIST locally */
	/* Do we need to concatenate fields? */
	if (!ignorereference || pattern[0] == proxyd_namespace.hier_sep) {
	    /* Either
	     * - name begins with dot
	     * - we're configured to honor the reference argument */

	    /* Allocate a buffer, figure out how to stick the arguments
	       together, do it, then do that instead of using pattern. */
	    patlen = strlen(pattern);
	    reflen = strlen(reference);
	    
	    buf = xmalloc(patlen + reflen + 1);
	    buf[0] = '\0';

	    if (*reference) {
		/* check for LIST A. .B, change to LIST "" A.B */
		if (reference[reflen-1] == proxyd_namespace.hier_sep &&
		    pattern[0] == proxyd_namespace.hier_sep) {
		    reference[--reflen] = '\0';
		}
		strcpy(buf, reference);
	    }
	    strcat(buf, pattern);
	    pattern = buf;
	}

	/* Translate any separators in pattern */
	mboxname_hiersep_tointernal(&proxyd_namespace, pattern);

	(*proxyd_namespace.mboxlist_findall)(&proxyd_namespace, pattern,
					     proxyd_userisadmin, proxyd_userid,
					     proxyd_authstate, listdata, NULL);
	listdata((char *)0, 0, 0, 0);

	if (buf) free(buf);
    }

    if (backend_current && (!subscribed || backend_current != backend_inbox)) {
	/* our Lsub would've done this if 
	   backend_current == backend_inbox */
	char mytag[128];

	proxyd_gentag(mytag);

	prot_printf(backend_current->out, "%s Noop\r\n", mytag);
	pipe_until_tag(backend_current, mytag, 0);
    }

    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}
  
/*
 * Perform a SUBSCRIBE (add is nonzero) or
 * UNSUBSCRIBE (add is zero) command
 */
void cmd_changesub(char *tag, char *namespace, char *name, int add)
{
    char *cmd = add ? "Subscribe" : "Unsubscribe";
    int r = 0;

    if (!backend_inbox) {
	backend_inbox = proxyd_findinboxserver();
    }

    if (backend_inbox) {
	char mailboxname[MAX_MAILBOX_NAME+1];

	if (add) {
	    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
							name, proxyd_userid,
							mailboxname);
	    if(!r) r = mlookup(mailboxname, NULL, NULL, NULL);

	    /* Doesn't exist on murder */
	    if(r) goto done;
	}
	
	if (namespace) {
	    prot_printf(backend_inbox->out, 
			"%s %s {%d+}\r\n%s {%d+}\r\n%s\r\n", 
			tag, cmd, 
			strlen(namespace), namespace,
			strlen(name), name);
	} else {
	    prot_printf(backend_inbox->out, "%s %s {%d+}\r\n%s\r\n", 
			tag, cmd, 
			strlen(name), name);
	}
	pipe_including_tag(backend_inbox, tag, 0);
    } else {
	r = IMAP_SERVER_UNAVAILABLE;
    }

 done:
    if(r) {
	prot_printf(proxyd_out, "%s NO %s: %s\r\n", tag,
		    add ? "Subscribe" : "Unsubscribe", error_message(r));
    }
}

/*
 * Perform a GETACL command
 */
void cmd_getacl(char *tag, char *name, int oldform)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r, access;
    char *acl;
    char *rights, *nextid;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) r = mlookup(mailboxname, NULL, &acl, NULL);

    if (!r) {
	access = cyrus_acl_myrights(proxyd_authstate, acl);

	if (!(access & (ACL_READ|ACL_ADMIN)) &&
	    !proxyd_userisadmin &&
	    !mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
	    r = (access & ACL_LOOKUP) ?
	      IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
	}
    }
    if (r) {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
	return;
    }
    
    if (oldform) {
	while (acl) {
	    rights = strchr(acl, '\t');
	    if (!rights) break;
	    *rights++ = '\0';

	    nextid = strchr(rights, '\t');
	    if (!nextid) break;
	    *nextid++ = '\0';

	    prot_printf(proxyd_out, "* ACL MAILBOX ");
	    printastring(name);
	    prot_printf(proxyd_out, " ");
	    printastring(acl);
	    prot_printf(proxyd_out, " ");
	    printastring(rights);
	    prot_printf(proxyd_out, "\r\n");
	    acl = nextid;
	}
    }
    else {
	prot_printf(proxyd_out, "* ACL ");
	printastring(name);
	
	while (acl) {
	    rights = strchr(acl, '\t');
	    if (!rights) break;
	    *rights++ = '\0';

	    nextid = strchr(rights, '\t');
	    if (!nextid) break;
	    *nextid++ = '\0';

	    prot_printf(proxyd_out, " ");
	    printastring(acl);
	    prot_printf(proxyd_out, " ");
	    printastring(rights);
	    acl = nextid;
	}
	prot_printf(proxyd_out, "\r\n");
    }
    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}

/*
 * Perform a LISTRIGHTS command
 */
void cmd_listrights(char *tag, char *name, char *identifier)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r, rights;
    char *canon_identifier;
    int canonidlen = 0;
    char *acl;
    char *rightsdesc;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) {
	r = mlookup(mailboxname, (char **)0, &acl, NULL);
    }

    if (!r) {
	rights = cyrus_acl_myrights(proxyd_authstate, acl);

	if (!rights && !proxyd_userisadmin &&
	    !mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
	    r = IMAP_MAILBOX_NONEXISTENT;
	}
    }

    if (!r) {
	canon_identifier = auth_canonifyid(identifier, 0);
	if (canon_identifier) canonidlen = strlen(canon_identifier);

	if (!canon_identifier) {
	    rightsdesc = "\"\"";
	}
	else if (!strncmp(mailboxname, "user.", 5) &&
		 !strchr(canon_identifier, '.') &&
		 !strncmp(mailboxname+5, canon_identifier, canonidlen) &&
		 (mailboxname[5+canonidlen] == '\0' ||
		  mailboxname[5+canonidlen] == '.')) {
	    rightsdesc = "lca r s w i p d 0 1 2 3 4 5 6 7 8 9";
	}
	else {
	    rightsdesc = "\"\" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9";
	}

	prot_printf(proxyd_out, "* LISTRIGHTS ");
	printastring(name);
	prot_putc(' ', proxyd_out);
	printastring(identifier);
	prot_printf(proxyd_out, " %s", rightsdesc);

	prot_printf(proxyd_out, "\r\n%s OK %s\r\n", tag,
		    error_message(IMAP_OK_COMPLETED));
	return;
    }

    prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}

/*
 * Perform a MYRIGHTS command
 */
void cmd_myrights(char *tag, char *name, int oldform)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r, rights = 0;
    char *acl;
    char str[ACL_MAXSTR];

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) {
	r = mlookup(mailboxname, (char **)0, &acl, NULL);
    }

    if (!r) {
	rights = cyrus_acl_myrights(proxyd_authstate, acl);

	/* Add in implicit rights */
	if (proxyd_userisadmin ||
	    mboxname_userownsmailbox(proxyd_userid, mailboxname)) {
	    rights |= ACL_LOOKUP|ACL_ADMIN;
	}

	if (!rights) {
	    r = IMAP_MAILBOX_NONEXISTENT;
	}
    }
    if (r) {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
	return;
    }
    
    prot_printf(proxyd_out, "* MYRIGHTS ");
    if (oldform) prot_printf(proxyd_out, "MAILBOX ");
    printastring(name);
    prot_printf(proxyd_out, " ");
    printastring(cyrus_acl_masktostr(rights, str));
    prot_printf(proxyd_out, "\r\n%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}

/*
 * Perform a SETACL command
 */
void cmd_setacl(char *tag, char *name, char *identifier, char *rights)
{
    int r, res;
    char mailboxname[MAX_MAILBOX_NAME+1];
    char *server;
    struct backend *s = NULL;
    char *acl = NULL;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);
    if (!r) r = mlookup(mailboxname, &server, &acl, NULL);
    if (!r) {
	s = proxyd_findserver(server);
	if (!s) r = IMAP_SERVER_UNAVAILABLE;
    }

    if (!r && proxyd_userisadmin && supports_referrals) {
	/* They aren't an admin remotely, so let's refer them */
	proxyd_refer(tag, server, name);
	referral_kick = 1;
	return;
    } else if (!r) {
	if (rights) {
	    prot_printf(s->out, 
			"%s Setacl {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
			tag, strlen(name), name,
			strlen(identifier), identifier,
			strlen(rights), rights);
	} else {
	    prot_printf(s->out, 
			"%s Deleteacl {%d+}\r\n%s {%d+}\r\n%s\r\n",
			tag, strlen(name), name,
			strlen(identifier), identifier);
	}	    
	res = pipe_including_tag(s, tag, 0);
	tag = "*";		/* can't send another tagged response */
	if (!CAPA(s, MUPDATE) && res == PROXY_OK) {
#if 0
	    acapmbox_handle_t *acaphandle = acapmbox_get_handle();
	    int mode;
	    
	    /* calculate new ACL; race conditions here */
	    if (rights) {
		mode = ACL_MODE_SET;
		if (*rights == '+') {
		    rights++;
		    mode = ACL_MODE_ADD;
		} else if (*rights == '-') {
		    rights++;
		    mode = ACL_MODE_REMOVE;
		}
		
		if (cyrus_acl_set(&acl, identifier, mode, cyrus_acl_strtomask(rights),
			    NULL, proxyd_userid)) {
		    r = IMAP_INVALID_IDENTIFIER;
		}
	    } else {
		if (cyrus_acl_remove(&acl, identifier, NULL, proxyd_userid)) {
		    r = IMAP_INVALID_IDENTIFIER;
		}
	    }
	    
	    /* change the ACAP server */
	    r = acapmbox_setproperty_acl(acaphandle, mailboxname, acl);
	    if (r) {
		syslog(LOG_ERR, "ACAP: unable to change ACL on %s: %s\n", 
		       mailboxname, error_message(r));
	    }
#endif
	}
	/* make sure we've seen the update */
	if (ultraparanoid && res == PROXY_OK) kick_mupdate();
    }

    if (r) prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
}

/*
 * Callback for (get|set)quota, to ensure that all of the
 * submailboxes are on the same server.
 */
static int quota_cb(char *name, int matchlen, int maycreate, void *rock) 
{
    int r;
    char *this_server;
    const char *servername = (const char *)rock;
    
    r = mlookup(name, &this_server, NULL, NULL);
    if(r) return r;

    if(strcmp(servername, this_server)) {
	/* Not on same server as the root */
	return IMAP_NOT_SINGULAR_ROOT;
    } else {
	return PROXY_OK;
    }
}

/*
 * Perform a GETQUOTA command
 */
void cmd_getquota(char *tag, char *name)
{
    int r;
    char *server_rock = NULL, *server_rock_tmp = NULL;
    char mailboxname[MAX_MAILBOX_NAME+1];
    char quotarootbuf[MAX_MAILBOX_NAME + 3];

    if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
    else {
	r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
						    name,
						    proxyd_userid,
						    mailboxname);
    }

    if(!r)
	r = mlookup(mailboxname, &server_rock_tmp, NULL, NULL);

    if(!r) {
	server_rock = xstrdup(server_rock_tmp);

	snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);

	r = mboxlist_findall(&proxyd_namespace, quotarootbuf,
			     proxyd_userisadmin, proxyd_userid,
			     proxyd_authstate, quota_cb, server_rock);
    }

    if (!r) {
	/* Do the referral */
	proxyd_refer(tag, server_rock, mailboxname);
	free(server_rock);
    } else {
	if(server_rock) free(server_rock);
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    }
}

/*
 * Perform a GETQUOTAROOT command
 */
void cmd_getquotaroot(char *tag, char *name)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    char *server;
    int r;
    struct backend *s = NULL;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);
    if (!r) r = mlookup(mailboxname, &server, NULL, NULL);

    if(proxyd_userisadmin && supports_referrals) {
	/* If they are an admin, they won't be on the backend, so we
	 * should refer them if we can. */
	proxyd_refer(tag, server, name);
    } else {
	if (!r) s = proxyd_findserver(server);

	if (s) {
	    prot_printf(s->out, "%s Getquotaroot {%d+}\r\n%s\r\n",
			tag, strlen(name), name);
	    pipe_including_tag(s, tag, 0);
	} else {
	    r = IMAP_SERVER_UNAVAILABLE;
	}

	if (r) {
	    prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
	    return;
	}
    }
}

/*
 * Parse and perform a SETQUOTA command
 * The command has been parsed up to the resource list
 */
void cmd_setquota(char *tag, char *quotaroot)
{
    int r;
    char c;
    char *p;
    static struct buf arg;
    int badresource = 0;
    char *server_rock = NULL, *server_rock_tmp = NULL;
    char mailboxname[MAX_MAILBOX_NAME+1];
    char quotarootbuf[MAX_MAILBOX_NAME + 3];

    /* First ensure the validity of the command */
    c = prot_getc(proxyd_in);
    if (c != '(') goto badlist;

    /* xxx maybe we don't want to be this stringant on what types
     * of quota we allow to be set, since we will just be doing a referral
     * anyway... */
    c = getword(proxyd_in, &arg);
    if (c != ')' || arg.s[0] != '\0') {
	for (;;) {
	    if (c != ' ') goto badlist;
	    if (strcasecmp(arg.s, "storage") != 0) badresource = 1;
	    c = getword(proxyd_in, &arg);
	    if (c != ' ' && c != ')') goto badlist;
	    if (arg.s[0] == '\0') goto badlist;
	    /* We are just syntax checking here, no need to save the value */
	    for (p = arg.s; *p; p++) {
		if (!isdigit((int) *p)) goto badlist;
	    }
	    if (c == ')') break;
	}
    }
    c = prot_getc(proxyd_in);
    if (c == '\r') c = prot_getc(proxyd_in);
    if (c != '\n') {
	prot_printf(proxyd_out,
		    "%s BAD Unexpected extra arguments to SETQUOTA\r\n", tag);
	eatline(proxyd_in, c);
	return;
    }

    if(badresource) r = IMAP_UNSUPPORTED_QUOTA;
    else if(!proxyd_userisadmin) r = IMAP_PERMISSION_DENIED;
    else {
	r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace,
						    quotaroot,
						    proxyd_userid,
						    mailboxname);
    }

    if(!r)
	r = mlookup(mailboxname, &server_rock_tmp, NULL, NULL);

    if(!r) {
	server_rock = xstrdup(server_rock_tmp);

	snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);

	r = mboxlist_findall(&proxyd_namespace, quotarootbuf,
			     proxyd_userisadmin, proxyd_userid,
			     proxyd_authstate, quota_cb, server_rock);
    }

    if (!r) {
	/* Do the referral */
	proxyd_refer(tag, server_rock, mailboxname);
	free(server_rock);
    } else {
	if(server_rock) free(server_rock);
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    }

    return;

 badlist:
    prot_printf(proxyd_out, "%s BAD Invalid quota list in Setquota\r\n", tag);
    eatline(proxyd_in, c);
}

#ifdef HAVE_SSL
/*
 * this implements the STARTTLS command, as described in RFC 2595.
 * one caveat: it assumes that no external layer is currently present.
 * if a client executes this command, information about the external
 * layer that was passed on the command line is disgarded. this should
 * be fixed.
 */
/* imaps - weather this is an imaps transaction or not */
void cmd_starttls(char *tag, int imaps)
{
    int result;
    int *layerp;
    sasl_ssf_t ssf;
    char *auth_id;
    
    /* SASL and openssl have different ideas about whether ssf is signed */
    layerp = (int *) &(ssf);

    if (proxyd_starttls_done == 1)
    {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, 
		    "TLS already active");
	return;
    }

    result=tls_init_serverengine("imap",
				 5,        /* depth to verify */
				 !imaps,   /* can client auth? */
				 !imaps);  /* TLSv1 only? */

    if (result == -1) {

	syslog(LOG_ERR, "error initializing TLS");

	if (imaps == 0)
	    prot_printf(proxyd_out, "%s NO %s\r\n", 
			tag, "Error initializing TLS");
	else
	    fatal("tls_init() failed", EC_CONFIG);

	return;
    }

    if (imaps == 0)
    {
	prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		    "Begin TLS negotiation now");
	/* must flush our buffers before starting tls */
	prot_flush(proxyd_out);
    }
  
    result=tls_start_servertls(0, /* read */
			       1, /* write */
			       layerp,
			       &auth_id,
			       &tls_conn);

    /* if error */
    if (result==-1) {
	if (imaps == 0)	{
	    prot_printf(proxyd_out, "%s NO Starttls failed\r\n", tag);
	    syslog(LOG_NOTICE, "STARTTLS failed: %s", proxyd_clienthost);
	    return;
	} else {
	    syslog(LOG_NOTICE, "imaps failed: %s", proxyd_clienthost);
	    fatal("tls_start_servertls() failed", EC_TEMPFAIL);
	    return;
	}
    }

    /* tell SASL about the negotiated layer */
    result = sasl_setprop(proxyd_saslconn, SASL_SSF_EXTERNAL, &ssf);
    if (result != SASL_OK) {
	fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
    }
    saslprops.ssf = ssf;

    result = sasl_setprop(proxyd_saslconn, SASL_AUTH_EXTERNAL, auth_id);
    if (result != SASL_OK) {
       fatal("sasl_setprop() failed: cmd_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(proxyd_in, tls_conn);
    prot_settls(proxyd_out, tls_conn);

    proxyd_starttls_done = 1;
}
#else
void cmd_starttls(char *tag, int imaps)
{
    fatal("cmd_starttls() executed, but starttls isn't implemented!",
	  EC_SOFTWARE);
}
#endif /* HAVE_SSL */

/*
 * Parse and perform a STATUS command
 * The command has been parsed up to the attribute list
 */
void cmd_status(char *tag, char *name)
{
    char mailboxname[MAX_MAILBOX_NAME+1];
    int r;
    char *server;
    struct backend *s = NULL;

    r = (*proxyd_namespace.mboxname_tointernal)(&proxyd_namespace, name,
						proxyd_userid, mailboxname);

    if (!r) r = mlookup(mailboxname, &server, NULL, NULL);
    if (!r && supports_referrals
	&& config_getswitch("proxyd_allow_status_referral",0)) { 
	proxyd_refer(tag, server, mailboxname);
	/* Eat the argument */
	eatline(proxyd_in, prot_getc(proxyd_in));
	return;
    }

    if (!r) s = proxyd_findserver(server);
    if (!r && !s) r = IMAP_SERVER_UNAVAILABLE;
    if (!r) {
	prot_printf(s->out, "%s Status {%d+}\r\n%s ", tag,
		    strlen(name), name);
	if (!pipe_command(s, 65536)) {
	    pipe_until_tag(s, tag, 0);
	}
	if (backend_current && s != backend_current) {
	    char mytag[128];
	    
	    proxyd_gentag(mytag);

	    prot_printf(backend_current->out, "%s Noop\r\n", mytag);
	    pipe_until_tag(backend_current, mytag, 0);
	}
    } else {
	eatline(proxyd_in, prot_getc(proxyd_in));
    }

    if (!r) {
	prot_printf(proxyd_out, "%s %s", tag, s->last_result);
    } else {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    }
}

#ifdef ENABLE_X_NETSCAPE_HACK
/*
 * Reply to Netscape's crock with a crock of my own
 */
void
cmd_netscape(tag)
    char *tag;
{
    const char *url;
    /* so tempting, and yet ... */
    /* url = "http://random.yahoo.com/ryl/"; */
    url = config_getstring("netscapeurl",
			   "http://andrew2.andrew.cmu.edu/cyrus/imapd/netscape-admin.html");

    /* I only know of three things to reply with: */
    prot_printf(proxyd_out,
"* OK [NETSCAPE] Carnegie Mellon Cyrus IMAP proxy\r\n* VERSION %s\r\n",
		CYRUS_VERSION);
    prot_printf(proxyd_out,
		"* ACCOUNT-URL %s\r\n%s OK %s\r\n",
		url, tag, error_message(IMAP_OK_COMPLETED));

    /* no tagged response?!? */
}
#endif /* ENABLE_X_NETSCAPE_HACK */

/* Callback for cmd_namespace to be passed to mboxlist_findall.
 * For each top-level mailbox found, print a bit of the response
 * if it is a shared namespace.  The rock is used as an integer in
 * order to ensure the namespace response is correct on a server with
 * no shared namespace.
 */
static int namespacedata(name, matchlen, maycreate, rock)
    char* name;
    int matchlen;
    int maycreate;
    void* rock;
{
    int* sawone = (int*) rock;

    if (!name) {
	return 0;
    }
    
    if (!(strncmp(name, "INBOX.", 6))) {
	/* The user has a "personal" namespace. */
	sawone[NAMESPACE_INBOX] = 1;
    } else if (!(strncmp(name, "user.", 5))) {
	/* The user can see the "other users" namespace. */
	sawone[NAMESPACE_USER] = 1;
    } else {
	/* The user can see the "shared" namespace. */
	sawone[NAMESPACE_SHARED] = 1;
    }

    return 0;
}

/*
 * Print out a response to the NAMESPACE command defined by
 * RFC 2342.
 */
void cmd_namespace(tag)
    char* tag;
{
    int sawone[3] = {0, 0, 0};
    char pattern[2] = {'%','\0'};

    /* now find all the exciting toplevel namespaces -
     * we're using internal names here
     */
    mboxlist_findall(NULL, pattern, proxyd_userisadmin, proxyd_userid,
		     proxyd_authstate, namespacedata, (void*) sawone);

    prot_printf(proxyd_out, "* NAMESPACE");
    if (sawone[NAMESPACE_INBOX]) {
	prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
		    proxyd_namespace.prefix[NAMESPACE_INBOX],
		    proxyd_namespace.hier_sep);
    } else {
	prot_printf(proxyd_out, " NIL");
    }
    if (sawone[NAMESPACE_USER]) {
	prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
		    proxyd_namespace.prefix[NAMESPACE_USER],
		    proxyd_namespace.hier_sep);
    } else {
	prot_printf(proxyd_out, " NIL");
    }
    if (sawone[NAMESPACE_SHARED]) {
	prot_printf(proxyd_out, " ((\"%s\" \"%c\"))",
		    proxyd_namespace.prefix[NAMESPACE_SHARED],
		    proxyd_namespace.hier_sep);
    } else {
	prot_printf(proxyd_out, " NIL");
    }
    prot_printf(proxyd_out, "\r\n");

    prot_printf(proxyd_out, "%s OK %s\r\n", tag,
		error_message(IMAP_OK_COMPLETED));
}

/*
 * Print 's' as a quoted-string or literal (but not an atom)
 */
void
printstring(s)
const char *s;
{
    const char *p;
    int len = 0;

    /* Look for any non-QCHAR characters */
    for (p = s; *p && len < 1024; p++) {
	len++;
	if (*p & 0x80 || *p == '\r' || *p == '\n'
	    || *p == '\"' || *p == '%' || *p == '\\') break;
    }

    /* if it's too long, literal it */
    if (*p || len >= 1024) {
	prot_printf(proxyd_out, "{%u}\r\n%s", strlen(s), s);
    } else {
	prot_printf(proxyd_out, "\"%s\"", s);
    }
}

/*
 * Print 's' as an atom, quoted-string, or literal
 */
void printastring(const char *s)
{
    const char *p;
    int len = 0;

    if (imparse_isatom(s)) {
	prot_printf(proxyd_out, "%s", s);
	return;
    }

    /* Look for any non-QCHAR characters */
    for (p = s; *p && len < 1024; p++) {
	len++;
	if (*p & 0x80 || *p == '\r' || *p == '\n'
	    || *p == '\"' || *p == '%' || *p == '\\') break;
    }

    /* if it's too long, literal it */
    if (*p || len >= 1024) {
	prot_printf(proxyd_out, "{%u}\r\n%s", strlen(s), s);
    } else {
	prot_printf(proxyd_out, "\"%s\"", s);
    }
}

/*
 * Issue a MAILBOX untagged response
 */
static int mailboxdata(char *name, 
		       int matchlen, 
		       int maycreate, 
		       void* rock)
{
    char mboxname[MAX_MAILBOX_PATH+1];

    (*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, name,
					    proxyd_userid, mboxname);
    prot_printf(proxyd_out, "* MAILBOX %s\r\n", mboxname);
    return 0;
}

/*
 * Issue a LIST or LSUB untagged response
 */
static void mstringdata(cmd, name, matchlen, maycreate)
char *cmd;
char *name;
int matchlen;
int maycreate;
{
    static char lastname[MAX_MAILBOX_PATH];
    static int lastnamedelayed = 0;
    static int lastnamenoinferiors = 0;
    static int sawuser = 0;
    int lastnamehassub = 0;
    int c;
    char mboxname[MAX_MAILBOX_PATH+1];

    /* We have to reset the sawuser flag before each list command.
     * Handle it as a dirty hack.
     */
    if (cmd == NULL) {
	sawuser = 0;
	return;
    }
    
    if (lastnamedelayed) {
	if (name && strncmp(lastname, name, strlen(lastname)) == 0 &&
	    name[strlen(lastname)] == '.') {
	    lastnamehassub = 1;
	}
	prot_printf(proxyd_out, "* %s (%s) \"%c\" ", cmd,
		    lastnamenoinferiors ? "\\Noinferiors" :
		    lastnamehassub ? "\\HasChildren" : "\\HasNoChildren",
		    proxyd_namespace.hier_sep);
	(*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, lastname,
						proxyd_userid, mboxname);
	printstring(mboxname);
	prot_printf(proxyd_out, "\r\n");
	lastnamedelayed = lastnamenoinferiors = 0;
    }

    /* Special-case to flush any final state */
    if (!name) {
	lastname[0] = '\0';
	return;
    }

    /* Suppress any output of a partial match */
    if (name[matchlen] && strncmp(lastname, name, matchlen) == 0) {
	return;
    }
	
    /*
     * We can get a partial match for "user" multiple times with
     * other matches inbetween.  Handle it as a special case
     */
    if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) {
	if (sawuser) return;
	sawuser = 1;
    }

    strcpy(lastname, name);
    lastname[matchlen] = '\0';

    if (!name[matchlen]) {
	lastnamedelayed = 1;
	if (!maycreate) lastnamenoinferiors = 1;
	return;
    }

    c = name[matchlen];
    if (c) name[matchlen] = '\0';
    prot_printf(proxyd_out, "* %s (%s) \"%c\" ", cmd,
		c ? "\\HasChildren \\Noselect" : "",
		proxyd_namespace.hier_sep);
    (*proxyd_namespace.mboxname_toexternal)(&proxyd_namespace, name,
					    proxyd_userid, mboxname);
    printstring(mboxname);
    prot_printf(proxyd_out, "\r\n");
    if (c) name[matchlen] = c;
    return;
}

/*
 * Issue a LIST untagged response
 */
static int listdata(char *name, int matchlen, int maycreate, void *rock)
{
    mstringdata("LIST", name, matchlen, maycreate);
    return 0;
}

#ifdef ENABLE_ANNOTATEMORE
/*
 * Parse annotate fetch data.
 *
 * This is a generic routine which parses just the annotation data.
 * Any surrounding command text must be parsed elsewhere, ie,
 * GETANNOTATION, FETCH.
 */

int getannotatefetchdata(char *tag,
			 struct strlist **entries, struct strlist **attribs)
{
    int c;
    static struct buf arg;

    *entries = *attribs = NULL;

    c = prot_getc(proxyd_in);
    if (c == EOF) {
	prot_printf(proxyd_out,
		    "%s BAD Missing annotation entry\r\n", tag);
	goto baddata;
    }
    else if (c == '(') {
	/* entry list */
	do {
	    c = getqstring(proxyd_in, proxyd_out, &arg);
	    if (c == EOF) {
		prot_printf(proxyd_out,
			    "%s BAD Missing annotation entry\r\n", tag);
		goto baddata;
	    }

	    /* add the entry to the list */
	    appendstrlist(entries, arg.s);

	} while (c == ' ');

	if (c != ')') {
	    prot_printf(proxyd_out,
			"%s BAD Missing close paren in annotation entry list \r\n",
			tag);
	    goto baddata;
	}

	c = prot_getc(proxyd_in);
    }
    else {
	/* single entry -- add it to the list */
	prot_ungetc(c, proxyd_in);
	c = getqstring(proxyd_in, proxyd_out, &arg);
	if (c == EOF) {
	    prot_printf(proxyd_out,
			"%s BAD Missing annotation entry\r\n", tag);
	    goto baddata;
	}

	appendstrlist(entries, arg.s);
    }

    if (c != ' ' || (c = prot_getc(proxyd_in)) == EOF) {
	prot_printf(proxyd_out,
		    "%s BAD Missing annotation attribute(s)\r\n", tag);
	goto baddata;
    }

    if (c == '(') {
	/* attrib list */
	do {
	    c = getnstring(proxyd_in, proxyd_out, &arg);
	    if (c == EOF) {
		prot_printf(proxyd_out,
			    "%s BAD Missing annotation attribute(s)\r\n", tag);
		goto baddata;
	    }

	    /* add the attrib to the list */
	    appendstrlist(attribs, arg.s);

	} while (c == ' ');

	if (c != ')') {
	    prot_printf(proxyd_out,
			"%s BAD Missing close paren in "
			"annotation attribute list\r\n", tag);
	    goto baddata;
	}

	c = prot_getc(proxyd_in);
    }
    else {
	/* single attrib */
	prot_ungetc(c, proxyd_in);
	c = getqstring(proxyd_in, proxyd_out, &arg);
	    if (c == EOF) {
		prot_printf(proxyd_out,
			    "%s BAD Missing annotation attribute\r\n", tag);
		goto baddata;
	    }

	appendstrlist(attribs, arg.s);
   }

    return c;

  baddata:
    if (c != EOF) prot_ungetc(c, proxyd_in);
    return EOF;
}

/*
 * Parse annotate store data.
 *
 * This is a generic routine which parses just the annotation data.
 * Any surrounding command text must be parsed elsewhere, ie,
 * SETANNOTATION, STORE, APPEND.
 */

int getannotatestoredata(char *tag, struct entryattlist **entryatts)
{
    int c;
    static struct buf entry, attrib, value;
    struct attvaluelist *attvalues = NULL;

    *entryatts = NULL;

    do {
	/* get entry */
	c = getqstring(proxyd_in, proxyd_out, &entry);
	if (c == EOF) {
	    prot_printf(proxyd_out,
			"%s BAD Missing annotation entry\r\n", tag);
	    goto baddata;
	}

	/* parse att-value list */
	if (c != ' ' || (c = prot_getc(proxyd_in)) != '(') {
	    prot_printf(proxyd_out,
			"%s BAD Missing annotation attribute-values list\r\n",
			tag);
	    goto baddata;
	}

	do {
	    /* get attrib */
	    c = getqstring(proxyd_in, proxyd_out, &attrib);
	    if (c == EOF) {
		prot_printf(proxyd_out,
			    "%s BAD Missing annotation attribute\r\n", tag);
		goto baddata;
	    }

	    /* get value */
	    if (c != ' ' ||
		(c = getnstring(proxyd_in, proxyd_out, &value)) == EOF) {
		prot_printf(proxyd_out,
			    "%s BAD Missing annotation value\r\n", tag);
		goto baddata;
	    }

	    /* add the attrib-value pair to the list */
	    appendattvalue(&attvalues, attrib.s, value.s);

	} while (c == ' ');

	if (c != ')') {
	    prot_printf(proxyd_out,
			"%s BAD Missing close paren in annotation "
			"attribute-values list\r\n", tag);
	    goto baddata;
	}

	/* add the entry to the list */
	appendentryatt(entryatts, entry.s, attvalues);
	attvalues = NULL;

	c = prot_getc(proxyd_in);

    } while (c == ' ');

    return c;

  baddata:
    if (attvalues) freeattvalues(attvalues);
    if (c != EOF) prot_ungetc(c, proxyd_in);
    return EOF;
}

/*
 * Output an entry/attribute-value list response.
 *
 * This is a generic routine which outputs just the annotation data.
 * Any surrounding response text must be output elsewhere, ie,
 * GETANNOTATION, FETCH. 
 */
void annotate_response(struct entryattlist *l)
{
    int islist; /* do we have more than one entry? */

    if (!l) return;

    islist = (l->next != NULL);

    if (islist) prot_printf(proxyd_out, "(");

    while (l) {
	prot_printf(proxyd_out, "\"%s\"", l->entry);

	/* do we have attributes?  solicited vs. unsolicited */
	if (l->attvalues) {
	    struct attvaluelist *av = l->attvalues;

	    prot_printf(proxyd_out, " (");
	    while (av) {
		prot_printf(proxyd_out, "\"%s\" ", av->attrib);
		if (!strcasecmp(av->value, "NIL"))
		    prot_printf(proxyd_out, "NIL");
		else
		    prot_printf(proxyd_out, "\"%s\"", av->value);

		if ((av = av->next) == NULL)
		    prot_printf(proxyd_out, ")");
		else
		    prot_printf(proxyd_out, " ");
	    }
	}
	if ((l = l->next) != NULL)
	    prot_printf(proxyd_out, " ");
    }

    if (islist) prot_printf(proxyd_out, ")");
}

/*
 * Perform a GETANNOTATION command
 *
 * The command has been parsed up to the entries
 */    
void cmd_getannotation(char *tag)
{
    int c, r = 0;
    struct strlist *entries = NULL, *attribs = NULL;
    struct entryattlist *entryatts = NULL;

    c = getannotatefetchdata(tag, &entries, &attribs);
    if (c == EOF) {
	eatline(proxyd_in, c);
	return;
    }


    /* check for CRLF */
    if (c == '\r') c = prot_getc(proxyd_in);
    if (c != '\n') {
	prot_printf(proxyd_out,
		    "%s BAD Unexpected extra arguments to Getannotation\r\n",
		    tag);
	eatline(proxyd_in, c);
	goto freeargs;
    }

    r = annotatemore_fetch(entries, attribs, &proxyd_namespace,
			   proxyd_userisadmin, proxyd_userid,
			   proxyd_authstate, &entryatts);

    if (r) {
	prot_printf(proxyd_out, "%s NO %s\r\n", tag, error_message(r));
    }
    else if (entryatts) {
	prot_printf(proxyd_out, "* ANNOTATION ");
	annotate_response(entryatts);
	prot_printf(proxyd_out, "\r\n");
	prot_printf(proxyd_out, "%s OK %s\r\n",
		    tag, error_message(IMAP_OK_COMPLETED));
    }
    else
	prot_printf(proxyd_out, "%s NO %s\r\n", tag,
		    error_message(IMAP_NO_NOSUCHANNOTATION));

  freeargs:
    if (entries) freestrlist(entries);
    if (attribs) freestrlist(attribs);
    if (entryatts) freeentryatts(entryatts);

    return;
}

/*
 * Perform a SETANNOTATION command
 *
 * The command has been parsed up to the entry-att list
 */    
void cmd_setannotation(char *tag)
{
    int c;
    struct entryattlist *entryatts = NULL;

    c = getannotatestoredata(tag, &entryatts);
    if (c == EOF) {
	eatline(proxyd_in, c);
	return;
    }

    /* check for CRLF */
    if (c == '\r') c = prot_getc(proxyd_in);
    if (c != '\n') {
	prot_printf(proxyd_out,
		    "%s BAD Unexpected extra arguments to Setannotation\r\n",
		    tag);
	eatline(proxyd_in, c);
	goto freeargs;
    }

    prot_printf(proxyd_out, "%s NO setting annotations not supported\r\n", tag);

  freeargs:
    if (entryatts) freeentryatts(entryatts);
    return;
}

/*
 * Append 's' to the strlist 'l'.
 */
void
appendstrlist(l, s)
struct strlist **l;
char *s;
{
    struct strlist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct strlist *)xmalloc(sizeof(struct strlist));
    (*tail)->s = xstrdup(s);
    (*tail)->p = 0;
    (*tail)->next = 0;
}

/*
 * Free the strlist 'l'
 */
void
freestrlist(l)
struct strlist *l;
{
    struct strlist *n;

    while (l) {
	n = l->next;
	free(l->s);
	if (l->p) charset_freepat(l->p);
	free((char *)l);
	l = n;
    }
}

/*
 * Append the 'attrib'/'value' pair to the attvaluelist 'l'.
 */
void appendattvalue(struct attvaluelist **l, char *attrib, char *value)
{
    struct attvaluelist **tail = l;

    while (*tail) tail = &(*tail)->next;

    *tail = (struct attvaluelist *)xmalloc(sizeof(struct attvaluelist));
    (*tail)->attrib = xstrdup(attrib);
    (*tail)->value = xstrdup(value);
    (*tail)->next = 0;
}

/*
 * Free the attvaluelist 'l'
 */
void freeattvalues(struct attvaluelist *l)
{
    struct attvaluelist *n;

    while (l) {
	n = l->next;
	free(l->attrib);
	free(l->value);
	l = n;
    }
}
#endif /* ENABLE_ANNOTATEMORE */

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

    sasl_dispose(conn);
    /* do initialization typical of service_main */
    ret = sasl_server_new("imap", 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;
    
    secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
    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);
    } else {
       ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_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;
}
