/* ========================================================================
 * Copyright 2008 Mark Crispin
 * ========================================================================
 */

/*
 * Program:	Mail Delivery Module
 *
 * Author:	Mark Crispin
 *
 * Date:	5 April 1993
 * Last Edited:	27 November 2008
 *
 * Previous versions of this file were
 *
 * Copyright 1988-2007 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 */

#include <stdio.h>
#include <pwd.h>
#include <errno.h>
#include <ctype.h>
extern int errno;		/* just in case */
#include <sysexits.h>
#include <sys/file.h>
#include <sys/stat.h>
#include "c-client.h"
#include "tquota.h"


/* Globals */

char *version = "25";		/* tmail edit version */
int debug = NIL;		/* debugging (don't fork) */
int trycreate = NIL;		/* flag saying gotta create before appending */
int critical = NIL;		/* flag saying in critical code */
char *sender = NIL;		/* message origin */
char *inbox = NIL;		/* inbox file */
long precedence = 0;		/* delivery precedence - used by quota hook */
DRIVER *format = NIL;		/* desired format */


/* Function prototypes */

void file_string_init (STRING *s,void *data,unsigned long size);
char file_string_next (STRING *s);
void file_string_setpos (STRING *s,unsigned long i);
int main (int argc,char *argv[]);
int deliver (FILE *f,unsigned long msglen,char *user);
long ibxpath (MAILSTREAM *ds,char **mailbox,char *path);
int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
		    uid_t uid,char *tmp);
int delivery_unsafe (char *path,uid_t uid,struct stat *sbuf,char *tmp);
int fail (char *string,int code);
char *getusername (char *s,char **t);


/* File string driver for file stringstructs */

STRINGDRIVER file_string = {
  file_string_init,		/* initialize string structure */
  file_string_next,		/* get next byte in string structure */
  file_string_setpos		/* set position in string structure */
};


/* Cache buffer for file stringstructs */

#define CHUNKLEN 16384
char chunk[CHUNKLEN];

/* Initialize file string structure for file stringstruct
 * Accepts: string structure
 *	    pointer to string
 *	    size of string
 */

void file_string_init (STRING *s,void *data,unsigned long size)
{
  s->data = data;		/* note fd */
  s->size = size;		/* note size */
  s->chunk = chunk;
  s->chunksize = (unsigned long) CHUNKLEN;
  SETPOS (s,0);			/* set initial position */
}


/* Get next character from file stringstruct
 * Accepts: string structure
 * Returns: character, string structure chunk refreshed
 */

char file_string_next (STRING *s)
{
  char c = *s->curpos++;	/* get next byte */
  SETPOS (s,GETPOS (s));	/* move to next chunk */
  return c;			/* return the byte */
}


/* Set string pointer position for file stringstruct
 * Accepts: string structure
 *	    new position
 */

void file_string_setpos (STRING *s,unsigned long i)
{
  if (i > s->size) i = s->size;	/* don't permit setting beyond EOF */
  s->offset = i;		/* set new offset */
  s->curpos = s->chunk;		/* reset position */
				/* set size of data */
  if ((s->cursize = min (s->chunksize,SIZE (s))) != 0L) {
				/* move to that position in the file */
    fseek ((FILE *) s->data,s->offset,SEEK_SET);
    fread (s->curpos,sizeof (char),(unsigned int) s->cursize,(FILE *) s->data);
  }
}

/* Main program */

