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

/*LINTLIBRARY*/

#include <stdio.h>
#include "hostenv.h"
#include <errno.h>
#include <sys/param.h>
#include <sys/stat.h>
#ifdef FCNTL_H
# include FCNTL_H
#endif
#include <sys/file.h>
#include <sys/socket.h>
#include "mail.h"

extern char *fullname();
extern char *getenv();
extern char *strchr();
extern char *strrchr();
extern char *whathost();
extern int errno;

/*
 * Standard routines that may be used by any program to submit mail.
 *
 * This file should be part of the standard C library on your system.
 * 
 * The proper use of these routines is as follows:
 *
 *	...
 *      mail_priority = 0;
 *	FILE *mfp = mail_open(type);
 *	if (mfp != NULL) {
 *	... output the mail message to mfp ...
 *	} else
 *		... error handling for not even being able to open the file ...
 *	if (oops)
 *		(void) mail_abort(mfp);
 *	else if (mail_close(mfp) == EOF)
 *		... error handling if something went wrong ...
 *	...
 *
 * Note that the return value from these routines corresponds to the
 * return values of fopen() and fclose() respectively. The routines
 * are single-threaded due to the need to remember a filename.
 *
 * Note also that the mail_alloc() routine is called instead of malloc()
 * directly, allowing applications that make many calls to these routines
 * during the process lifetime to provide an alternate byte allocator
 * that will not cause them to run out of data space.  Similarly, the
 * mail_host() routine is expected to return a unique host identification.
 *
 * Some simple signal handling is advisable too.
 */


/* array of message file name associated with a file descriptor */
static char **mail_file = NULL;
static int mail_nfiles = 0;
char *postoffice;	/* may be extern or local */

/*
  Define sending mail priority.
*/

int mail_priority = 0;

/*
 * Makes a temporary file under the postoffice, based on a file name template.
 * The last '%' character of the file name passed will be overwritten with
 * different suffix characters until the open() succeeds or we have exhausted
 * the search space.  Note: a single process cannot hold more than number-of-
 * suffix-characters message files at once.
 */

