#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "bmaild.c,v 1.1.1.1 1995/06/16 21:10:41 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * bmaild
 *
 * Accept mail from network (stdin) via SMTP and queue to file on disk
 */

#include "sos.h"
#include "bsClient.h"

#include <dict.h>
#include <dll.h>

#ifndef	VERSION
#define	VERSION	"1.1.1.1"
#endif

#ifndef BS_QUEUEIN
#define BS_QUEUEIN "/queue/in"
#endif
#ifndef BS_QUEUEOUT
#define BS_QUEUEOUT "/queue/out"
#endif
#ifndef BS_QUEUEBAD
#define BS_QUEUEBAD "/queue/bad"
#endif
#ifndef BS_MODE
#define BS_MODE 0644
#endif


#define WHITESPACE " \t\n"
#define COLONSPACE ": \t\n"
#define SMALLBUF 64
#define MAXSTOREOPT 128
#define MAXARG 16
#define MAX_RCPT_LINES (_POSIX_ARG_MAX - 30)
#define DEFAULT_MAX_SIZE "2097152"	/* Max size of mail message */
#define DEFAULT_FREE_BLOCKS "1024"	/* Number of disk blocks to hold in reserve */
#define DEFAULT_BLOCK_SIZE "1024" /* Number of bytes per block */
#define MAIL_NOSAVE 0
#define MAIL_SAVE 1

char *program;

#define BS_HELO_STATE 1
#define BS_MAIL_STATE 2
#define BS_RCPT_STATE 4

char *myhostname;
int sfdin = 0;
int sfdout = 1;
FILE *sfilein;
FILE *sfileout;
int debug = 0;
int state = 0;
sos_config_t config;
int default_max_size = 0;
int default_free_blocks = 0;
int default_block_size = 0;
int current_max_size = 0;

struct iostat
{
  int starttime;
  int endtime;
  int bytes_from_source;
} ioinfo;

struct mailinfo_t
{
  char *from;
  dict_obj rcpt;
  int numrcpt;
  char *pathname;
  char *presentsas;
} mailinfo;

struct sos_conninfo sourceinfo = { NULL, -1 };
static struct sos_readline_t readopts;

void exiterror(int retcode, int wanterrno, char *msg, ...);
void exitcleanup();
char *get_smtp_arg(char *line, char **rest, char *sep);
void clean_mailinfo(int *state, int save);
int command();
int sub_helo(char *line, int *state);
int sub_ehlo(char *line, int *state);
int sub_mail(char *line, int *state);
int sub_rcpt(char *line, int *state);
int sub_data(char *line, int *state);
int sub_rset(char *line, int *state);
int sub_vrfy(char *line, int *state);
int sub_expn(char *line, int *state);
int sub_help(char *line, int *state);
int sub_noop(char *line, int *state);
int sub_quit(char *line, int *state);
int renamemail(char *name);
int relaymail(FILE *in, FILE *out);
char *gen_pathname(FILE **fp);
int greet();

struct smtpcmd
{
  char *name;
  int requires;
  int (*sub)(char *line, int *state);
} smtpcmd[] = {
  { "helo",	0, sub_helo },
  { "ehlo",	0, sub_ehlo },
  { "mail",	1, sub_mail },
  { "rcpt",	3, sub_rcpt },
  { "data",	7, sub_data },
  { "rset",	0, sub_rset },
  { "vrfy",	0, sub_vrfy },
  { "expn",	0, sub_expn },
  { "help",	0, sub_help },
  { "noop",	0, sub_noop },
  { "quit",	0, sub_quit },
  { NULL,	0, NULL },
};


/* Timeouts */
int timedout = 0;
void sigtimeout()
{
  timedout = 1;
}
static struct sigaction old_sigaction; /* Caches old signal vec. */
static struct sigaction iotimeout = SIGACTIONDEFINE( (void *)sigtimeout, 0, _INTERRUPT_SYSCALLS_ );