int main (int argc,char *argv[])
{
  FILE *f = NIL;
  int pid,c,ret = 0;
  unsigned long msglen,status = 0;
  char *s,tmp[MAILTMPLEN];
  uid_t ruid = getuid ();
  struct passwd *pwd;
  openlog ("tmail",LOG_PID,LOG_MAIL);
#include "linkage.c"
				/* make sure have some arguments */
  if (--argc < 1) _exit (fail ("usage: tmail [-D] user[+folder]",EX_USAGE));
				/* process all flags */
  while (argc && (*(s = *++argv)) == '-') {
    argc--;			/* gobble this argument */
    switch (s[1]) {		/* what is this flag? */
    case 'D':			/* debug */
      debug = T;		/* don't fork */
      break;
    case 'I':			/* inbox specifier */
      if (inbox || format) _exit (fail ("duplicate -b or -I",EX_USAGE));
      if (argc--) inbox = cpystr (*++argv);
      else _exit (fail ("missing argument to -I",EX_USAGE));
      break;
    case 'f':			/* new name for this flag */
    case 'r':			/* flag giving return path */
      if (sender) _exit (fail ("duplicate -f or -r",EX_USAGE));
      if (argc--) sender = cpystr (*++argv);
      else _exit (fail ("missing argument to -f or -r",EX_USAGE));
      break;
    case 'b':			/* create INBOX in this format */
      if (inbox || format) _exit (fail ("duplicate -b or -I",EX_USAGE));
      if (!argc--) _exit (fail ("missing argument to -b",EX_USAGE));
      if (!(format = mail_parameters (NIL,GET_DRIVER,*++argv)))
	_exit (fail ("unknown format to -b",EX_USAGE));
      else if (!(format->flags & DR_LOCAL) ||
	       !compare_cstring (format->name,"dummy"))
	_exit (fail ("invalid format to -b",EX_USAGE));
      break;
    /* following flags are undocumented */
    case 'p':			/* precedence for quota */
      if (s[2] && ((s[2] == '-') || isdigit (s[2]))) precedence = atol (s + 2);
      else if (argc-- && ((*(s = *++argv) == '-') || isdigit (*s)))
	precedence = atol (s);
      else _exit (fail ("missing argument to -p",EX_USAGE));
      break;
    case 'd':			/* obsolete flag meaning multiple users */
      break;			/* ignore silently */
    /* -s has been deprecated and replaced by the -s and -k flags in dmail.
     * dmail's -k flag does what -s once did in tmail; dmail's -s flag
     * takes no argument and just sets \Seen.  Flag setting is more properly
     * done in dmail which runs as the user and is clearly at the user's
     * behest.  Since tmail runs privileged, -s would have to be disabled
     * unless the caller is also privileged.
     */
    case 's':			/* obsolete flag meaning delivery flags */
      if (!argc--)		/* takes an argument */
	_exit (fail ("missing argument to deprecated flag",EX_USAGE));
      syslog (LOG_INFO,"tmail called with deprecated flag: -s %.200s",*++argv);
      break;
    default:			/* anything else */
      _exit (fail ("unknown switch",EX_USAGE));
    }
  }

  if (!argc) ret = fail ("no recipients",EX_USAGE);
  else if (!(f = tmpfile ())) ret = fail ("can't make temp file",EX_TEMPFAIL);
  else {			/* build delivery headers */
    if (sender) fprintf (f,"Return-Path: <%s>\015\012",sender);
				/* start Received line: */
    fprintf (f,"Received: via tmail-%s.%s",CCLIENTVERSION,version);
				/* not root or daemon? */
    if (ruid && !((pwd = getpwnam ("daemon")) && (ruid == pwd->pw_uid))) {
      pwd = getpwuid (ruid);	/* get unprivileged user's information */
      if (inbox || format) {
	if (pwd) sprintf (tmp,"user %.80s",pwd->pw_name);
	else sprintf (tmp,"UID %ld",(long) ruid);
	strcat (tmp," is not privileged to use -b or -I");
	_exit (fail (tmp,EX_USAGE));
      }
      fputs (" (invoked by ",f);
      if (pwd) fprintf (f,"user %s",pwd->pw_name);
      else fprintf (f,"UID %ld",(long) ruid);
      fputs (")",f);
    }
				/* write "for" if single recipient */
    if (argc == 1) fprintf (f," for %s",*argv);
    fputs ("; ",f);
    rfc822_date (tmp);
    fputs (tmp,f);
    fputs ("\015\012",f);
				/* copy text from standard input */
    if (!fgets (tmp,MAILTMPLEN-1,stdin) || !(s = strchr (tmp,'\n')) ||
	(s == tmp) || s[1]) _exit (fail ("bad first message line",EX_USAGE));
    if (s[-1] == '\015') {	/* nuke leading "From " line */
      if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
	  (tmp[3] != 'm') || (tmp[4] != ' ')) fputs (tmp,f);
      while ((c = getchar ()) != EOF) putc (c,f);
    }
    else {
      mm_log ("tmail called with LF-only newlines",WARN);
      if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
	  (tmp[3] != 'm') || (tmp[4] != ' ')) {
	*s++ = '\015';		/* overwrite NL with CRLF */
	*s++ = '\012';
	*s = '\0';		/* tie off string */
	fputs (tmp,f);		/* write line */
      }
				/* copy text from standard input */
      while ((c = getchar ()) != EOF) {
				/* add CR if needed */
	if (c == '\012') putc ('\015',f);
	putc (c,f);
      }
    }
    msglen = ftell (f);		/* size of message */
    fflush (f);			/* make sure all changes written out */

    if (ferror (f)) ret = fail ("error writing temp file",EX_TEMPFAIL);
    else if (!msglen) ret = fail ("empty message",EX_TEMPFAIL);
				/* single delivery */
    else if (argc == 1) ret = deliver (f,msglen,*argv);
    else do {			/* multiple delivery uses daughter forks */
      if ((pid = fork ()) < 0) ret = fail (strerror (errno),EX_OSERR);
      else if (pid) {		/* mother process */
	grim_pid_reap_status (pid,NIL,(void *) status);
				/* normal termination? */
	if (!ret) ret = (status & 0xff) ? EX_SOFTWARE : (status & 0xff00) >> 8;
      }
				/* daughter process */
      else _exit (deliver (f,msglen,*argv));
    } while (--argc && *argv++);
    mm_dlog (ret ? "error in delivery" : "all recipients delivered");
  }
  if (f) fclose (f);		/* all done with temporary file */
  _exit (ret);			/* normal exit */
  return 0;			/* stupid gcc */
}

