/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/* Sendmail -- a sendmail compatible interface to ZMailer */

#include "hostenv.h"
#include <stdio.h>
#include <sysexits.h>
#include <ctype.h>
#include <pwd.h>
#include "mail.h"
#include "zmsignal.h"
#ifdef FCNTL_H
# include FCNTL_H
#endif
#include <sys/file.h>
#ifdef	HAVE_SYSLOG
#include <syslog.h>
#endif	/* HAVE_SYSLOG */

#define	SMTPSERVER	"smtpserver"
#define	ROUTER		"router"
#define	SCHEDULER	"scheduler"
#define MAILQ		"mailq"
#define	NEWALIASES	"newaliases"

char *zmailer = "ZMailer";
char *verbfile = NULL;

extern FILE *deadletter(), *fopen(), *fdopen();
extern char *emalloc();
extern char *optarg;
extern char *strchr();
extern char *strrchr();
extern char *getenv();
extern char *getlogin();
extern int errno;
extern int optind;
extern int runastrusteduser(), runasrootuser();
extern void prversion(), doabort();
extern int mail_priority;
extern void check_and_print_to();

char *progname;

int D_alloc = 0;	/* memory usage debugging */

#ifdef	lint
#undef	putc
#define	putc	fputc
#endif

FILE	*mfp = NULL;
char	*postoffice;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	int	c, errflg, n, pid, bpflag, uid, truid, external, verbose;
	int	speaksmtp, daemon_flg, interactive, aliases, printq, dotiseof;
	int	binary, vfd;
	char	*ebp, *s, *cp, errbuf[BUFSIZ];
	char	*from, *fullname, *mailbin, *mailshare, *mav[10];
	char	*configfile, *pooption, *path, *mailpriority;
	FILE	*vfp;
	struct passwd *pwd;
	char	*LC_ctype;
	char	buf[8192];
	char   *buf2 = NULL;

	if ((cp = strrchr(argv[0], '/')) != NULL)
		progname = ++cp;
	else
		progname = argv[0];
	umask(022);

	mailpriority = getenv("MAILPRIORITY");
	if (mailpriority) mail_priority = atoi(mailpriority);

	/* Pick up the sender's idea about C-chartype.. */
	LC_ctype = getenv("LC_CTYPE");

	printq = strcmp(progname, MAILQ) == 0;
	aliases = strcmp(progname, NEWALIASES) == 0;
	from = fullname = NULL;
	speaksmtp = daemon_flg = interactive = 0;
	bpflag = external = verbose = binary = 0;
	dotiseof = 1;	/* tested below to see if changed */
	ebp = errbuf;
	*ebp = '\0';
	errflg = 0;
	configfile = pooption = NULL;
	if (printq || aliases)
		goto otherprog;
	while ((c = getopt(argc, argv,"B:EJb:e:r:f:h:F:C:d:Oo:P:imnq:stvVx")) != EOF) {
		switch (c) {
		case 'B':
			/* Sendmail 8.7 compability:
			   -B8BITMIME */
			break;
		case 'J': break; /* Sony NEWS OS  JIS-conversion option,
				    ignore */
		case 'E':
			external = 1;
			break;
		case 'r':
		case 'f':	/* from address */
			from = optarg;
			break;
		case 'F':	/* full name of sender */
			fullname = optarg;
			break;
		case 't':	/* scan header for recipient addresses */
			binary = 1;
			break;
		case 'b':
			switch (*optarg) {
			case 'm':	/* deliver mail */
				/* sure */
				break;
			case 's':	/* speak SMTP on input */
				speaksmtp = 1;
				break;
			case 'd':	/* run as daemon */
				daemon_flg = 1;
				break;
			case 't':	/* run in test mode */
				interactive = 1;
				break;
			case 'i':	/* initialize the alias database */
				aliases = 1;
				break;
			case 'p':	/* print the mail queue */
				printq = 1;
				bpflag = 1;
				break;
			case 'v':	/* just verify addresses */
			case 'a':	/* run in arpanet mode */
			case 'z':	/* freeze the configuration file */
				sprintf(ebp, " -%c%c", c, *optarg);
				ebp += strlen(ebp);
				break;
			}
			break;
		case 'h':	/* hop count */
		case 'n':	/* don't do aliasing or forwarding */
		case 'd':	/* debug level */
			sprintf(ebp, " -%c", c);
			ebp += strlen(ebp);
			break;
		case 'q':	/* process queue */
			/* sometimes with/without option in real sendmail */
			break;
		case 'C':	/* specify configuration file */
			configfile = emalloc((unsigned)(2+strlen(optarg)+1));
			sprintf(configfile, "-f%s", optarg);
			break;
		case 'o':	/* options */
			switch (*optarg) {
			case 'Q':	/* queue directory */
				if (getuid() != geteuid() && getuid() != 0)
					break;
				pooption = emalloc((unsigned)(2+strlen(optarg+1)+1));
				sprintf(pooption, "-P%s", optarg+1);
				postoffice = pooption + 2 /* "-P" */;
				break;
			case 'e':	/* -oem -- delivery mail, errors to sender.
					           Common from ELM MUA.
						   Ignored quietly [mea@utu.fi] */
				break;
				
			case 'i':	/* ignore dots */
				dotiseof = 0;
				break;
			default:
				sprintf(ebp, " -o%s", optarg);
				ebp += strlen(ebp);
				break;
			}
			break;
			/* option idioms */
		case 'e':
			/* too many applications use this, ignore silently */
			break;
		case 'i':	/* ignore dots */
			dotiseof = 0;
			break;
		case 'm':	/* me too */
		case 'O':	/* for NeXT mail */
			/* default behavior */
			break;
		case 'v':	/* be verbose */
			verbose = 1;
			break;
		case 's':	/* save From_ lines */
			sprintf(ebp, " -%c", c);
			ebp += strlen(ebp);
			break;
		case 'P':
			mail_priority = atoi(optarg);
			break;
		case 'V':
			prversion("sendmail");
			exit(0);
			break;
		case 'x':	/* Ignore this AIXism */
			break;
		default:
			++errflg;
			break;
		}
	}

	/* Make sure the submission priority is >= 0  */
	if (mail_priority < 0) mail_priority = 0;

	if (errbuf[0] != '\0')
		fprintf(stderr, "%s: ignored %s options:%s\n",
				zmailer, progname, errbuf);
