parser.c   [plain text]


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

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>

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


#include "libconfig.h"
#include "global.h"
#include "auth.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "xmalloc.h"
#include "prot.h"
#include "tls.h"
#include "lex.h"
#include "actions.h"
#include "exitcodes.h"
#include "telemetry.h"

#include "AppleOD.h"

extern char sieved_clienthost[250];
extern int sieved_domainfromip;

/* xxx these are both leaked, but we only handle one connection at a
 * time... */
extern sasl_conn_t *sieved_saslconn; /* the sasl connection context */
const char *referral_host = NULL;

int authenticated = 0;
int verify_only = 0;
int starttls_done = 0;
#ifdef HAVE_SSL
/* our tls connection, if any */
static SSL *tls_conn = NULL;
#endif /* HAVE_SSL */

/* from elsewhere */
void fatal(const char *s, int code);
extern int sieved_logfd;
extern struct od_user_opts	*gUserOpts;

/* forward declarations */
static void cmd_logout(struct protstream *sieved_out,
		       struct protstream *sieved_in);
static int cmd_authenticate(struct protstream *sieved_out, struct protstream *sieved_in,
			    mystring_t *mechanism_name, mystring_t *initial_challenge, const char **errmsg);
static int cmd_od_authenticate(struct protstream *sieved_out, struct protstream *sieved_in,
			    mystring_t *mechanism_name, mystring_t *initial_challenge, const char **errmsg);
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in);