/* Deliver message to recipient list
 * Accepts: file description of message temporary file
 *	    size of message temporary file in bytes
 *	    recipient name
 * Returns: NIL if success, else error code
 */

int deliver (FILE *f,unsigned long msglen,char *user)
{
  MAILSTREAM *ds = NIL;
  char *s,*t,*mailbox,tmp[MAILTMPLEN],path[MAILTMPLEN];
  struct passwd *pwd;
  STRING st;
  struct stat sbuf;
  uid_t duid;
  uid_t euid = geteuid ();
				/* get user record */
  if (!((s = getusername (user,&mailbox)) && (pwd = getpwnam (s)))) {
    sprintf (tmp,"no such user as %.80s",user);
    return fail (tmp,EX_NOUSER);
  }
				/* absurd is absurd */
  if (mailbox && (strlen (mailbox) > 256))
    return fail ("absurd folder name",EX_NOUSER);
				/* big security hole if this is allowed */
  if (!(duid = pwd->pw_uid)) return fail ("mail to root prohibited",EX_NOUSER);
				/* log in as user if different than euid */
  if ((duid != euid) && !loginpw (pwd,1,&user)) {
    sprintf (tmp,"unable to log in UID %ld from UID %ld",
	     (long) duid,(long) euid);
    return fail (tmp,EX_NOUSER);
  }
				/* can't use pwd after this point */
  env_init (pwd->pw_name,pwd->pw_dir);
  sprintf (tmp,"delivering to %.80s+%.80s",user,mailbox ? mailbox : "INBOX");
  mm_dlog (tmp);
				/* prepare stringstruct */
  INIT (&st,file_string,(void *) f,msglen);
  if (mailbox) {		/* non-INBOX name */
    switch (mailbox[0]) {	/* make sure a valid name */
    default:			/* other names, try to deliver if not INBOX */
      if (!strstr (mailbox,"..") && !strstr (mailbox,"//") &&
	  !strstr (mailbox,"/~") && mailboxfile (path,mailbox) && path[0] &&
	  !deliver_safely (NIL,&st,mailbox,path,duid,tmp)) return NIL;
    case '%': case '*':		/* wildcards not valid */
    case '#':			/* namespace name not valid */
    case '/':			/* absolute path names not valid */
    case '~':			/* user names not valid */
      sprintf (tmp,"invalid mailbox name %.80s+%.80s",user,mailbox);
      mm_log (tmp,WARN);
      break;
    }
    mm_dlog ("retrying delivery to INBOX");
    SETPOS (&st,0);		/* rewind stringstruct just in case */
  }

				/* -I specified and not "-I INBOX"? */
  if (inbox && !(((inbox[0] == 'I') || (inbox[0] == 'i')) &&
		 ((inbox[1] == 'N') || (inbox[1] == 'n')) &&
		 ((inbox[2] == 'B') || (inbox[2] == 'b')) &&
		 ((inbox[3] == 'O') || (inbox[3] == 'o')) &&
		 ((inbox[4] == 'X') || (inbox[4] == 'x')) && !inbox[5])) {
    DRIVER *dv = NIL;
				/* "-I #driver.xxx/name"? */
    if ((*inbox == '#') && ((inbox[1] == 'd') || (inbox[1] == 'D')) &&
	((inbox[2] == 'r') || (inbox[2] == 'R')) &&
	((inbox[3] == 'i') || (inbox[3] == 'I')) &&
	((inbox[4] == 'v') || (inbox[4] == 'V')) &&
	((inbox[5] == 'e') || (inbox[5] == 'E')) &&
	((inbox[6] == 'r') || (inbox[6] == 'R')) && (inbox[7] == '.') &&
	(s = strchr (inbox+8,'/'))) {
      *s = '\0';		/* temporarily tie off driver name */
      if (!((dv = mail_parameters (NIL,GET_DRIVER,(void *) (inbox+8))) &&
	    (mailboxfile (path,s[1] ? s + 1 : "&&&&&") == path) &&
	    (s[1] || ((t = strstr (path,"&&&&&")) && strcpy (t,"INBOX"))))) {
	path[0] = '\0';		/* bad -I argument, no path resolved */
	sprintf (tmp,"Unable to resolve driver in %.80s, -I ignored",inbox);
	mm_log (tmp,WARN);
      }
      *s = '/';			/* restore delimiter */
    }
				/* resolve "-I other" specification */
    else if (mailboxfile (path,inbox) && path[0]) {
				/* resolution succeeded, impute driver */
      if (!strcmp (inbox,"mail.txt"))
	dv = mail_parameters (NIL,GET_DRIVER,(void *) "tenex");
      else if (!strcmp (inbox,"INBOX.MTX"))
	dv = mail_parameters (NIL,GET_DRIVER,(void *) "mtx");
      else if (!strcmp (inbox,"mbox"))
	dv = mail_parameters (NIL,GET_DRIVER,(void *) "unix");
    }
    else {			/* bad -I argument */
      path[0] = '\0';		/* no path resolved */
      sprintf (tmp,"Unable to resolve %.80s, -I ignored",inbox);
      mm_log (tmp,WARN);
    }
    if (*path) {		/* -I successfully resolved a path? */
      MAILSTREAM dpr;
      dpr.dtb = dv;
      if (dv) ds = &dpr;
				/* supplicate to the Evil One if necessary */
      if (lstat (path,&sbuf) && !path_create (ds,path)) {
				/* the Evil One rejected the plea */
	sprintf (tmp,"Unable to create %.80s, -I ignored",path);
	mm_log (tmp,WARN);
      }
				/* now attempt delivery */
      else return deliver_safely (ds,&st,inbox,path,duid,tmp);
    }
  }

				/* no -I, resolve "INBOX" into path */
  if (mailboxfile (path,mailbox = "INBOX") && !path[0]) {
				/* clear box, get generic INBOX prototype */
    if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
      fatal ("no INBOX prototype");
				/* standard system driver? */
    if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf")) {
      strcpy (path,sysinbox ());/* use system INBOX */
      if (!lstat (path,&sbuf))	/* deliver to existing system INBOX */
	return deliver_safely (ds,&st,mailbox,path,duid,tmp);
    }
    else {			/* other driver, try ~/INBOX */
      if ((mailboxfile (path,"&&&&&") == path) &&
	  (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX") &&
	  !lstat (path,&sbuf)){	/* deliver to existing ~/INBOX */
	sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
	return deliver_safely (ds,&st,cpystr (tmp),path,duid,tmp);
      }
    }
				/* not dummy, deliver to driver imputed path */
    if (strcmp (ds->dtb->name,"dummy"))
      return (ibxpath (ds,&mailbox,path) && !lstat (path,&sbuf)) ?
	deliver_safely (ds,&st,mailbox,path,duid,tmp) :
	  fail ("unable to resolve INBOX path",EX_CANTCREAT);
				/* dummy, empty imputed append path exist? */
    if (ibxpath (ds = default_proto (T),&mailbox,path) &&
	!lstat (path,&sbuf) && !sbuf.st_size)
      return deliver_safely (ds,&st,mailbox,path,duid,tmp);
				/* impute path that we will create */
    if (!ibxpath (ds = format ? (format->open) (NIL) : default_proto (NIL),
		  &mailbox,path))
      return fail ("unable to resolve INBOX",EX_CANTCREAT);
  }
				/* black box, must create, get create proto */
  else if (lstat (path,&sbuf)) ds = default_proto (NIL);
  else {			/* black box, existing file */
				/* empty file, get append prototype */
    if (!sbuf.st_size) ds = default_proto (T);
				/* non-empty, get prototype from its data */
    else if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
      fatal ("no INBOX prototype");
				/* error if unknown format */
    if (!strcmp (ds->dtb->name,"phile"))
      return fail ("unknown format INBOX",EX_UNAVAILABLE);
				/* otherwise can deliver to it */
    return deliver_safely (ds,&st,mailbox,path,duid,tmp);
  }
  sprintf (tmp,"attempting to create mailbox %.80s path %.80s",mailbox,path);
  mm_dlog (tmp);
				/* supplicate to the Evil One */
  if (!path_create (ds,path)) return fail ("can't create INBOX",EX_CANTCREAT);
  sprintf (tmp,"created %.80s",path);
  mm_dlog (tmp);
				/* deliver the message */
  return deliver_safely (ds,&st,mailbox,path,duid,tmp);
}