main(int argc,char *argv[],char *envp[])
{
  int from_stdin = 0;
  int tmpvar;
  int getopterr = 0;
  extern char *optarg;
  extern int optind;
  char *tmp;

  sos_initProcTitle(argc, &argv, &envp, &program);
  sos_setProcTitle("INITIALIZING: SRC=%s/%d: QID=%s","Not.connected [0.0.0.0]",-1,"undetermined");


  while ((tmpvar = getopt(argc,argv, "sd")) != -1)
    switch(tmpvar)
      {
      case 's':
	from_stdin=1;
	break;
      case 'd':
	debug=1;
	break;
      case '?':
	getopterr++;
      }

  if (getopterr)
    {
      fprintf(stderr,"Usage: %s [-s]\n", program);
      exit(2);
    }
  
  open_soslog(program,debug);

  /* set up readline information */
  readopts.echo = -1;
  readopts.wantalert = 0;
  readopts.wantedit = 0;
  readopts.wantnull = 0;
  readopts.escape_char = -1;
  readopts.escape_mode = NULL;
  readopts.eolseq[0].str = "\r\n";
  readopts.eolseq[0].len = 2;
  readopts.numeolseq = 1;
#if defined(DEBUG)
  if (from_stdin)
    {
      readopts.eolseq[1].str = "\n";
      readopts.eolseq[1].len = 1;
      readopts.numeolseq = 2;
    }
#endif

  mailinfo.from = NULL;
  mailinfo.rcpt = NULL;
  mailinfo.numrcpt = 0;
  mailinfo.pathname = NULL;
  mailinfo.presentsas = NULL;

  /* This is non-fatal */
  if ((config = sos_config_read(SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF))) == NULL)
    {
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT,1, "Could not read config file %s",SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF));
    }

  if (chdir(SOS_GWD("MAIL_QUEUEIN",BS_QUEUEIN)) < 0)
    exiterror(451, 1, "chdir(3) failed.  Very strange.");

  /* Get some defaults for size limitations */
  default_max_size = atoi(SOS_GWD("MAIL_MAX_SIZE",DEFAULT_MAX_SIZE));
  default_free_blocks = atoi(SOS_GWD("MAIL_FREE_BLOCKS",DEFAULT_FREE_BLOCKS));
  default_block_size = atoi(SOS_GWD("MAIL_BLOCK_SIZE",DEFAULT_BLOCK_SIZE));

  /* Find out who we are connected to */
  if (sos_getpeername(sfdin, &sourceinfo) < 0)
    {
      if (from_stdin)
	  sourceinfo.printhost = strdup(".stdin. [0.0.0.0]");
      else
	exiterror(554, 1, "getpeername(2) failed.  Who are you and how did you connect?");
    }
  sos_setProcTitle("INITIALIZING: SRC=%s/%d: QID=%s",sourceinfo.printhost,sourceinfo.port,"undetermined");

  soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT, 0, "connection from %s", sourceinfo.printhost);

  ioinfo.starttime = time(NULL);
  ioinfo.endtime = -1;
  ioinfo.bytes_from_source = 0;

  if ((sfilein = fdopen(sfdin,"r")) == NULL)
    exiterror(451, 1, "fdopen(3) failed.  Very strange.");
  if ((sfileout = fdopen(sfdout,"w")) == NULL)
    exiterror(451, 1, "fdopen(3) failed.  Very strange.");

  current_max_size = gen_maxmailsize();
  if (debug)
    fprintf(stderr,"Current max size is %d bytes\n",current_max_size);

  /* Connection is all set up */
  if (greet() < 0)
    {
      exiterror(451, 1, "greet failed.  Send mail to postmaster telling him about this :-)");
    }

  sos_setProcTitle("COMMAND: SRC=%s/%d: QID=%s",sourceinfo.printhost,sourceinfo.port,mailinfo.pathname?mailinfo.pathname:"undetermined");
  if (command() == -1)
    {
      exiterror(451, 1, "error while in command mode: %s",sourceinfo.printhost);
    }
  return(0);
}