/* Returns TRUE if we are done */
int parser(struct protstream *sieved_out, struct protstream *sieved_in)
{
  int token = EOL;
  const char *error_msg = "Generic Error";

  mystring_t *mechanism_name = NULL;
  mystring_t *initial_challenge = NULL;
  mystring_t *sieve_name = NULL;
  mystring_t *sieve_data = NULL;
  unsigned long num;
  int ret = FALSE;

  /* get one token from the lexer */
  while(token == EOL) 
      token = timlex(NULL, NULL, sieved_in);

  if (!authenticated && (token > 255) && (token!=AUTHENTICATE) &&
      (token!=LOGOUT) && (token!=CAPABILITY) &&
      (!tls_enabled() || (token!=STARTTLS)))
  {
    error_msg = "Authenticate first";
    if (token!=EOL)
      lex_setrecovering();

    goto error;
  }

  if (verify_only && (token > 255) && (token!=PUTSCRIPT) && (token!=LOGOUT))
  {
    error_msg = "Script verification only";
    if (token!=EOL)
      lex_setrecovering();

    goto error;
  }

  switch (token)
  {
  case AUTHENTICATE:
    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "SPACE must occur after AUTHENTICATE";
      goto error;
    }

    if (timlex(&mechanism_name, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify mechanism name";
      goto error;
    }

    token = timlex(NULL, NULL, sieved_in);

    if (token != EOL)
    {
      /* optional client first challenge */
      if (token!=SPACE)
      {
	error_msg = "Expected SPACE";
	goto error;
      }

      if (timlex(&initial_challenge, NULL, sieved_in)!=STRING)
      {
	error_msg = "Expected string";
	goto error;
      }

      token = timlex(NULL, NULL, sieved_in);      
    }

    if (token != EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if (authenticated)
	prot_printf(sieved_out, "NO \"Already authenticated\"\r\n");
	else
	{
		if ( config_getswitch( IMAPOPT_APPLE_AUTH ) == 0 )
		{
			if (cmd_authenticate(sieved_out, sieved_in, mechanism_name,
						  initial_challenge, &error_msg)==FALSE)
			{
				error_msg = "Authentication Error";
				goto error;
			}
		}
		else
		{
			if (cmd_od_authenticate(sieved_out, sieved_in, mechanism_name,
						  initial_challenge, &error_msg)==FALSE)
			{
				error_msg = "Authentication Error";
				goto error;
			}
		}
	}

#if 0 /* XXX - not implemented in sieveshell*/
    /* referral_host is non-null only once we are authenticated */
    if(referral_host)
	goto do_referral;
#endif
    break;

  case CAPABILITY:
      if (timlex(NULL, NULL, sieved_in)!=EOL)
      {
	  error_msg = "Expected EOL";
	  goto error;
      }

      if(referral_host)
	  goto do_referral;

      capabilities(sieved_out, sieved_saslconn, starttls_done, authenticated);
      break;

  case HAVESPACE:
      if (timlex(NULL, NULL, sieved_in)!=SPACE)
      {
	  error_msg = "SPACE must occur after HAVESPACE";
	  goto error;
      }
      
      if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
      {
	  error_msg = "Did not specify script name";
	  goto error;
      }
      
      if (timlex(NULL, NULL, sieved_in)!=SPACE)
      {
	  error_msg = "Expected SPACE";
	  goto error;
      }
      
      if (timlex(NULL, &num, sieved_in)!=NUMBER)
      {
	  error_msg = "Expected Number";
	  goto error;
      }

      if (timlex(NULL, NULL, sieved_in)!=EOL)
      {
	  error_msg = "Expected EOL";
	  goto error;
      }

      if(referral_host)
	  goto do_referral;

      cmd_havespace(sieved_out, sieve_name, num);

      break;

  case LOGOUT:
      token = timlex(NULL, NULL, sieved_in);
      
      /* timlex() will return LOGOUT when the remote disconnects badly */
      if (token!=EOL && token!=EOF && token!=LOGOUT)
      {
	  error_msg = "Garbage after logout command";
	  goto error;
      }

      /* no referral for logout */

      cmd_logout(sieved_out, sieved_in);
      
      ret = TRUE;
      goto done;
      break;

  case GETSCRIPT:
    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "SPACE must occur after GETSCRIPT";
      goto error;
    }

    if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify script name";
      goto error;
    }

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;

    getscript(sieved_out, sieve_name);
    
    break;


  case PUTSCRIPT:
    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "SPACE must occur after PUTSCRIPT";
      goto error;
    }

    if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify script name";
      goto error;
    }

    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "Expected SPACE";
      goto error;
    }

    if (timlex(&sieve_data, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify legal script data length";
      goto error;
    }

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;

    putscript(sieved_out, sieve_name, sieve_data, verify_only);
    
    break;

  case SETACTIVE:
    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "SPACE must occur after SETACTIVE";
      goto error;
    }

    if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify script name";
      goto error;
    }

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;

    setactive(sieved_out, sieve_name);
    
    break;

  case DELETESCRIPT:
    if (timlex(NULL, NULL, sieved_in)!=SPACE)
    {
      error_msg = "SPACE must occur after DELETESCRIPT";
      goto error;
    }

    if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
    {
      error_msg = "Did not specify script name";
      goto error;
    }

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;

    deletescript(sieved_out, sieve_name);
    
    break;

  case LISTSCRIPTS:

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;
    
    listscripts(sieved_out);
    
    break;

  case STARTTLS:

    if (timlex(NULL, NULL, sieved_in)!=EOL)
    {
      error_msg = "Expected EOL";
      goto error;
    }

    if(referral_host)
	goto do_referral;

    cmd_starttls(sieved_out, sieved_in);
    
    break;

  default:
    error_msg="Expected a command. Got something else.";
    goto error;
    break;

  }

 done: 
  /* free memory */
  free(mechanism_name);
  free(initial_challenge);
  free(sieve_name);
  free(sieve_data);
 
  prot_flush(sieved_out);

  return ret;

 error:

  /* free memory */
  free(mechanism_name);
  free(initial_challenge);
  free(sieve_name);
  free(sieve_data);

  prot_printf(sieved_out, "NO \"%s\"\r\n",error_msg);
  prot_flush(sieved_out);

  return FALSE;

 do_referral:
  {
      char buf[4096];
      char *c;

      /* Truncate the hostname if necessary */
      strlcpy(buf, referral_host, sizeof(buf));
      c = strchr(buf, '!');
      if(c) *c = '\0';
      
      prot_printf(sieved_out, "BYE (REFERRAL \"sieve://%s\") \"Try Remote.\"\r\n",
		  buf);
      ret = TRUE;
      goto done;
  }

}