/* Resolve INBOX from driver prototype into mailbox name and filesystem path
 * Accepts: driver prototype
 * 	    pointer to mailbox name string pointer
 *	    buffer to return mailbox path
 * Returns: T if success, NIL if error
 */

long ibxpath (MAILSTREAM *ds,char **mailbox,char *path)
{
  char *s,tmp[MAILTMPLEN];
  long ret = T;
  if (!ds) ret = NIL;
  else if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf"))
    strcpy (path,sysinbox ());	/* use system INBOX for unix and MMDF */
  else if (!strcmp (ds->dtb->name,"tenex"))
    ret = (mailboxfile (path,"mail.txt") == path) ? T : NIL;
  else if (!strcmp (ds->dtb->name,"mtx"))
    ret = (mailboxfile (path,"INBOX.MTX") == path) ? T : NIL;
  else if (!strcmp (ds->dtb->name,"mbox"))
    ret = (mailboxfile (path,"mbox") == path) ? T : NIL;
				/* better not be a namespace driver */
  else if (ds->dtb->flags & DR_NAMESPACE) return NIL;
				/* INBOX in home directory */
  else ret = ((mailboxfile (path,"&&&&&") == path) &&
	      (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX")) ? T : NIL;
  if (ret) {			/* don't bother if lossage */
    sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
    *mailbox = cpystr (tmp);	/* name of INBOX in this namespace */
  }
  return ret;
}

/* Deliver safely
 * Accepts: prototype stream to force mailbox format
 *	    stringstruct of message temporary file
 *	    mailbox name
 *	    filesystem path name
 *	    user id
 *	    scratch buffer for messages
 * Returns: NIL if success, else error code
 */

int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
		    uid_t uid,char *tmp)
{
  struct stat sbuf;
  int i = delivery_unsafe (path,uid,&sbuf,tmp);
  if (i) return i;		/* give up now if delivery unsafe */
				/* directory, not file */
  if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
    if (sbuf.st_mode & 0001) {	/* listable directories may be worrisome */
      sprintf (tmp,"WARNING: directory %.80s is listable",path);
      mm_log (tmp,WARN);
    }
  }
  else {			/* file, not directory */
    if (sbuf.st_nlink != 1) {	/* multiple links may be worrisome */
      sprintf (tmp,"WARNING: multiple links to file %.80s",path);
      mm_log (tmp,WARN);
    }
    if (sbuf.st_mode & 0111) {	/* executable files may be worrisome */
      sprintf (tmp,"WARNING: file %.80s is executable",path);
      mm_log (tmp,WARN);
    }
  }
  if (sbuf.st_mode & 0002) {	/* public-write files may be worrisome */
    sprintf (tmp,"WARNING: file %.80s is publicly-writable",path);
    mm_log (tmp,WARN);
  }
  if (sbuf.st_mode & 0004) {	/* public-write files may be worrisome */
    sprintf (tmp,"WARNING: file %.80s is publicly-readable",path);
    mm_log (tmp,WARN);
  }
				/* check site-written quota procedure */
  if (!tmail_quota (st,path,uid,tmp,sender,precedence)) return fail (tmp,-1);
				/* so far, so good */
  sprintf (tmp,"%s appending to %.80s (%s %.80s)",
	   prt ? prt->dtb->name : "default",mailbox,
	   ((sbuf.st_mode & S_IFMT) == S_IFDIR) ? "directory" : "file",path);
  mm_dlog (tmp);
				/* do the append now! */
  if (!mail_append (prt,mailbox,st)) {
    sprintf (tmp,"message delivery failed to %.80s",path);
    return fail (tmp,EX_CANTCREAT);
  }
				/* note success */
  sprintf (tmp,"delivered to %.80s",path);
  mm_log (tmp,NIL);
				/* make sure nothing evil this way comes */
  return delivery_unsafe (path,uid,&sbuf,tmp);
}

