Beispiel #1
0
long dummy_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
{
  struct stat sbuf;
  int fd = -1;
  int e;
  char tmp[MAILTMPLEN];
  MAILSTREAM *ts = default_proto (T);
  if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox) &&
      ((fd = open (tmp,O_RDONLY,NIL)) < 0)) {
    if ((e = errno) == ENOENT)	/* failed, was it no such file? */
      mm_notify (stream,"[TRYCREATE] Must create mailbox before append",
		 (long) NIL);
    sprintf (tmp,"%.80s: %.80s",strerror (e),mailbox);
    mm_log (tmp,ERROR);		/* pass up error */
    return NIL;			/* always fails */
  }
  if (fd >= 0) {		/* found file? */
    fstat (fd,&sbuf);		/* get its size */
    close (fd);			/* toss out the fd */
    if (sbuf.st_size) ts = NIL;	/* non-empty file? */
  }
  if (ts) return (*ts->dtb->append) (stream,mailbox,af,data);
  sprintf (tmp,"Indeterminate mailbox format: %.80s",mailbox);
  mm_log (tmp,ERROR);
  return NIL;
}
Beispiel #2
0
long dummy_rename (MAILSTREAM *stream,char *old,char *newname)
{
  struct stat sbuf;
  char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN];
  long ret = NIL;
				/* no trailing \ allowed */
  if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) ||
      ((s = strrchr (s,'\\')) && !s[1])) {
    sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname);
    mm_log (mbx,ERROR);
    return NIL;
  }
				/* found superior to destination name? */
  if (s && (s != mbx) && ((mbx[1] != ':') || (s != mbx + 2))) {
    c = s[1];			/* remember character after delimiter */
    *s = s[1] = '\0';		/* tie off name at delimiter */
				/* name doesn't exist, create it */
    if (stat (mbx,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
      *s = '\\';		/* restore delimiter */
      if (!dummy_create (stream,mbx)) return NIL;
    }
    else *s = '\\';		/* restore delimiter */
    s[1] = c;			/* restore character after delimiter */
  }
				/* rename of non-ex INBOX creates dest */
  if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf))
    return dummy_create (NIL,mbx);
  if (rename (oldname,mbx)) {
    sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname,
	     strerror (errno));
    mm_log (tmp,ERROR);
    return NIL;
  }
  return LONGT;			/* return success */
}
Beispiel #3
0
MAILSTREAM *dummy_open (MAILSTREAM *stream)
{
  int fd;
  char err[MAILTMPLEN],tmp[MAILTMPLEN];
  struct stat sbuf;
				/* OP_PROTOTYPE call */
  if (!stream) return &dummyproto;
  err[0] = '\0';		/* no error message yet */
				/* can we open the file? */
  if (!dummy_file (tmp,stream->mailbox))
    sprintf (err,"Can't open this name: %.80s",stream->mailbox);
  else if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
				/* no, error unless INBOX */
    if (compare_cstring (stream->mailbox,"INBOX"))
      sprintf (err,"%.80s: %.80s",strerror (errno),stream->mailbox);
  }
  else {			/* file had better be empty then */
    fstat (fd,&sbuf);		/* sniff at its size */
    close (fd);
    if (sbuf.st_size)		/* bogus format if non-empty */
      sprintf (err,"%.80s (file %.80s) is not in valid mailbox format",
	       stream->mailbox,tmp);
  }
  if (err[0]) {			/* if an error happened */
    mm_log (err,stream->silent ? WARN : ERROR);
    return NIL;
  }
  else if (!stream->silent) {	/* only if silence not requested */
    mail_exists (stream,0);	/* say there are 0 messages */
    mail_recent (stream,0);	/* and certainly no recent ones! */
    stream->uid_validity = time (0);
  }
  stream->inbox = T;		/* note that it's an INBOX */
  return stream;		/* return success */
}
Beispiel #4
0
long sm_unsubscribe (char *mailbox)
{
  FILE *f,*tf;
  char *s,tmp[MAILTMPLEN],old[MAILTMPLEN],newname[MAILTMPLEN];
  int found = NIL;
				/* canonicalize INBOX */
  if (!compare_cstring (mailbox,"INBOX")) mailbox = "INBOX";
  SUBSCRIPTIONFILE (old);	/* make file names */
  SUBSCRIPTIONTEMP (newname);
  if (!(f = fopen (old,"r")))	/* open subscription database */
    MM_LOG ("No subscriptions",ERROR);
  else if (!(tf = fopen (newname,"w"))) {
    MM_LOG ("Can't create subscription temporary file",ERROR);
    fclose (f);
  }
  else {
    while (fgets (tmp,MAILTMPLEN,f)) {
      if (s = strchr (tmp,'\n')) *s = '\0';
      if (strcmp (tmp,mailbox)) fprintf (tf,"%s\n",tmp);
      else found = T;		/* found the name */
    }
    fclose (f);
    if (fclose (tf) == EOF)
      MM_LOG ("Can't write subscription temporary file",ERROR);
    else if (!found) {
      sprintf (tmp,"Not subscribed to mailbox %.80s",mailbox);
      MM_LOG (tmp,ERROR);	/* error if at end */
    }
    else if (!unlink (old) && !rename (newname,old)) return LONGT;
    else MM_LOG ("Can't update subscription database",ERROR);
  }
  return NIL;
}
Beispiel #5
0
long sm_subscribe (char *mailbox)
{
  FILE *f;
  char *s,db[MAILTMPLEN],tmp[MAILTMPLEN];
				/* canonicalize INBOX */
  if (!compare_cstring (mailbox,"INBOX")) mailbox = "INBOX";
  SUBSCRIPTIONFILE (db);	/* open subscription database */
  if (f = fopen (db,"r")) {	/* make sure not already there */
    while (fgets (tmp,MAILTMPLEN,f)) {
      if (s = strchr (tmp,'\n')) *s = '\0';
      if (!strcmp (tmp,mailbox)) {/* already subscribed? */
	sprintf (tmp,"Already subscribed to mailbox %.80s",mailbox);
	MM_LOG (tmp,ERROR);
	fclose (f);
	return NIL;
      }
    }
    fclose (f);
  }
  if (!(f = fopen (db,"a"))) {	/* append new entry */
    MM_LOG ("Can't append to subscription database",ERROR);
    return NIL;
  }
  fprintf (f,"%s\n",mailbox);
  return (fclose (f) == EOF) ? NIL : T;
}
Beispiel #6
0
long dummy_create (MAILSTREAM *stream,char *mailbox)
{
  char tmp[MAILTMPLEN];
  if (compare_cstring (mailbox,"INBOX") && dummy_file (tmp,mailbox))
    return dummy_create_path (stream,tmp,NIL);
  sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
  mm_log (tmp,ERROR);
  return NIL;
}
Beispiel #7
0
long smtp_ehlo (SENDSTREAM *stream,char *host,NETMBX *mb)
{
  unsigned long i,j;
  long flags = (mb->secflag ? AU_SECURE : NIL) |
    (mb->authuser[0] ? AU_AUTHUSER : NIL);
  char *s,*t,*r,tmp[MAILTMPLEN];
				/* clear ESMTP data */
  memset (&ESMTP,0,sizeof (ESMTP));
  if (mb->loser) return 500;	/* never do EHLO if a loser */
  sprintf (tmp,"EHLO %s",host);	/* build the complete command */
  if (stream->debug) mm_dlog (tmp);
  strcat (tmp,"\015\012");
				/* send the command */
  if (!net_soutr (stream->netstream,tmp))
    return smtp_fake (stream,"SMTP connection broken (EHLO)");
				/* got an OK reply? */
  do if ((i = smtp_reply (stream)) == SMTPOK) {
				/* hack for AUTH= */
    if (stream->reply[4] && stream->reply[5] && stream->reply[6] &&
	stream->reply[7] && (stream->reply[8] == '=')) stream->reply[8] = ' ';
				/* get option code */
    if (!(s = strtok_r (stream->reply+4," ",&r)));
				/* have option, does it have a value */
    else if ((t = strtok_r (NIL," ",&r)) && *t) {
				/* EHLO options which take arguments */
      if (!compare_cstring (s,"SIZE")) {
	if (isdigit (*t)) ESMTP.size.limit = strtoul (t,&t,10);
	ESMTP.size.ok = T;
      }
      else if (!compare_cstring (s,"DELIVERBY")) {
	if (isdigit (*t)) ESMTP.deliverby.minby = strtoul (t,&t,10);
	ESMTP.deliverby.ok = T;
      }
      else if (!compare_cstring (s,"ATRN")) {
	ESMTP.atrn.domains = cpystr (t);
	ESMTP.atrn.ok = T;
      }
      else if (!compare_cstring (s,"AUTH"))
	do if ((j = mail_lookup_auth_name (t,flags)) &&
	       (--j < MAXAUTHENTICATORS)) ESMTP.auth |= (1 << j);
	while ((t = strtok_r (NIL," ",&r)) && *t);
    }
				/* EHLO options which do not take arguments */
    else if (!compare_cstring (s,"SIZE")) ESMTP.size.ok = T;
    else if (!compare_cstring (s,"8BITMIME")) ESMTP.eightbit.ok = T;
    else if (!compare_cstring (s,"DSN")) ESMTP.dsn.ok = T;
    else if (!compare_cstring (s,"ATRN")) ESMTP.atrn.ok = T;
    else if (!compare_cstring (s,"SEND")) ESMTP.service.send = T;
    else if (!compare_cstring (s,"SOML")) ESMTP.service.soml = T;
    else if (!compare_cstring (s,"SAML")) ESMTP.service.saml = T;
    else if (!compare_cstring (s,"EXPN")) ESMTP.service.expn = T;
    else if (!compare_cstring (s,"HELP")) ESMTP.service.help = T;
    else if (!compare_cstring (s,"TURN")) ESMTP.service.turn = T;
    else if (!compare_cstring (s,"ETRN")) ESMTP.service.etrn = T;
    else if (!compare_cstring (s,"STARTTLS")) ESMTP.service.starttls = T;
    else if (!compare_cstring (s,"RELAY")) ESMTP.service.relay = T;
    else if (!compare_cstring (s,"PIPELINING")) ESMTP.service.pipe = T;
    else if (!compare_cstring (s,"ENHANCEDSTATUSCODES"))
      ESMTP.service.ensc = T;
    else if (!compare_cstring (s,"BINARYMIME")) ESMTP.service.bmime = T;
    else if (!compare_cstring (s,"CHUNKING")) ESMTP.service.chunk = T;
  }
  while ((i < 100) || (stream->reply[3] == '-'));
				/* disable LOGIN if PLAIN also advertised */
  if ((j = mail_lookup_auth_name ("PLAIN",NIL)) && (--j < MAXAUTHENTICATORS) &&
      (ESMTP.auth & (1 << j)) &&
      (j = mail_lookup_auth_name ("LOGIN",NIL)) && (--j < MAXAUTHENTICATORS))
    ESMTP.auth &= ~(1 << j);
  return i;			/* return the response code */
}
Beispiel #8
0
SENDSTREAM *smtp_open_full (NETDRIVER *dv,char **hostlist,char *service,
			    unsigned long port,long options)
{
  SENDSTREAM *stream = NIL;
  long reply;
  char *s,tmp[MAILTMPLEN];
  NETSTREAM *netstream;
  NETMBX mb;
  if (!(hostlist && *hostlist)) mm_log ("Missing SMTP service host",ERROR);
				/* maximum domain name is 64 characters */
  else do if (strlen (*hostlist) < SMTPMAXDOMAIN) {
    sprintf (tmp,"{%.1000s}",*hostlist);
    if (!mail_valid_net_parse_work (tmp,&mb,service ? service : "smtp") ||
	mb.anoflag || mb.readonlyflag) {
      sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
      mm_log (tmp,ERROR);
    }
    else {			/* light tryssl flag if requested */
      mb.trysslflag = (options & SOP_TRYSSL) ? T : NIL;
				/* explicit port overrides all */
      if (mb.port) port = mb.port;
				/* else /submit overrides port argument */
      else if (!compare_cstring (mb.service,"submit")) {
	port = SUBMITTCPPORT;	/* override port, use IANA name */
	strcpy (mb.service,"submission");
      }
				/* else port argument overrides SMTP port */
      else if (!port) port = smtp_port ? smtp_port : SMTPTCPPORT;
      if (netstream =		/* try to open ordinary connection */
	  net_open (&mb,dv,port,
		    (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
		    "*smtps",smtp_sslport ? smtp_sslport : SMTPSSLPORT)) {
	stream = (SENDSTREAM *) memset (fs_get (sizeof (SENDSTREAM)),0,
					sizeof (SENDSTREAM));
	stream->netstream = netstream;
	stream->host = cpystr ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
			       net_host (netstream) : mb.host);
	stream->debug = (mb.dbgflag || (options & OP_DEBUG)) ? T : NIL;
	if (options & SOP_SECURE) mb.secflag = T;
				/* get name of local host to use */
	s = compare_cstring ("localhost",mb.host) ?
	  net_localhost (netstream) : "localhost";

	do reply = smtp_reply (stream);
	while ((reply < 100) || (stream->reply[3] == '-'));
	if (reply != SMTPGREET){/* get SMTP greeting */
	  sprintf (tmp,"SMTP greeting failure: %.80s",stream->reply);
	  mm_log (tmp,ERROR);
	  stream = smtp_close (stream);
	}
				/* try EHLO first, then HELO */
	else if (((reply = smtp_ehlo (stream,s,&mb)) != SMTPOK) &&
		 ((reply = smtp_send (stream,"HELO",s)) != SMTPOK)) {
	  sprintf (tmp,"SMTP hello failure: %.80s",stream->reply);
	  mm_log (tmp,ERROR);
	  stream = smtp_close (stream);
	}
	else {
	  NETDRIVER *ssld =(NETDRIVER *)mail_parameters(NIL,GET_SSLDRIVER,NIL);
	  sslstart_t stls = (sslstart_t) mail_parameters(NIL,GET_SSLSTART,NIL);
	  ESMTP.ok = T;		/* ESMTP server, start TLS if present */
	  if (!dv && stls && ESMTP.service.starttls &&
	      !mb.sslflag && !mb.notlsflag &&
	      (smtp_send (stream,"STARTTLS",NIL) == SMTPGREET)) {
	    mb.tlsflag = T;	/* TLS OK, get into TLS at this end */
	    stream->netstream->dtb = ssld;
				/* TLS started, negotiate it */
	    if (!(stream->netstream->stream = (*stls)
		  (stream->netstream->stream,mb.host,
		   (mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
		   (mb.novalidate ? NET_NOVALIDATECERT:NIL)))){
				/* TLS negotiation failed after STARTTLS */
	      sprintf (tmp,"Unable to negotiate TLS with this server: %.80s",
		       mb.host);
	      mm_log (tmp,ERROR);
				/* close without doing QUIT */
	      if (stream->netstream) net_close (stream->netstream);
	      stream->netstream = NIL;
	      stream = smtp_close (stream);
	    }
				/* TLS OK, re-negotiate EHLO */
	    else if ((reply = smtp_ehlo (stream,s,&mb)) != SMTPOK) {
	      sprintf (tmp,"SMTP EHLO failure after STARTTLS: %.80s",
		       stream->reply);
	      mm_log (tmp,ERROR);
	      stream = smtp_close (stream);
	    }
	    else ESMTP.ok = T;	/* TLS OK and EHLO successful */
	  }
	  else if (mb.tlsflag) {/* user specified /tls but can't do it */
	    sprintf (tmp,"TLS unavailable with this server: %.80s",mb.host);
	    mm_log (tmp,ERROR);
	    stream = smtp_close (stream);
	  }

				/* remote name for authentication */
	  if (stream && ((mb.secflag || mb.user[0]))) {
	    if (ESMTP.auth) {	/* use authenticator? */
	      if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
				/* remote name for authentication */
		strncpy (mb.host,
			 (long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
			 net_remotehost (netstream) : net_host (netstream),
			 NETMAXHOST-1);
		mb.host[NETMAXHOST-1] = '\0';
	      }
	      if (!smtp_auth (stream,&mb,tmp)) stream = smtp_close (stream);
	    }
	    else {		/* no available authenticators? */
	      sprintf (tmp,"%sSMTP authentication not available: %.80s",
		       mb.secflag ? "Secure " : "",mb.host);
	      mm_log (tmp,ERROR);
	      stream = smtp_close (stream);
	    }
	  }
	}
      }
    }
  } while (!stream && *++hostlist);
  if (stream) {			/* set stream options if have a stream */
    if (options &(SOP_DSN | SOP_DSN_NOTIFY_FAILURE | SOP_DSN_NOTIFY_DELAY |
		  SOP_DSN_NOTIFY_SUCCESS | SOP_DSN_RETURN_FULL)) {
      ESMTP.dsn.want = T;
      if (options & SOP_DSN_NOTIFY_FAILURE) ESMTP.dsn.notify.failure = T;
      if (options & SOP_DSN_NOTIFY_DELAY) ESMTP.dsn.notify.delay = T;
      if (options & SOP_DSN_NOTIFY_SUCCESS) ESMTP.dsn.notify.success = T;
      if (options & SOP_DSN_RETURN_FULL) ESMTP.dsn.full = T;
    }
    if (options & SOP_8BITMIME) ESMTP.eightbit.want = T;
  }
  return stream;
}
Beispiel #9
0
int deliver (FILE *f,unsigned long msglen,char *user)
{
  MAILSTREAM *ds = NIL;
  char *s,*mailbox,tmp[MAILTMPLEN],path[MAILTMPLEN];
  STRING st;
  struct stat sbuf;
				/* have a mailbox specifier? */
  if ((mailbox = strchr (user,'+')) != NULL) {
    *mailbox++ = '\0';		/* yes, tie off user name */
    if (!*mailbox || !compare_cstring ((unsigned char *) mailbox,"INBOX"))
      mailbox = NIL;		/* user+ and user+INBOX same as user */
  }
  if (!*user) user = myusername ();
  else if (strcmp (user,myusername ()))
    return fail ("can't deliver to other user",EX_CANTCREAT);
  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 ((strlen (mailbox) <= NETMAXMBX) &&
	  !strstr (mailbox,"..") && !strstr (mailbox,"//") &&
	  !strstr (mailbox,"/~") && mailboxfile (path,mailbox) && path[0] &&
	  !deliver_safely (NIL,&st,mailbox,path,tmp)) return NIL;
    case '%': case '*':		/* wildcards 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 */
  }

				/* 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,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,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,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,tmp);
				/* impute path that we will create */
    if (!ibxpath (ds = 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,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,tmp);
}
Beispiel #10
0
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");
  }