void cmd_logout(struct protstream *sieved_out,
		struct protstream *sieved_in __attribute__((unused)))
{
    prot_printf(sieved_out, "OK \"Logout Complete\"\r\n");
    prot_flush(sieved_out);
}

static sasl_ssf_t ssf = 0;
static char *authid = NULL;

extern int reset_saslconn(sasl_conn_t **conn, sasl_ssf_t ssf, char *authid);

static int cmd_authenticate(struct protstream *sieved_out,
			    struct protstream *sieved_in,
			    mystring_t *mechanism_name,
			    mystring_t *initial_challenge, 
			    const char **errmsg)
{

  int sasl_result;

  char *mech = string_DATAPTR(mechanism_name);

  mystring_t *clientinstr=NULL;
  char *clientin = NULL;
  unsigned int clientinlen = 0;

  const char *serverout=NULL;
  unsigned int serveroutlen;
  const char *errstr=NULL;
  const char *canon_user;
  char *username;
  int ret = TRUE;

  clientinstr = initial_challenge;
  if (clientinstr!=NULL)
  {
      clientin=(char *) malloc(clientinstr->len*2);
      
      if (clientinstr->len) {
	  sasl_result=sasl_decode64(string_DATAPTR(clientinstr), 
				    clientinstr->len,
				    clientin, clientinstr->len*2,
				    &clientinlen);
      } else {
	  clientinlen = 0;
	  sasl_result = SASL_OK;
      }

      if (sasl_result!=SASL_OK)
      {
	*errmsg="error base64 decoding string";
	syslog(LOG_NOTICE, "badlogin: %s %s %s",
	       sieved_clienthost, mech, "error base64 decoding string");

      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	  fatal("could not reset the sasl_conn_t after failure",
		EC_TEMPFAIL);

	return FALSE;
      }
  }

  sasl_result = sasl_server_start(sieved_saslconn, mech,
				  clientin, clientinlen,
				  &serverout, &serveroutlen);

  while (sasl_result==SASL_CONTINUE)
  {
    int token1;
    int token2;
    mystring_t *str, *blahstr;
    char *inbase64;
    unsigned int inbase64len;

    /* convert to base64 */
    inbase64 = xmalloc(serveroutlen*2+1);
    sasl_encode64(serverout, serveroutlen,
		  inbase64, serveroutlen*2+1, &inbase64len);

    /* send out the string always as a literal */
    prot_printf(sieved_out, "{%d}\r\n",inbase64len);
    prot_write(sieved_out,inbase64,inbase64len);
    prot_printf(sieved_out,"\r\n");

    token1 = timlex(&str, NULL, sieved_in);

    if (token1==STRING)
    {
      clientin=(char *) malloc(str->len*2);

      if (str->len) {
	  sasl_result=sasl_decode64(string_DATAPTR(str), str->len,
				    clientin, str->len*2, &clientinlen);
      } else {
	  clientinlen = 0;
	  sasl_result = SASL_OK;
      }

      if (sasl_result!=SASL_OK)
      {
	*errmsg="error base64 decoding string";
	syslog(LOG_NOTICE, "badlogin: %s %s %s",
	       sieved_clienthost, mech, "error base64 decoding string");

      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	  fatal("could not reset the sasl_conn_t after failure",
		EC_TEMPFAIL);

	return FALSE;
      }      
      
    } else {
      *errmsg="Expected STRING-xxx1";

      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	  fatal("could not reset the sasl_conn_t after failure",
		EC_TEMPFAIL);

      return FALSE;
    }

    token2 = timlex(&blahstr, NULL, sieved_in);

    /* we want to see a STRING followed by EOL */
    if ((token1==STRING) && (token2==EOL))
    {
      sasl_result = sasl_server_step(sieved_saslconn,
				     clientin,
				     clientinlen,
				     &serverout, &serveroutlen);
    } else {
      *errmsg = "expected a STRING followed by an EOL";
      syslog(LOG_NOTICE, "badlogin: %s %s %s",
	     sieved_clienthost, mech, "expected string");

      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	  fatal("could not reset the sasl_conn_t after failure",
		EC_TEMPFAIL);

      return FALSE;
    }

  }

  if (sasl_result!=SASL_OK)
  {
      /* convert to user error code */
      if(sasl_result == SASL_NOUSER)
	  sasl_result = SASL_BADAUTH;
      *errmsg = (const char *) sasl_errstring(sasl_result,NULL,NULL);
      if (errstr!=NULL) {
	  syslog(LOG_NOTICE, "badlogin: %s %s %d %s",
		 sieved_clienthost, mech, sasl_result, errstr);
      } else { 
	  syslog(LOG_NOTICE, "badlogin: %s %s %s",
		 sieved_clienthost, mech, 
		 sasl_errstring(sasl_result, NULL, NULL));
      }

      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	  fatal("could not reset the sasl_conn_t after failure",
		EC_TEMPFAIL);

      return FALSE;
  }

  /* get the userid from SASL */
  sasl_result=sasl_getprop(sieved_saslconn, SASL_USERNAME,
			   (const void **) &canon_user);
  if (sasl_result!=SASL_OK)
  {
    *errmsg = "Internal SASL error";
    syslog(LOG_ERR, "SASL: sasl_getprop SASL_USERNAME: %s",
	   sasl_errstring(sasl_result, NULL, NULL));

    if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	fatal("could not reset the sasl_conn_t after failure",
	      EC_TEMPFAIL);

    return FALSE;
  }
  username = xstrdup(canon_user);

  verify_only = !strcmp(username, "anonymous");

  if (!verify_only) {
      /* Check for a remote mailbox (should we setup a redirect?) */
      struct namespace sieved_namespace;
      char inboxname[MAX_MAILBOX_NAME];
      char *server;
      int type, r;
      
      /* Set namespace */
      if ((r = mboxname_init_namespace(&sieved_namespace, 0)) != 0) {
	  syslog(LOG_ERR, error_message(r));
	  fatal(error_message(r), EC_CONFIG);
      }

      /* Translate any separators in userid */
      mboxname_hiersep_tointernal(&sieved_namespace, username,
				  config_virtdomains ?
				  strcspn(username, "@") : 0);

      (*sieved_namespace.mboxname_tointernal)(&sieved_namespace, "INBOX",
					     username, inboxname);

      r = mboxlist_detail(inboxname, &type, &server, NULL, NULL, NULL);
      
      if(r) {
	  /* mboxlist_detail error */
	  *errmsg = "mailbox unknown";
	  return FALSE;
      }

      if(type & MBTYPE_REMOTE) {
	  /* It's a remote mailbox, we want to set up a referral */
	  if (sieved_domainfromip) {
	      char *authname, *p;

	      /* get a new copy of the userid */
	      free(username);
	      username = xstrdup(canon_user);

	      /* get the authid from SASL */
	      sasl_result=sasl_getprop(sieved_saslconn, SASL_AUTHUSER,
				       (const void **) &canon_user);
	      if (sasl_result!=SASL_OK)
		  {
		      *errmsg = "Internal SASL error";
		      syslog(LOG_ERR, "SASL: sasl_getprop SASL_AUTHUSER: %s",
			     sasl_errstring(sasl_result, NULL, NULL));

		      if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
			  fatal("could not reset the sasl_conn_t after failure",
				EC_TEMPFAIL);

		      ret = FALSE;
		      goto cleanup;
		  }
	      authname = xstrdup(canon_user);

	      if ((p = strchr(authname, '@'))) *p = '%';
	      if ((p = strchr(username, '@'))) *p = '%';

	      referral_host =
		  (char*) xmalloc(strlen(authname)+1+strlen(username)+1+
				  strlen(server)+1);
	      sprintf((char*) referral_host, "%s;%s@%s",
		      authname, username, server);

	      free(authname);
	  }
	  else
	      referral_host = xstrdup(server);
      } else if (actions_setuser(username) != TIMSIEVE_OK) {
	  *errmsg = "internal error";
	  syslog(LOG_ERR, "error in actions_setuser()");

	  if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
	      fatal("could not reset the sasl_conn_t after failure",
		    EC_TEMPFAIL);

	  ret = FALSE;
	  goto cleanup;
      }
  }

  /* Yay! authenticated */
  if(serverout) {
      char *inbase64;
      unsigned int inbase64len;

      /* convert to base64 */
      inbase64 = xmalloc(serveroutlen*2+1);
      sasl_encode64(serverout, serveroutlen,
		    inbase64, serveroutlen*2+1, &inbase64len);

      prot_printf(sieved_out, "OK (SASL \"%s\")\r\n", inbase64);
      free(inbase64);
  } else {
      prot_printf(sieved_out, "OK\r\n");
  }
  
  syslog(LOG_NOTICE, "login: %s %s %s%s %s", sieved_clienthost, username,
	 mech, starttls_done ? "+TLS" : "", "User logged in");

  authenticated = 1;

  prot_setsasl(sieved_in, sieved_saslconn);
  prot_setsasl(sieved_out, sieved_saslconn);

  /* Create telemetry log */
  sieved_logfd = telemetry_log(username, sieved_in, sieved_out, 0);
  
  cleanup:
  /* free memory */
  free(username);

  return ret;
}