/*
 * Exit and log information
 */
void exiterror(int retcode, int want_perror, char *fmt, ...)
{
  va_list args;

  alarm(0);
  va_start(args, fmt);
  printf("%d ",retcode);
  vprintf(fmt,args);
  printf("\r\n");
  vsoslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, want_perror, fmt, args);
  va_end(args);

  exitcleanup();
}



/*
 * Log exit messages
 */
void exitcleanup()
{
  int junk;

  alarm (0);

  clean_mailinfo(&junk, MAIL_SAVE);

  ioinfo.endtime = time(NULL);
  soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, 0,
	 "Exit: %d bytes from :%s:, %d seconds",
	 ioinfo.bytes_from_source,sourceinfo.printhost,
	 ioinfo.endtime-ioinfo.starttime);

  exit(0);
}  



/*
 * Scrub all information about a finished or aborted mail message
 */
void clean_mailinfo(int *state, int save)
{
  if (mailinfo.from)
    free(mailinfo.from);

  if (mailinfo.pathname)
    {
      /* let's save the message */
      char tmpname[MAXPATHLEN];

      if (save)
	{
	  sprintf(tmpname,"%s/%s",SOS_GWD("MAIL_QUEUEBAD",BS_QUEUEBAD),mailinfo.pathname);

	  if (rename(mailinfo.pathname,tmpname) < 0)
	    soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 1, "Failed to rename(%s,%s)",mailinfo.pathname,tmpname);
	  else
	    soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 0, "MAIL DROPPED: %s", mailinfo.pathname);
	}
      else
	{
	  unlink(mailinfo.pathname);
	}

      free(mailinfo.pathname);
    }

  if (mailinfo.rcpt)
    {
      dict_obj llcur;

      for(llcur = dll_maximum(mailinfo.rcpt);llcur != NULL;llcur = dll_maximum(mailinfo.rcpt))
	{
	  free(llcur);
	  dll_delete(mailinfo.rcpt,llcur);
	}
      dll_destroy(mailinfo.rcpt);
    }

  mailinfo.from = NULL;
  mailinfo.rcpt = NULL;
  mailinfo.numrcpt = 0;
  mailinfo.pathname = NULL;

  *state &= ~(BS_MAIL_STATE|BS_MAIL_STATE);
}



/*
 * Greet the poor SOB trying to send mail
 */
int greet()
{
  char *line;

  myhostname = strdup(sos_fqhname(NULL,0));
  if (fprintf(sfileout,"220-%s proxy mail (%s) ready at %s\r\n",myhostname,VERSION,sos_prompttime(NULL,0)) < 1)
    return(-1);

  while ((line = sos_config_getnext(config, "SMTP_GREET", SOS_CONFIG_FORWARD, SOS_CONFIG_LINEAR)) != NULL)
    {
      if (fprintf(sfileout,"220-%s\r\n",line) < 1)
	return(-1);
    }

  if (fprintf(sfileout,"220 ESMTP spoken here\r\n") < 1)
    return(-1);

  fflush(sfileout);

  return(0);
}



/*
 * Mail command loop.  Read commands and dispatch them
 */