FILE *
_mail_fopen(filenamep)
	char **filenamep;
{
	char *path, *cp, *suffix, *post;
	FILE *fp;
	int fd, eno;

	if (postoffice == NULL && (postoffice = getzenv("POSTOFFICE")) == NULL)
		postoffice = POSTOFFICE;
	path = mail_alloc(strlen(postoffice)+strlen(*filenamep)+2);
	sprintf(path, "%s/%s", postoffice, *filenamep);
	for (cp = *filenamep; *cp != '\0' && *cp != '%'; ++cp)
		continue;
	if (*cp == '%') {
		post = cp + 1;
		cp = (cp - *filenamep) + strlen(postoffice) + 1 + path;
	} else
		post = cp = NULL;
	fp = NULL;
	eno = 0;
	for (suffix = SUFFIXCHARS; *suffix != 0; ++suffix) {
		if (cp == NULL)
			sleep(2);	/* hope something happens meanwhile */
		else if (*suffix != ' ') {
			*cp = *suffix;
			strcpy(cp+1, post);
		} else
			strcpy(cp, post);
		if ((fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0) {
			fp = fdopen(fd, "w+");
			mail_free(*filenamep);
			*filenamep = path;
			return fp;
		}
		eno = errno;
	}
	mail_free(path);
	errno = eno;
	return fp;
}

/*
 * Link from-file to a file given by the to-file template.
 * The last '%' character of the to-file name passed will be overwritten with
 * different suffix characters until the link() succeeds or we have exhausted
 * the search space.
 */

int
mail_link(from, tonamep)
	const char *from;
	char **tonamep;
{
	char *path, *cp, *suffix, *post;
	int eno;

	if (postoffice == NULL && (postoffice = getzenv("POSTOFFICE")) == NULL)
		postoffice = POSTOFFICE;
	path = mail_alloc(strlen(postoffice)+strlen(*tonamep)+2);
	sprintf(path, "%s/%s", postoffice, *tonamep);
	for (cp = *tonamep; *cp != '\0' && *cp != '%'; ++cp)
		continue;
	if (*cp == '%') {
		post = cp + 1;
		cp = (cp - *tonamep) + strlen(postoffice) + 1 + path;
	} else
		post = cp = NULL;
	eno = 0;
	for (suffix = SUFFIXCHARS; *suffix != 0; ++suffix) {
		if (cp == NULL)
			sleep(2); /* hope something happens meanwhile */
		else if (*suffix != ' ') {
			*cp = *suffix;
			strcpy(cp+1, post);
		} else
			strcpy(cp, post);
		if (link(from, path) >= 0) {
			mail_free(*tonamep);
			*tonamep = path;
			return 0;
		}
		eno = errno;
	}
	mail_free(path);
	errno = eno;
	return -1;
}

/*
 * Open a message file of the specified type and initialize generic envelope
 * information (i.e. the file position on return may not be 0).
 */

FILE *
mail_open(type)
	const char *type;
{
	char *scratch, *message, *cp;
	FILE *fp;
	int eno;
	struct stat stbuf;
	char namebuf[BUFSIZ];
	static char *host = NULL;
	
	/* Create a file, any file, in the PUBLIC directory */

	if (host == NULL)
		host = mail_host();
	cp = host == NULL ? "I" : host ;
	scratch = mail_alloc(strlen(PUBLICDIR)+strlen(cp)+3+1+10);

	sprintf(scratch, "%s/%7s:%d%%", PUBLICDIR,
		host == NULL ? "I" : host, getpid());

	if ((fp = _mail_fopen(&scratch)) == NULL) {
		eno = errno;
		fprintf(stderr,
			"mail_fopen(\"%s\", \"w+\"): errno %d\n",
			scratch, errno);
		mail_free(scratch);
		errno = eno;
		return NULL;
	}

	/* Determine a unique id associated with the file (inode number) */

	if (fstat(fileno(fp), &stbuf) < 0) {
		eno = errno;
		fprintf(stderr, "fstat(\"%s\"): errno %d\n", scratch, errno);
		mail_free(scratch);
		errno = eno;
		return NULL;
	}

	/* Rename the scratch file to the message file name based on the id */

#ifdef	notype
	message = mail_alloc(strlen(PUBLICDIR)+1+1+10);
	sprintf(message, "%s/%d%%", PUBLICDIR, stbuf.st_ino);
#else
	if (type == NULL)
		type = MSG_RFC822;
	message = mail_alloc(strlen(PUBLICDIR)+strlen(type)+1+1+10);
	sprintf(message, "%s/%d%%%s", PUBLICDIR, stbuf.st_ino, type);
#endif
	if (mail_link(scratch, &message) < 0) {
		eno = errno;
		fprintf(stderr, "mail_link(\"%s\", \"%s\"): errno %d\n",
				scratch, message, errno);
		mail_free(scratch);
		mail_free(message);
		errno = eno;
		return NULL;
	}
	unlink(scratch);
	mail_free(scratch);

	/* Housekeeping */

	if (mail_file == NULL) {
		/* getdtablesize() is defined in ../libc/detach.c on systems
		 * that don't have it. */
		int nfile;
		nfile = mail_nfiles = getdtablesize();	/* XX: assumed static */

		mail_file = (char**)mail_alloc((u_int)((sizeof (char*))*nfile));
		while (--nfile >= 0)
			mail_file[nfile] = NULL;
	}
	if ((int) fileno(fp) >= mail_nfiles)
		abort();
	mail_file[fileno(fp)] = message;

	/* Grab preferences from the environment to initialize the envelope */

#ifndef	notype
	if (type != NULL && *type != '\0')
		fprintf(fp, "type %s\n", type);
#endif
	if ((cp = getenv("FULLNAME")) != NULL)
		fprintf(fp, "fullname %s\n",
			fullname(cp, namebuf, sizeof namebuf, (char *)NULL));
	if ((cp = getenv("PRETTYLOGIN")) != NULL)
		fprintf(fp, "loginname %s\n", cp);
	/*
	 * If the postoffice lives elsewhere, put our hostname
	 * in the Received-from header, to aid in message tracing.
	 */
	if ((getzenv("MAILSERVER") != NULL
	|| ((host = whathost(message)) != NULL && strcmp(host,"localhost"))) &&
	    getmyhostname(namebuf, sizeof namebuf) == 0)
		fprintf(fp, "rcvdfrom %s\n", namebuf);
	return fp;
}

/*
 * Abort the message file composition on the indicated stream.
 */

int
mail_abort(fp)
	FILE *fp;
{
	register char **messagep, *message;
	int r;

	if (fp == NULL) {
		errno = EBADF;
		return -1;
	}
	if ((int) fileno(fp) >= mail_nfiles)
		abort();
	messagep = &mail_file[fileno(fp)];
	if (*messagep == NULL) {
		errno = ENOENT;
		return -1;
	}
	fclose(fp);
	message = *messagep;
	*messagep = NULL;
	r = unlink(message);
	mail_free(message);
	return r;
}

/*
 * Close the message file on the indicated stream and submit it to the mailer.
 */

int
mail_close(fp)
	FILE *fp;
{
	char **messagep, *message, *nmessage, *msgbase;
	char *routerdir;
	char *routerdirs, *s = NULL;
	int eno;

	if (postoffice == NULL) {
		fprintf(stderr, "mail_close: called out of order!\n");
		errno = EINVAL;
		return -1;
	}
	if (fp == NULL) {
		errno = EBADF;
		return -1;
	}
	if ((int) fileno(fp) >= mail_nfiles)
		abort();
	messagep = &mail_file[fileno(fp)];
	if (*messagep == NULL) {
		errno = ENOENT;
		return -1;
	}

	message = *messagep;
	*messagep = NULL;

	/*
	 * *** NFS users beware ***
	 * the fsync() between fflush() and fclose() may be mandatory
	 * on NFS mounted postoffices if you want to guarantee not losing
	 * data without being told about it.
	 */
	if (fflush(fp) == EOF
#ifdef	NFSFSYNC
	    || fsync(fileno(fp)) < 0
#endif	/* NFSFSYNC */
	    || fclose(fp) == EOF) {
		mail_free(message);
		errno = EIO;
		return -1;
	}


	/* Find the base name (we know format is PUBLICDIR/basename) */
	if ((msgbase = strrchr(message, '/')) == NULL)
		msgbase = message;
	else
		++msgbase;

	routerdir = ROUTERDIR;
	if (mail_priority) {
	  /* We are asked to place the mail somewhere else */
	  char *routerdirs = getzenv("ROUTERDIRS");
	  if (routerdirs) {
	    /* There are some defined!   A ":" separated list of strings */
	    int i = mail_priority;
	    char *rd = routerdirs;
	    while ((s = strchr(rd,':')) && --i)
	      rd = s+1;
	    /* mail_priority == 1 pics first, 2 pics second, ..
	       if segments run out, last one is kept at  rs     */
	    if (s) *s = 0;
	    routerdir = rd;
	  }
	}
	/* Assert postoffice != NULL */
	nmessage = mail_alloc(strlen(postoffice)+
				    strlen(routerdir)+strlen(msgbase)+2+1);
	sprintf(nmessage, "%s/%s/%s", postoffice, routerdir, msgbase);
	if (s) *s = ':';

	/*
	 * Unfortunately, rename() doesn't guarantee the same inode will
	 * be used if the two paths are on the same filesystem, so we do
	 * it the hard way.
	 */

	if (link(message, nmessage) < 0) {
		eno = errno;
		fprintf(stderr, "link(\"%s\", \"%s\"): errno %d\n",
				message, nmessage, errno);
		mail_free(message);
		mail_free(nmessage);
		errno = eno;
		return -1;
	}
	mail_free(nmessage);
	unlink(message);
	mail_free(message);
	return 0;
}