/* Verify that delivery is safe
 * Accepts: path name
 *	    user id
 *	    stat buffer
 *	    scratch buffer for messages
 * Returns: NIL if delivery is safe, error code if unsafe
 */

int delivery_unsafe (char *path,uid_t uid,struct stat *sbuf,char *tmp)
{
  u_short type;
  sprintf (tmp,"Verifying safe delivery to %.80s by UID %ld",path,(long) uid);
  mm_dlog (tmp);
				/* prepare message just in case */
  sprintf (tmp,"delivery to %.80s unsafe: ",path);
				/* unsafe if can't get its status */
  if (lstat (path,sbuf)) strcat (tmp,strerror (errno));
  else if (sbuf->st_uid != uid)	/* unsafe if UID does not match */
    sprintf (tmp + strlen (tmp),"uid mismatch (%ld != %ld)",
	     (long) sbuf->st_uid,(long) uid);
				/* check file type */
  else switch (sbuf->st_mode & S_IFMT) {
  case S_IFDIR:			/* directory is always OK */
    return NIL;
  case S_IFREG:			/* file is unsafe if setuid */
    if (sbuf->st_mode & S_ISUID) strcat (tmp,"setuid file");
				/* or setgid */
    else if (sbuf->st_mode & S_ISGID) strcat (tmp,"setgid file");
    else return NIL;		/* otherwise safe */
    break;
  case S_IFCHR: strcat (tmp,"character special"); break;
  case S_IFBLK: strcat (tmp,"block special"); break;
  case S_IFLNK: strcat (tmp,"symbolic link"); break;
  case S_IFSOCK: strcat (tmp,"socket"); break;
  default:
    sprintf (tmp + strlen (tmp),"file type %07o",(unsigned int) type);
  }
  return fail (tmp,EX_CANTCREAT);
}