int command()
{
  int ret = -1;
  int cmdtimeout;
  sos_string INBUF;
  struct smtpcmd *curcmd;
  char *peekcmd;
  char *restline;

  if (!sos_allocstr(&INBUF,BS_IOLEN))
    return(-1);

  cmdtimeout = atoi(SOS_GWD("SMTP_CMDTIMEOUT","300"));
  timedout = 0;
  if (cmdtimeout > 0)
    {
      sigaction (SIGALRM, (struct sigaction *)&iotimeout,
		 (struct sigaction *)&old_sigaction);
    }

  while (1)
    {
      if (cmdtimeout)
	alarm(cmdtimeout);

      sos_setProcTitle("COMMAND: SRC=%s/%d: QID=%s",sourceinfo.printhost,sourceinfo.port,mailinfo.pathname?mailinfo.pathname:"undetermined");
      ret = sos_readline(sfilein, sfileout, &INBUF, &readopts);
      if (timedout) exiterror(451,0,"timeout waiting for input during command mode: %s",sourceinfo.printhost);
      if (ret == 0) exiterror(451,1,"eof from remote host: %s",sourceinfo.printhost);
      if (ret == -1) exiterror(451,1,"system error during readline: %s",sourceinfo.printhost);
      if (ret < 0)
	{
	  if (fprintf(sfileout,"\r\n%s\r\n",SOS_GWD("SMTP_INVALID","Invalid entry")) < 1)
	    return(-1);
	  return(-1);
	}
      sos_rip(INBUF.str);
      if (strlen(INBUF.str) == 0) { ret = 0; goto unkcmd; }
      alarm(0);

      if (!(peekcmd = get_smtp_arg(INBUF.str,&restline, WHITESPACE)))
	{
	  goto unkcmd;
	}

      for(curcmd=smtpcmd;curcmd->name;curcmd++)
	{
	  if (!strcasecmp(peekcmd,curcmd->name))
	    break;
	}

      if (!curcmd->name)
	{
	unkcmd:
	  if (fprintf(sfileout,"500 Command unrecognized\r\n") < 1)
	    return -1;
	  fflush(sfileout);
	  continue;
	}

      if ((state & curcmd->requires) == curcmd->requires)
	{
	  if ( (ret = (*curcmd->sub)(restline,&state)) < 0)
	    return(ret);
	}
      else
	{
	  if (fprintf(sfileout,"503 Bad sequence of commands\r\n") < 1)
	    return(-1);
	}
      fflush(sfileout);
    }

  /* SHOULD never get here */
  return(-1);
}



/*
 * Break an input line into an array of tokens
 */
int smtp_enargv(char *line, int *argc, char *argv[], char *sep)
{
  int c;
  char *ret = line;

  *argc = -1;
  if (!line || !*line)
    return(0);

  for(c=0;c<MAXARG && ret;c++)
    {
      ret = strtok(line,sep);
      argv[c] = ret;
      line = NULL;
    }

  if (c==MAXARG)
    return(-1);

  *argc = c-2;
  return(c-1);
}



/*
 * Get the first element of a line
 */
char *get_smtp_arg(char *line, char **rest, char *sep)
{
  char *ret;

  ret = strtok(line,sep);
  *rest = strtok(NULL, "");

  if (*rest)
    *rest += strspn(*rest,sep);

  return(ret);
}



/*********************************************************************
 *                               help                                *
 *********************************************************************/
int sub_help(char *rest, int *state)
{
  char *line;

  while ((line = sos_config_getnext(config, "SMTP_HELP", SOS_CONFIG_FORWARD, SOS_CONFIG_AUTORESET)) != NULL)
    {
      if (fprintf(sfileout,"214-%s\r\n",line) < 1)
	return(-1);
    }
  if (fprintf(sfileout,"214 This is as much help as you are going to get\r\n") < 1)
    return(-1);

  return(0);
}



/*********************************************************************
 *                               quit                                *
 *********************************************************************/
int sub_quit(char *rest, int *state)
{
  fprintf(sfileout,"221 %s Service closing transmission channel\r\n",myhostname);
  fflush(sfileout);
  exitcleanup();
  abort();
}



/*********************************************************************
 *                               helo                                *
 *********************************************************************/