static int cmd_od_authenticate ( struct protstream *sieved_out,
								 struct protstream *sieved_in,
								 mystring_t *mechanism_name,
								 mystring_t *initial_challenge, 
								 const char **errmsg)
{
	int			 r				= 0;
	char		*mech			= string_DATAPTR( mechanism_name );
	char		*chal			= NULL;
	const char	*serverout		= NULL;
	unsigned int serveroutlen;
	const char	*errstr			= NULL;
	char		*username;
	int			 ret			= TRUE;

	/* get initial challenge pointer if any */
	if ( initial_challenge != NULL )
	{
		chal = string_DATAPTR( initial_challenge );
	}

	if ( strcasecmp( mech, "CRAM-MD5" ) == 0 )
	{
		mech = "SIEVE-CRAM-MD5";
	}
	else if ( strcasecmp( mech, "LOGIN" ) == 0 )
	{
		mech = "SIEVE-LOGIN";
	}
	else if ( strcasecmp( mech, "PLAIN" ) == 0 )
	{
		mech = "SIEVE-PLAIN";
	}

	/* do open directory authentication */
	r = odDoAuthenticate( mech, chal, "+ ", kXMLIMAP_Principal, sieved_in, sieved_out, gUserOpts );
	if ( r != eAODNoErr )
	{
		syslog( LOG_NOTICE, "badlogin: %s %s (%d)", sieved_clienthost, mech, r );

		return( FALSE );
	}
	else if ( gUserOpts->fAuthIDNamePtr != NULL )
	{
		int			len		= 0;
		int			isAdmin	= 0;
		char		buf[ 1024 ];
		const char *val = config_getstring( IMAPOPT_ADMINS );

		/* Is the option defined? */
		if ( !val )
		{
			return( FALSE );
		}

		while ( *val )
		{
			char *p	= NULL;
			
			for( p = (char *) val; *p && !isspace((int) *p); p++ );
			len = p-val;
			if( len >= sizeof( buf ) )
			{
				len = sizeof( buf ) - 1;
			}
			memcpy( buf, val, len );
			buf[len] = '\0';

			if ( strcmp( gUserOpts->fAuthIDNamePtr, buf ) == 0 )
			{
				isAdmin = 1;
				break;
			}

			val = p;
			while ( *val && isspace( (int)*val ) )
			{
				val++;
			}
		}

		if ( !isAdmin )
		{
			syslog(LOG_ERR, "illegal authorization attempt: - %s - is not an admin user", gUserOpts->fAuthIDNamePtr );
			return( FALSE );
		}
	}

	/* duplicate the userid returned from open directory authentication */
	username = xstrdup( gUserOpts->fRecNamePtr );

	/* get verify only */
	verify_only = !strcmp(username, "anonymous");
	if ( !verify_only )
	{
	  /* Check for a remote mailbox (should we setup a redirect?) */
	  struct namespace sieved_namespace;
	  char	inboxname[ MAX_MAILBOX_NAME ];
	  char	*server;
	  int	type;
	  int	r;
	  
	  /* Set namespace */
	  if ((r = mboxname_init_namespace(&sieved_namespace, 0)) != 0)
	  {
	  syslog(LOG_ERR, error_message(r));
	  fatal(error_message(r), EC_CONFIG);
	  }

	  /* Translate any separators in userid */
	  mboxname_hiersep_tointernal(&sieved_namespace, username,
				  config_virtdomains ?
				  strcspn(username, "@") : 0);

	  (*sieved_namespace.mboxname_tointernal)(&sieved_namespace, "INBOX",
						 username, inboxname);

	  r = mboxlist_detail(inboxname, &type, &server, NULL, NULL, NULL);
	  
	  if(r) {
	  /* mboxlist_detail error */
	  *errmsg = "mailbox unknown";
	  return FALSE;
	  }

	  if(type & MBTYPE_REMOTE) {
	  // It's a remote mailbox, we want to set up a referral 
		  fatal("remote host not supported", EC_UNAVAILABLE);
	  } else if (actions_setuser(username) != TIMSIEVE_OK) {
	  *errmsg = "internal error";
	  syslog(LOG_ERR, "error in actions_setuser()");

	  if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
		  fatal("could not reset the sasl_conn_t after failure",
			EC_TEMPFAIL);

	  ret = FALSE;
	  goto cleanup;
	  }
	}

	/* Yay! authenticated */
	if(serverout) {
	  char *inbase64;
	  unsigned int inbase64len;

	  /* convert to base64 */
	  inbase64 = xmalloc(serveroutlen*2+1);
	  sasl_encode64(serverout, serveroutlen,
			inbase64, serveroutlen*2+1, &inbase64len);

	  prot_printf(sieved_out, "OK (SASL \"%s\")\r\n", inbase64);
	  free(inbase64);
	} else {
	  prot_printf(sieved_out, "OK\r\n");
	}

	syslog(LOG_NOTICE, "login: %s %s %s%s %s", sieved_clienthost, username,
	 mech, starttls_done ? "+TLS" : "", "User logged in");

	authenticated = 1;

	prot_setsasl(sieved_in, sieved_saslconn);
	prot_setsasl(sieved_out, sieved_saslconn);

	/* Create telemetry log */
	sieved_logfd = telemetry_log(username, sieved_in, sieved_out, 0);

	cleanup:
	/* free memory */
	free(username);

	return ret;
}