/* Report an error
 * Accepts: string to output
 */

int fail (char *string,int code)
{
  mm_log (string,ERROR);	/* pass up the string */
  switch (code) {
#if T
  case EX_USAGE:
  case EX_OSERR:
  case EX_SOFTWARE:
  case EX_NOUSER:
  case EX_CANTCREAT:
  case EX_UNAVAILABLE:
    code = EX_TEMPFAIL;		/* coerce these to TEMPFAIL */
#endif
    break;
  case -1:			/* quota failure... */
    code = EX_CANTCREAT;	/* ...really returns this code */
    break;
  default:
    break;
  }
  return code;			/* error code to return */
}


/* Get user name from username+mailbox specifier
 * Accepts: username/mailbox specifier
 *	    pointer to return location for mailbox specifier
 * Returns: user name, mailbox specifier value NIL if INBOX, patches out +,
 *	    or NIL if error
 */

char *getusername (char *s,char **t)
{
  if ((*t = strchr (s,'+')) != NULL) {	/* have a mailbox specifier? */
    *(*t)++ = '\0';		/* yes, tie off user name */
				/* forbid overlong name */
    if (strlen (*t) > NETMAXMBX) return NIL;
				/* user+ and user+INBOX same as user */
    if (!**t || !compare_cstring ((unsigned char *) *t,"INBOX")) *t = NIL;
  }
  return s;			/* return user name */
}