int sub_helo(char *rest, int *state)
{
  char *argv[MAXARG];
  int argc;

  clean_mailinfo(state, MAIL_SAVE);
  *state = 0;
  if (smtp_enargv(rest,&argc,argv,WHITESPACE) < 0 || argc != 0)
    {
      if (fprintf(sfileout,"501 Syntax error in parameter scanning (%d)\r\n",argc) < 1)
	return(-1);
      return(0);
    }

  /* XXX - should I compare who the user says he is and who he actually is? */

  if (mailinfo.presentsas)
    free(mailinfo.presentsas);

  if (!(mailinfo.presentsas = strdup(argv[0])))
    return(-1);

  if (fprintf(sfileout,"250 %s Hello %s, pleased to meet you\r\n",myhostname,sourceinfo.printhost) < 1)
    return(-1);
  *state = BS_HELO_STATE;

  return(0);
}



/*********************************************************************
 *                               ehlo                                *
 *********************************************************************/
int sub_ehlo(char *rest, int *state)
{
  char *argv[MAXARG];
  int argc;

  clean_mailinfo(state, MAIL_SAVE);
  *state = 0;
  if (smtp_enargv(rest,&argc,argv, WHITESPACE) < 0 || argc != 0)
    {
      if (fprintf(sfileout,"501 Syntax error in parameter scanning\r\n") < 1)
	return(-1);
      return(0);
    }

  /* XXX - should I compare who the user says he is and who he actually is? */

  if (mailinfo.presentsas)
    free(mailinfo.presentsas);

  if (!(mailinfo.presentsas = strdup(argv[0])))
    return(-1);

  if (fprintf(sfileout,"250-%s Hello %s, pleased to meet you\r\n250-SIZE %d\r\n250 HELP\r\n",myhostname,sourceinfo.printhost,default_max_size) < 1)
    return(-1);

  *state = BS_HELO_STATE;
  return(0);
}



/*********************************************************************
 *                               mail                                *
 *********************************************************************/
int sub_mail(char *rest, int *state)
{
  char *from;
  char *tmp;

  /* Get new size (may have changed, especially after MAIL commands) */
  current_max_size = gen_maxmailsize();
  if (current_max_size < 1)
    {
      if (fprintf(sfileout,"452 I don't have any disk space left!!!\r\n") < 1)
	return(-1);
      return(-2);
    }

  clean_mailinfo(state, MAIL_SAVE);

  if (*state & BS_MAIL_STATE)
    {
      *state &= ~(BS_MAIL_STATE|BS_MAIL_STATE);
      if (fprintf(sfileout,"503 Sender already specified\r\n") < 1)
	return(-1);
      return(0);
    }

  *state &= ~(BS_MAIL_STATE|BS_MAIL_STATE);
  if (!(tmp = get_smtp_arg(rest,&rest,COLONSPACE)) || !rest)
    {
      goto syntax;
    }

  if (strcasecmp(tmp,"from"))
    {
      goto syntax;
    }

  if (*rest == '<')
    {				/* This had better be "<" [ <a-d-l> ":" ] <mailbox> ">" */
      char *last = strchr(rest,'>');

      if (!last || strchr(rest+1,'<') || strchr(last+1, '>'))
	{
	  goto syntax;
	}
      from = rest;

      /* If the character after the closing '>' is a null */
      if (!(*(last+1)))
	{
	  rest = NULL;
	}
      else
	{
	  *(last+1) = '\0';
	  rest = last+2 + strspn(last+2, WHITESPACE); /* Fix up rest for ESMTP size */
	}
    }
  else
    {				/* This is illegial, but common.  Should not have spaces */
      if (!(tmp = get_smtp_arg(rest,&rest,WHITESPACE)))
	{
	  goto syntax;
	}

      from = tmp;
    }

  if (rest && *rest)
    {				/* ESMTP size */
      if ((tmp = get_smtp_arg(rest, &rest, "= ")) && rest && !strcasecmp(tmp,"size"))
	{
	  int clientsize = atoi(rest);

	  if (!isdigit(*rest) || clientsize < 0)
	    goto syntax;

	  if (clientsize > default_max_size)
	    {
	      if (fprintf(sfileout,"552 Message size exceeds administrative limits\r\n") < 1)
		return(-1);
	      return(0);
	    }

	  if (clientsize > current_max_size)
	    {
	      if (fprintf(sfileout,"452 Message size exceeds current physical limits\r\n") < 1)
		return(-1);
	      return(0);
	    }
	}
      else
	{
	  goto syntax;		/* Pity--we were so close */
	}
    }

  if (fprintf(sfileout,"250 %s... Sender ok\r\n",from) < 1)
    return(-1);
  *state |= BS_MAIL_STATE;

  mailinfo.from = strdup(from);

  return(0);

 syntax:
  if (fprintf(sfileout,"501 Syntax error in parameter scanning\r\n") < 1)
    return(-1);
  return(0);
}