otherprog:
	if (speaksmtp + interactive + aliases + printq + daemon_flg > 1) {
		fprintf(stderr, "%s: conflicting sendmail options\n",
				zmailer);
	}
	if (errflg) {
		fprintf(stderr, "Usage: %s [sendmail options]\n",
				       progname);
		exit(EX_USAGE);
	}
	n = 0;
	mav[n++] = ROUTER;		/* has to be something... */
	mav[n++] = NULL;
	if (configfile != NULL)
		mav[n++] = configfile;
	if (pooption != NULL)
		mav[n++] = pooption;
	mav[n] = NULL;
	if ((mailbin = getzenv("MAILBIN")) == NULL)
		mailbin = MAILBIN;
	if ((mailshare = getzenv("MAILSHARE")) == NULL)
		mailshare = MAILSHARE;
	path = NULL;
	if (speaksmtp) {
		path = emalloc((unsigned)(strlen(mailbin)+1+strlen(SMTPSERVER)+1));
		sprintf(path, "%s/%s", mailbin, SMTPSERVER);
		/* pass on suidness */
		execl(path, "smtp-in", "-i", (char *)NULL);
		perror(path);
	} else if (daemon_flg) {
		setuid(getuid());
		if (chdir(mailbin) < 0) {
			perror(mailbin);
			exit(EX_UNAVAILABLE);
		}
		if ((pid = fork()) > 0) {
			mav[1] = "-dk";
			execv(ROUTER, mav);
			perror(ROUTER);
			exit(EX_UNAVAILABLE);
		} else if (pid == 0) {
			mav[0] = "scheduler";
			mav[1] = "-d";
			execv(SCHEDULER, mav);
			perror(SCHEDULER);
			exit(EX_UNAVAILABLE);
		}
		perror("fork");
		exit (EX_OSERR);
	} else if (interactive) {
		setuid(getuid());
		mav[1] = "-i";
		path = emalloc((unsigned)(strlen(mailbin)+1+strlen(ROUTER)+1));
		sprintf(path, "%s/%s", mailbin, ROUTER);
		execv(path, mav);
		perror(path);
		exit(EX_UNAVAILABLE);
	} else if (aliases) {
		setuid(getuid());
		mav[1] = emalloc((unsigned)(2+strlen(mailbin)+1
					     +strlen(NEWALIASES)+1));
		sprintf(mav[1], "-f%s/%s", mailbin, NEWALIASES);
		path = emalloc((unsigned)(strlen(mailbin)+1+strlen(ROUTER)+1));
		sprintf(path, "%s/%s", mailbin, ROUTER);
		execv(path, mav);
		perror(path);
		exit(EX_UNAVAILABLE);
	} else if (printq) {
		setuid(getuid());
		path = emalloc((unsigned)(strlen(mailbin)+1+strlen(MAILQ)+1));
		sprintf(path, "%s/%s", mailbin, MAILQ);
		argv[0] = MAILQ;
		if (bpflag)
			argv[1] = NULL;
		execv(path, argv);
		perror(path);
		exit(EX_UNAVAILABLE);
	} else {	/* ahh, normal, at last! */
		RETSIGTYPE (*oldsig)();
		SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
		if (oldsig != SIG_IGN)
			SIGNAL_HANDLE(SIGINT, doabort);
		SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig);
		if (oldsig != SIG_IGN)
			SIGNAL_HANDLE(SIGTERM, doabort);
		SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
		if (oldsig != SIG_IGN)
			SIGNAL_HANDLE(SIGHUP, doabort);
		/*
		 * If running as root, run as the user who su'ed if applicable.
		 * If still running as root, run as the trusted user.  If from
		 * was specified, we're okay.  If from wasn't specified
		 * but the real uid is nonzero, use that name.  Else if
		 * getlogin() returns something, use that.  Else use root.
		 */
		uid = getuid();
		truid = runastrusteduser();
		if ((mfp = mail_open(MSG_RFC822)) == NULL) {
		  n = errno;
		  fprintf(stderr, "%s: cannot submit mail (err# %d)!\n",
			  zmailer, errno);
#ifdef	LOG_CRIT
#ifdef	LOG_MAIL
		  openlog("sendmail", LOG_PID, LOG_MAIL);
#else  /* !LOG_MAIL */
		  openlog("sendmail", LOG_PID);
#endif /* LOG_MAIL */
		  errno = n;
		  syslog(
#ifdef	LOG_SALERT
			 LOG_SALERT
#else  /* LOG_SALERT */
			 LOG_ALERT
#endif /* LOG_SALERT */
			 , "cannot submit mail for uid %d: %m", uid);
#endif /* LOG_CRIT */
		  mfp = deadletter(uid);
		}
		if (truid != uid) {
		  /* if we are setuid root, and not invoked by root,
		     hardwire in the definition of from so the mailer
		     will know */
#ifdef	HAVE_FCHOWN
		  runasrootuser();
		  if (uid != 0 && fchown(fileno(mfp), uid, -1) == 0)
		    ;
		  else
#endif /* HAVE_FCHOWN */
		    if (from == NULL) {
		      if (uid != 0 && (pwd = getpwuid(uid)) != NULL)
			from = pwd->pw_name;
		      else if ((cp = getlogin()) != NULL)
			from = cp;
		      else	/* could find with getpwnam */
			from = "root";
		    }
#ifdef	HAVE_FCHOWN
		  runastrusteduser();
#endif /* HAVE_FCHOWN */
		}

		vfd = -1;
		if (verbose) {
		  if (postoffice == NULL) {
		    postoffice = getzenv("POSTOFFICE");
		    if (postoffice == NULL)
		      postoffice = POSTOFFICE;
		  }
		  verbfile = emalloc(strlen(postoffice)
				     +strlen(PUBLICDIR)+20);
		  sprintf(verbfile, "%s/%s/v_XXXXXX",
			  postoffice, PUBLICDIR);
		  mktemp(verbfile);
		  if (*verbfile == '\0' ||
		      (vfd = open(verbfile, O_CREAT|O_RDWR, 0600)) < 0) {
		    fprintf(stderr,
			    "%s: cannot create verbose log file in %s/%s\n",
			    zmailer, postoffice, PUBLICDIR);
		    verbfile = NULL;
		  } else {
		    /*
		     * We need to make it a relative pathname
		     * in case the router/scheduler's idea of
		     * root directory is different than ours.
		     */
		    cp = strrchr(verbfile, '/');
		    while (--cp > verbfile)
		      if (*cp == '/')
			break;
		    fprintf(mfp, "verbose \"../%s\"\n", cp+1);
		  }
		}
		if (external)
		  fprintf(mfp, "external\n");
		if (fullname != NULL && *fullname != '\0')
		  /* maybe we should put it in the environment? */
		  fprintf(mfp, "fullname %s\n", fullname);
		if (from != NULL && *from != '\0')
		  fprintf(mfp, "from %s\n", from);
		if (!binary) {
		  for (; optind < argc; ++optind)
		    /*fprintf(mfp, "to %s\n", argv[optind]);*/
		    check_and_print_to(mfp,argv[optind]);
		}

		if (fflush(mfp) == EOF
/* #ifdef NFSFSYNC */  /* This is propably ALWAYS a good idea.. */
		    || fsync(fileno(mfp)) < 0
/* #endif */	/* NFSFSYNC */
		    || ferror(mfp)) {
		  mail_abort(mfp);
		  mfp = deadletter(uid);
		  errflg = 1;
		}

		/*
		 * The postoffice cannot be changed from outside mail_open(),
		 * which, though it may be unsatisfactory, is good for security.
		 */
		
		if (binary) {	/* haa@cs.hut.fi  found a bug in read() --
				   sizeof buf[0] != sizeof buf  :-)     */
		  while ((n = read(0, buf, sizeof buf)) > 0) {
		    if (fwrite(buf, sizeof buf[0], n, mfp) != n)
		      break;
		  }
		} else {
		  /* cmd line can only set dotiseof to 0.. improvement? */
		  int crlf_strip = 0;
		  if (dotiseof)
		    dotiseof = isatty(fileno(stdin));
		  n = 0;
		  while (fgets(buf, sizeof buf, stdin)) {
		    /* In case the input contains CRLF instead of LF,
		       do convert them.. */
		    if (crlf_strip) {
		      s = strrchr(buf, '\r');
		      if (s && s[1] == '\n' && s[2] == 0) {
			s[0] = '\n';
			s[1] = 0;
		      }
		    }
		    if (++n == 1) {

		      /* The first line, see if we have junk in the begin..
			 But detect at first the input for CRLF line
			 termination in place of the UNIX-normal LF ... */

		      if ((s = strrchr(buf, '\r')) != NULL &&
			  s[1] == '\n' && s[2] == 0) {
			/* Brr... CRLF+NULL */
			crlf_strip = 1;
			s[0] = '\n';
			s[1] = 0;
		      }
		      if (strncmp(buf,"From ",5)==0) {
			/* [mea@utu.fi] I vote for
			   removing this line if next
			   is a RFC-header */
			buf2 = emalloc(2+strlen(buf));
			if (!buf2) { errflg=1; break; }
			strcpy(buf2,buf);
			continue;
		      }
		      if (strncmp(buf,">From ",6)==0) {
			/* [mea@utu.fi] I vote for
			   removing this line if next
			   is a RFC-header */
			buf2 = emalloc(2+strlen(buf));
			if (!buf2) { errflg=1; break; }
			strcpy(buf2,buf);
			continue;
		      }
		      for (s = buf;
			   isascii(*s) && !isspace(*s)
			   && *s != ':';
			   ++s)
			continue;
		      if (*s != ':')
			putc('\n', mfp);
		    }
		    if (n==2 && buf2) { /* Handle 2nd line if first begun
					   with a "From "  */
		      for (s = buf;
			   isascii(*s) && !isspace(*s) && *s != ':';
			   ++s)
			continue;
		      if (*s != ':') {
			putc('\n', mfp);
			fputs( buf2, mfp );
		      }
		    }
		    s = buf;
		    if (dotiseof && *s == '.' && *++s == '\n')
		      break;
		    fputs(buf, mfp);
		  }
		}

		fflush(mfp);
		if (errflg || ferror(mfp)) {
		  fprintf(stderr,
			  "%s: message not submitted due to I/O error!\n",
			  zmailer);
		  if (!errflg)
		    mail_abort(mfp);
		  if (vfd >= 0)
		    unlink(verbfile);
		  exit(EX_IOERR);
		} else if (mail_close(mfp) == EOF) {
		  fprintf(stderr, "%s: message not submitted!\n",
			  zmailer);
		  if (vfd >= 0)
		    unlink(verbfile);
		  exit(EX_UNAVAILABLE);
		}
		if (vfd >= 0) {
		  if (fork() > 0) exit(EX_OK); /* Let the child to do
						  the trace printout */
		  if (vfd != 0) close(0);
		  if (vfd != 1) close(1);
		  vfp = fdopen(vfd, "r");
		  while (vfp != NULL) {
		    while (fgets(buf, sizeof buf, vfp) != NULL)
		      fprintf(stderr, "%s", buf);
		    if (strncmp(buf, "scheduler done", 14) == 0)
		      break;
		    clearerr(vfp);
		    sleep(1);
		  }
		}
		if (verbfile)
		  unlink(verbfile);
		exit(EX_OK);
		/* NOTREACHED */
	}
	if (path != NULL)
	  free(path);
	exit(EX_UNAVAILABLE);
	/* NOTREACHED */
	return 0;
}