#ifdef HAVE_SSL
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in)
{
    int result;
    int *layerp;

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

    if (starttls_done == 1)
    {
	prot_printf(sieved_out, "NO \"TLS already active\"\r\n");
	return TIMSIEVE_FAIL;
    }

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

    if (result == -1) {

	syslog(LOG_ERR, "error initializing TLS");

	prot_printf(sieved_out, "NO \"Error initializing TLS\"\r\n");

	return TIMSIEVE_FAIL;
    }

    prot_printf(sieved_out, "OK \"Begin TLS negotiation now\"\r\n");
    /* must flush our buffers before starting tls */
    prot_flush(sieved_out);

    result=tls_start_servertls(0, /* read */
			       1, /* write */
			       layerp,
			       &authid,
			       &tls_conn);

    /* if error */
    if (result==-1) {
	prot_printf(sieved_out, "NO \"Starttls failed\"\r\n");
	syslog(LOG_NOTICE, "STARTTLS failed: %s", sieved_clienthost);
	return TIMSIEVE_FAIL;
    }

    /* tell SASL about the negotiated layer */
    result = sasl_setprop(sieved_saslconn, SASL_SSF_EXTERNAL, &ssf);

    if (result != SASL_OK) {
        fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
    }
            
    result = sasl_setprop(sieved_saslconn, SASL_AUTH_EXTERNAL, authid);

    if (result != SASL_OK) {
	fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
    }

    /* tell the prot layer about our new layers */
    prot_settls(sieved_in, tls_conn);
    prot_settls(sieved_out, tls_conn);

    starttls_done = 1;

    return result;
}
#else
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in)
{
    fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
}
#endif /* HAVE_SSL */