/*********************************************************************
 *                               rcpt                                *
 *********************************************************************/
int sub_rcpt(char *rest, int *state)
{
  char *rcpt;
  char *tmp;

  if (!mailinfo.rcpt)
    {
      if ((mailinfo.rcpt = dll_create(NULL,NULL,DICT_UNORDERED,NULL)) == NULL)
	return(-1);
    }

  if (!(tmp = get_smtp_arg(rest,&rest,COLONSPACE)) || !rest)
    {
      goto syntax;
    }

  if (strcasecmp(tmp,"to"))
    {
      goto syntax;
    }

  if (*rest == '<')
    {				/* This had better be "<" [ <a-d-l> ":" ] <mailbox> ">" */
      char *last = strchr(rest,'>');

      if (strchr(rest+1,'<') || !last ||
	  *(last+1) != '\0')
	{
	  goto syntax;
	}
      rcpt = rest;
    }
  else
    {				/* This is illegial, but common.  Should not have spaces */
      if (!(tmp = get_smtp_arg(rest,&rest,WHITESPACE)) || rest)
	{
	  goto syntax;
	}

      rcpt = tmp;
    }

  if (++mailinfo.numrcpt > MAX_RCPT_LINES)
    {
      if (fprintf(sfileout,"552 Too many recipients\r\n") < 1)
	return(-1);
      return(0);
    }

  if (dll_insert(mailinfo.rcpt,(dict_h)strdup(rcpt)) != DICT_OK)
    return(-1);

  if (fprintf(sfileout,"250 %s... Recipient ok\r\n",rcpt) < 1)
    return(-1);
  *state |= BS_RCPT_STATE;

  return(0);

 syntax:
  if (fprintf(sfileout,"501 Syntax error in parameter scanning\r\n") < 1)
    return(-1);
  return(0);
}



/*********************************************************************
 *                               data                                *
 *********************************************************************/
int sub_data(char *rest, int *state)
{
  FILE *fp;
  dict_obj llcur;
  int tmp;

  sos_setProcTitle("DATA: SRC=%s/%d: QID=%s",sourceinfo.printhost,sourceinfo.port,mailinfo.pathname?mailinfo.pathname:"undetermined");

  if (!mailinfo.rcpt)
    return(-1);

  if (!(mailinfo.pathname = strdup(gen_pathname(&fp))))
    return(-1);

  if (fprintf(fp,"HELO: %s\r\n",mailinfo.presentsas) < 1)
    return(-1);
  if (fprintf(fp,"HOST: %s\r\n",sourceinfo.printhost) < 1)
    return(-1);
  if (fprintf(fp,"VERSION: %s\r\n",VERSION) < 1)
    return(-1);
  if (fprintf(fp,"FROM: %s\r\n",mailinfo.from) < 1)
    return(-1);
  if (fprintf(fp,"NUMRCPT: %d\r\n",mailinfo.numrcpt) < 1)
    return(-1);

  for(llcur = dll_maximum(mailinfo.rcpt);llcur != NULL;llcur = dll_predecessor(mailinfo.rcpt,llcur))
    {
      if (fprintf(fp,"RCPT: %s\r\n",(char *)llcur) < 1)
	return(-1);
    }

  if (fprintf(fp,"**BODY**\r\n") < 1)
    return(-1);

  if (fprintf(sfileout,"354 Enter mail, end with \".\" on a line by itself\r\n") < 1)
    return(-1);
  fflush(sfileout);

  if (relaymail(sfilein,fp) < 0)
    {
      /* We have already logged and informed user of problem */
      clean_mailinfo(state, MAIL_NOSAVE);
      return(0);
    }

  if (fprintf(sfileout,"250 %s Message accepted for delivery\r\n",mailinfo.pathname) < 1)
    return(-1);

  fclose(fp);

  {
    char buf[1024];

    sprintf(buf,"Mail rhost:%s: queue:%s: from:%s: ",
	    sourceinfo.printhost,mailinfo.pathname,mailinfo.from);
    for(llcur = dll_maximum(mailinfo.rcpt);llcur != NULL;llcur = dll_predecessor(mailinfo.rcpt,llcur))
      {
	if (strlen(buf) > 900)
	  break;
	strcat(buf,"Rcpt:");
	strcat((char *)buf,(char *)llcur);
	strcat(buf,": ");
      }
    soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT,0,buf);
  }

  if (renamemail(mailinfo.pathname) < 0)
    return(-1);

  free(mailinfo.pathname);
  mailinfo.pathname = NULL;

  clean_mailinfo(state, MAIL_SAVE);
  return(0);
}