/* Co-routines from MAIL library */


/* Message matches a search
 * Accepts: MAIL stream
 *	    message number
 */

void mm_searched (MAILSTREAM *stream,unsigned long msgno)
{
  fatal ("mm_searched() call");
}


/* Message exists (i.e. there are that many messages in the mailbox)
 * Accepts: MAIL stream
 *	    message number
 */

void mm_exists (MAILSTREAM *stream,unsigned long number)
{
  fatal ("mm_exists() call");
}


/* Message expunged
 * Accepts: MAIL stream
 *	    message number
 */

void mm_expunged (MAILSTREAM *stream,unsigned long number)
{
  fatal ("mm_expunged() call");
}


/* Message flags update seen
 * Accepts: MAIL stream
 *	    message number
 */

void mm_flags (MAILSTREAM *stream,unsigned long number)
{
}

/* Mailbox found
 * Accepts: MAIL stream
 *	    delimiter
 *	    mailbox name
 *	    mailbox attributes
 */

void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
{
  fatal ("mm_list() call");
}


/* Subscribed mailbox found
 * Accepts: MAIL stream
 *	    delimiter
 *	    mailbox name
 *	    mailbox attributes
 */

void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
{
  fatal ("mm_lsub() call");
}


/* Mailbox status
 * Accepts: MAIL stream
 *	    mailbox name
 *	    mailbox status
 */

void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
{
  fatal ("mm_status() call");
}

/* Notification event
 * Accepts: MAIL stream
 *	    string to log
 *	    error flag
 */

void mm_notify (MAILSTREAM *stream,char *string,long errflg)
{
  char tmp[MAILTMPLEN];
  tmp[11] = '\0';		/* see if TRYCREATE */
  if (!strcmp (ucase (strncpy (tmp,string,11)),"[TRYCREATE]")) trycreate = T;
  mm_log (string,errflg);	/* just do mm_log action */
}


/* Log an event for the user to see
 * Accepts: string to log
 *	    error flag
 */

void mm_log (char *string,long errflg)
{
  if (trycreate)mm_dlog(string);/* debug logging only if trycreate in effect */
  else {			/* ordinary logging */
    fprintf (stderr,"%s\n",string);
    switch (errflg) {  
    case NIL:			/* no error */
      syslog (LOG_INFO,"%s",string);
      break;
    case PARSE:			/* parsing problem */
    case WARN:			/* warning */
      syslog (LOG_WARNING,"%s",string);
      break;
    case ERROR:			/* error */
    default:
      syslog (LOG_ERR,"%s",string);
      break;
    }
  }
}


/* Log an event to debugging telemetry
 * Accepts: string to log
 */

void mm_dlog (char *string)
{
  if (debug) fprintf (stderr,"%s\n",string);
  syslog (LOG_DEBUG,"%s",string);
}

/* Get user name and password for this host
 * Accepts: parse of network mailbox name
 *	    where to return user name
 *	    where to return password
 *	    trial count
 */

void mm_login (NETMBX *mb,char *username,char **password,long trial)
{
  fatal ("mm_login() call");
}

void mm_login_method(NETMBX *mb, char *user, void *login, long trial, char *method)
{
  fatal ("mm_login_method() call");
}

/* About to enter critical code
 * Accepts: stream
 */

void mm_critical (MAILSTREAM *stream)
{
  critical = T;			/* note in critical code */
}


/* About to exit critical code
 * Accepts: stream
 */

void mm_nocritical (MAILSTREAM *stream)
{
  critical = NIL;		/* note not in critical code */
}


/* Disk error found
 * Accepts: stream
 *	    system error code
 *	    flag indicating that mailbox may be clobbered
 * Returns: T if user wants to abort
 */

long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
{
  return T;
}


/* Log a fatal error event
 * Accepts: string to log
 */

void mm_fatal (char *string)
{
  printf ("?%s\n",string);	/* shouldn't happen normally */
}