FILE *
deadletter(uid)
	int uid;
{
	struct passwd *pwd;
	FILE *fp;
	char	buf[8192];

	if ((pwd = getpwuid(uid)) == NULL) {
		fprintf(stderr, "%s: can't save mail!\n", zmailer);
		exit(EX_NOUSER);
		/* NOTREACHED */
	}
	sprintf(buf, "%s/dead.letter", pwd->pw_dir);
	if ((fp = fopen(buf, "a")) == NULL) {
		fprintf(stderr, "%s: can't open \"%s\" to save mail!\n",
				zmailer, buf);
		exit(EX_CANTCREAT);
		/* NOTREACHED */
	}
	fprintf(stderr, "%s: mail saved in \"%s\"\n", zmailer, buf);
	return fp;
}

void
doabort()
{
	if (mfp != NULL)
		mail_abort(mfp);
	if (verbfile != NULL && *verbfile != '\0')
		unlink(verbfile);
	fprintf(stderr, "%s: interrupt! message submission aborted!\n",
		zmailer);
	exit(EX_TEMPFAIL);
}

/* haa@cs.hut.fi:  Sometimes address components contain pure junk.. */
void
check_and_print_to(mfp, addr)
	FILE *mfp;
	char *addr;
{
	char *copy = NULL, *printme = addr, *from, *to;

	if (addr == NULL) {
	  fprintf(stderr, "sendmail: Argument botch: NULL address\n");
	  return;
	}
	if (strchr(addr,'\n')) {
	  fprintf(stderr, "sendmail: LF in  to<SPC> address: %s\n",addr);
	  copy = emalloc(strlen(addr)+1); /* +1 just to be sure */
	  if (copy == NULL) return;	  /* should never happen... */

	  to = copy;
	  for (from = addr; *from; ++from)
	    if (*from != '\n') *to++ = *from;
	  *to = 0;

	  printme = copy;
	}
	fprintf(mfp, "to %s\n", printme);
	if (copy) free(copy);
}

#if 0
char *
tmalloc(n)
	unsigned int n;
{
	return emalloc(n);
}
#endif