/*********************************************************************
 *                               rset                                *
 *********************************************************************/
int sub_rset(char *rest, int *state)
{
  clean_mailinfo(state, MAIL_SAVE);
  if (fprintf(sfileout,"250 Reset state\r\n") < 1)
    return(-1);
  return(0);
}



/*********************************************************************
 *                               vrfy                                *
 *********************************************************************/
int sub_vrfy(char *rest, int *state)
{
  if (fprintf(sfileout,"252 Cannot VRFY user but will take message for this user and attempt delivery.\r\n") < 1)
    return(-1);
  return(0);
}



/*********************************************************************
 *                               expn                                *
 *********************************************************************/
int sub_expn(char *rest, int *state)
{
  if (fprintf(sfileout,"252 Cannot EXPN user but will take message for this user and attempt delivery.\r\n") < 1)
    return(-1);
  return(0);
}



/*********************************************************************
 *                               noop                                *
 *********************************************************************/
int sub_noop(char *rest, int *state)
{
  if (fprintf(sfileout,"250 Nothing ventured, nothing gained\r\n") < 1)
    return(-1);
  return(0);
}



/*
 * internal data state
 */
int relaymail(FILE *in, FILE *out)
{
  sos_string INBUF;
  int ret;
  int datatimeout;
  char *tmp;
  int continuation_line = 0;
  int retcode = 0;
  int char_this_session = 0;

  if (!sos_allocstr(&INBUF,BS_IOLEN))
    return(-1);

  datatimeout = atoi(SOS_GWD("SMTP_DATATIMEOUT","300"));
  timedout = 0;

  while (1)
    {
      if (datatimeout)
	alarm(datatimeout);

      ret = sos_readline(in,NULL,&INBUF,&readopts);
      if (timedout) exiterror(451,0,"timeout during data mode: %s",sourceinfo.printhost);
      if (ret == 0) exiterror(451,1,"eof from remote host: %s",sourceinfo.printhost);
      if (ret == -1) exiterror(451,1,"system error during readline: %s",sourceinfo.printhost);
      alarm(0);

      if (ret < -1)
	{
	  continuation_line = 1;
	  ret = -ret-1;
	}
      else
	continuation_line = 0;

      ioinfo.bytes_from_source += ret;
      char_this_session += ret;

      tmp = INBUF.str;

      if (!continuation_line && *tmp == '.')
	{
	  if (*(tmp+1) == '\r' || *(tmp+1) == '\n' || *(tmp+2) == '\0')
	    break;

	  /* By RFC we don't check for a second . */
	  tmp++;
	  char_this_session--;	/* We don't count quoting characters (RFC 1653) */
	}

      if (retcode == 0 && char_this_session > current_max_size)
	{
	  soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT, 0, "Mail message from %s exceeded administrative size limits (%d)", sourceinfo.printhost, current_max_size);
	  if (char_this_session > default_max_size)
	    {
	      retcode = -2;
	    }
	  else
	    {
	      retcode = -3;
	    }
	}

      /* If we are exiting with an error, don't bother writing any more info */
      if (retcode == 0)
	if (fputs(tmp,out) == EOF)
	  {
	    soslog(SOSLOG_INFO,SOSLOG_TYPE_EVENT, 1, "Mail message from %s exceeded available disk space (%d)", sourceinfo.printhost, ioinfo.bytes_from_source);
	    retcode = -1;
	  }
    }

  /* Tell about problems */
  if (retcode == -1)
    {
      retcode = -1;
      if (fprintf(sfileout,"452 %s Message size exceeds physical limits\r\n",mailinfo.pathname) < 1)
	return(-1);
    }
  else if (retcode == -2)
    {
      retcode = -1;
      if (fprintf(sfileout,"552 %s Message size exceeds administrative limits\r\n",mailinfo.pathname) < 1)
	return(-1);
    }
  else if (retcode == -3)
    {
      retcode = -1;
      if (fprintf(sfileout,"452 %s Message size exceeds physical limits\r\n",mailinfo.pathname) < 1)
	return(-1);
    }

  return(retcode);
}



/*
 * Good message, so move to outgoing for final delivery
 */
int renamemail(char *name)
{
  char tmpname[MAXPATHLEN];

  if (chmod(name,BS_MODE) < 0)
    return(-1);

  sprintf(tmpname,"%s/%s",SOS_GWD("MAIL_QUEUEOUT",BS_QUEUEOUT),name);

  return(rename(name,tmpname));
}



/*
 * Generate a unique pathname (need to be more unique than mkstemp can
 * do, because of potenential number of messages being delivered).
 */
char *gen_pathname(FILE **fp)
{
  static unsigned short CNTR = 0;
  static char fulltemplate[14];
  int fd;

  if (CNTR == (unsigned short)-1)
    {
      return(NULL);
    }

  sprintf(fulltemplate,"BS%xXXXXXX",CNTR++);

  if ((fd = mkstemp(fulltemplate)) < 0)
    return (NULL);

  if ((*fp = fdopen(fd,"w+")) == NULL)
    return(NULL);

  return(fulltemplate);
}



/*
 * Generate the current maximum message size
 */
int gen_maxmailsize()
{
/*   struct statfs buf; */
  struct sos_fsinfo buf;
  unsigned long freespace;
  unsigned long bfree;

  if (sos_statfs(".",&buf) < 0) 

    {
      soslog(SOSLOG_INFO,SOSLOG_TYPE_CONDITION, 1, "Could not statfs .");
      return(default_max_size);
    }

  if ( buf.f_fsize != default_block_size )
    {
      buf.f_bavail = sos_blocksize_convert (buf.f_bavail, 
					    buf.f_fsize, default_block_size);
      /*
       * Just to keep our stat structure consistant
       */
      buf.f_fsize = default_block_size;
    }


  if (buf.f_bavail < default_free_blocks )
    {
      soslog(SOSLOG_ALERT,SOSLOG_TYPE_CONDITION, 0, "DISK SPACE ALERT! %d free blocks",buf.f_bavail);
      return (-1);
    }

  bfree = buf.f_bavail - default_free_blocks;
  /* XXX - this is major league wrong -- NOT */
  freespace = default_block_size * bfree;

#ifdef DEBUG
  if (debug)
    fprintf(stderr, "Space: (%d,%d) * %d (%d - %d) = %d\n",buf.f_fsize,
	    default_block_size, bfree, buf.f_bavail, default_free_blocks, 
	    freespace);
#endif

  if (default_max_size)
    freespace = freespace > default_max_size ? default_max_size : freespace;

  return(freespace);
}
