/*
 * Copyright 1998-2002 Ben Smithurst <ben@smithurst.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Miscellaneous routines shared by multiple programs.
 */

static const char rcsid[] =
	"$BCPS: src/mailutils/misc.c,v 1.75 2003/01/19 19:18:25 ben Exp $";

#include "misc.h"

int quiet = -1;
volatile sig_atomic_t sig_count = 0;
char *curfile = NULL;
static int maildir_seq;

void
signal_handler(int sig) {
	if (++sig_count > 2)
		exit(2);
}

char *
print_mailbox(const char *name) {
	int maildir = is_maildir(name);
	int mlen = strlen(mailpath());
	int hlen = strlen(homedir());
	static char buf[1024];
	const char *pre, *suf;

	if (strncmp(name, mailpath(), mlen) == 0 && name[mlen] == '/') {
		pre = "=";
		name += mlen + 1;
	} else if (strncmp(name, homedir(), hlen) == 0 && name[hlen] == '/') {
		pre = "~";
		name += hlen;
	} else
		pre = "";
	if (maildir)
		suf = "/";
	else
		suf = "";
	snprintf(buf, sizeof buf, "%s%s%s", pre, name, suf);
	return (buf);
}

static char *
hostname(void) {
	static char host[MAXHOSTNAMELEN];
	if (host[0] == '\0') {
		if (gethostname(host, sizeof host - 1) < 0)
			return (NULL);
		host[sizeof host - 1] = '\0';
	}
	return (host);
}

#define GLT_MAX 10
#define GLT_BUF 1024
gl_getline_t glts[GLT_MAX];
int glcount = 0;

char *
gl_getline(FILE *fp) {
	gl_getline_t *glp;
	int i, n;

	for (i = 0; i < glcount; i++)
		if (glts[i].fp == fp || glts[i].fp == NULL)
			break;

	if (i == glcount || glts[i].fp == NULL) {
		if (glcount == GLT_MAX) {
			warnx("glcount==GLT_MAX");
			errno = ENOMEM;
			return (NULL);
		}
		glts[i].fp = fp;
		if (i == glcount) {
			if ((glts[i].buf = malloc(GLT_BUF)) == NULL) {
				warnx("can't malloc %d bytes for glt.buf", GLT_BUF);
				return (NULL);
			}
			glts[i].size = GLT_BUF;
			glts[i].eof = 0;
			glcount++;
		}
	}

	glp = &glts[i];
	if (glp->eof)
		return (NULL);

	n = 0;
	for (;;) {
		char *p;

		if (glp->size - n < GLT_BUF) {
			glp->size += GLT_BUF;
			if ((p = realloc(glp->buf, glp->size)) == NULL) {
				warnx("can't realloc glp->buf to %d",
				  glp->size);
				gl_destroy(glp->fp);
				return (NULL);
			} else
				glp->buf = p;
		}

		p = fgets(glp->buf + n, glp->size - n, glp->fp);
		if (p == NULL) {
			glp->eof = 1;
			if (n == 0)
				return (NULL);
			glp->buf[n] = '\n';
			glp->buf[n+1] = '\0';
			return (glp->buf);
		}

		n += strlen(p);
		if (glp->buf[n-1] == '\n')
			return (glp->buf);

	}
}

void
gl_destroy(FILE *fp) {
	int i;

	for (i = 0; i < glcount; i++)
		if (glts[i].fp == fp) {
			glts[i].fp = NULL;
			return;
		}
}

/*
 * This function would be much easier to write by just using a regex, but
 * that would probably require linking with the PCRE library. It's not hard
 * to do manually... honest.
 *
 * XXX this will fail on addresses with a space in them (e.g., "foo
 * bar"@domain.example.com).
 */
int
is_from(char *line, int strict) {
	/* this function attempts to check whether the specified line matches
	 * this regular expression:
	 *
	 * "^From\\s+(\\S+)\\s+(?:[a-zA-Z]{3},?\\s+)?"    / Common start
	 * "(?:"                                    / Non-extracting bracket
	 * "[a-zA-Z]{3}\\s+\\d?\\d|"                      / First form
	 * "\\d?\\d\\s+[a-zA-Z]{3}\\s+\\d\\d(?:\\d\\d)?"  / Second form
	 * ")"                                            / End alternation
	 * "\\s+\\d\\d?:\\d\\d?";                         / Start of time
	 */

	/* Check for From\s */
	if (strncmp(line, "From", 4) != 0 || !isspace(line[4]))
		return (0);
	else if (!strict)
		return (1);

	/* Now skip spaces, non-spaces (address), and more spaces. */
	line += 5;
	while (isspace(*line))
		line++;
	while (*line != '\0' && !isspace(*line))
		line++;
	while (isspace(*line))
		line++;
	if (*line == '\0')
		return (0);

	/* Check for three letters. This must be followed by either a comma
	 * or a space, and could either be the month name or the weekday
	 * name.
	 */
	if (isalpha(line[0]) && isalpha(line[1]) && isalpha(line[2])) {
		if (line[3] == ',')
			line += 4;
		else if (!isspace(line[3]))
			return (0);
		else
			line += 3;

		while (isspace(*line))
			line++;
		if (*line == '\0')
			return (0);

		/* we have matched three letters. They are either a weekday
		 * name or a month name. If the next character is another
		 * letter, it was a weekday name, and is followed by a
		 * month. (first form.) Otherwise, we have to do a bit more
		 * work. There should be a digit next, if not a letter,
		 * followed by some spaces. After the spaces, the next
		 * character will determine which of the two alternatives to
		 * take.
		 */
		if (isalpha(*line)) {
			if (!isalpha(line[1]) || !isalpha(line[2]))
				return (0);
			line += 3;
			while (isspace(*line))
				line++;
			if (!isdigit(*line++))
				return (0);
			if (isdigit(*line))
				line++;
		} else if (!isdigit(*line++))
			return (0);
		else {
			if (isdigit(*line))
				line++;
			if (!isspace(*line++))
				return (0);
			while (isspace(*line))
				line++;
			if (*line == '\0')
				return (0);

			if (isalpha(*line))
				goto second_form;
			else if (isdigit(*line++))
				/* was first form all along */
				goto near_end;
			else
				return (0);
		}
	} else {
		/* the second form */

		if (!isdigit(*line++))
			return (0);
		if (isdigit(*line))
			line++;
		while (isspace(*line))
			line++;

		second_form:
		if (!isalpha(line[0]) || !isalpha(line[1]) ||
		  !isalpha(line[2]))
			return (0);
		line += 3;
		while (isspace(*line))
			return (0);

		if (!isdigit(line[0]) || !isdigit(line[1]))
			return (0);
		line += 2;
		if (isdigit(line[0]) && isdigit(line[1]))
			line += 2;
	}

	while (isspace(*line))
		line++;
	if (!isdigit(*line++))
		return (0);

	near_end:

	if (isdigit(*line))
		line++;
	if (*line != ':')
		return (0);
	line++;
	if (!isdigit(*line))
		return (0);

	return (1);

}

static char *
maildir_nextfile(char *dir) {
	static int done_new;
	static DIR *dp;
	struct dirent *dep;
	static const char *subdir;
	static char file[MAXPATHLEN];

	if (dir == NULL) {
		done_new = 0;
		if (dp != NULL) {
			closedir(dp);
			dp = NULL;
		}
		return (NULL);
	}

	if (dp == NULL) {
		subdir = done_new ? "cur" : "new";
		if ((dp = opendir(subdir)) == NULL) {
			warn("opendir %s/%s", dir, subdir);
			return (NULL);
		}
	}

	while ((dep = readdir(dp)) != NULL)
		if (dep->d_name[0] != '.') {
			snprintf(file, sizeof file, "%s/%s", subdir, dep->d_name);
			return (file);
		}

	if (done_new)
		return (NULL);

	closedir(dp);
	dp = NULL;
	done_new = 1;
	return (maildir_nextfile(dir));
}

static int
process_maildir(char *file, int (*func)(int, char *, FILE *)) {
	int ok = 1, fd, reset = 1, ret;
	FILE *fp;
	char *fn;

	if ((fd = open(".", O_RDONLY)) < 0) {
		warn("open .");
		return (0);
	}
	if (chdir(file) != 0) {
		warn("chdir %s", file);
		return (0);
	}

	maildir_nextfile(NULL);
	while (ok && (fn = maildir_nextfile(file)) != NULL) {
		if (sig_count > 0)
			errx(1, "exiting because of signal");

		if ((fp = fopen(fn, "r")) == NULL) {
			warn("%s/%s", file, fn);
			ok = 0;
			break;
		}
		switch (ret = func(reset, fn, fp)) {
		case RET_ERROR:
			warn("maildir %s file %s", file, fn);
			/* FALLTHROUGH */
		case RET_LOCALERROR:
			ok = 0;
			break;
		case RET_NOCHANGE:
			/* nothing to do */
			break;
		case RET_DELETE:
			if (unlink(fn) != 0) {
				warn("unlink %s/%s", file, fn);
				ok = 0;
			}
			break;
		default:
			warnx("unknown return %d for %s/%s", ret, file, fn);
			ok = 0;
			break;
		}
		fclose(fp);
		reset = 0;
	}
	maildir_nextfile(NULL);

	if (fchdir(fd) != 0) {
		warn("fchdir");
		return (0);
	}

	return (1);
}

int
process(char **argv, int (*func)(FILE *, FILE *), int (*maildir_func)(int, char *, FILE *)) {
	int ok, i, fd, tmp;
	FILE *in_fp, *out_fp;
	char tmpname[MAXPATHLEN];
	struct stat sb;
	struct timeval times[2];

	ok = 1;
	times[0].tv_usec = times[1].tv_usec = 0;

	/* process from stdin to stdout if no arguments */
	if (argv[0] == NULL) {
		curfile = "stdin";

		switch (func(stdin, stdout)) {
		case RET_ERROR:
			warnx("%s", strerror(errno));
			/* FALLTHROUGH */
		case RET_LOCALERROR:
			return (0);
		default:
			return (1);
		}
	}

	for (i = 0; argv[i] != NULL; i++) {
		curfile = argv[i];

		if (is_maildir(curfile)) {
			if (maildir_func != NULL) {
				if (!process_maildir(curfile, maildir_func))
					ok = 0;
			} else {
				warnx("%s: is a Maildir (not implemented yet)", curfile);
				ok = 0;
			}
			continue;
		}

		if ((in_fp = fopen(argv[i], "r+")) == NULL) {
			warn("%s", argv[i]);
			ok = 0;
			continue;
		}
		fd = fileno(in_fp); /* some things need raw fd */

		snprintf(tmpname, sizeof tmpname, "%s.tmp.XXXXXXXX", argv[i]);
		if ((tmp = mkstemp(tmpname)) < 0) {
			warn("mkstemp: %s", tmpname);
			fclose(in_fp);
			ok = 0;
			continue;
		}

		/* attach temp file to FILE* */
		if ((out_fp = fdopen(tmp, "w")) == NULL) {
			warn("fdopen: %d (%s)", tmp, tmpname);
			fclose(in_fp);
			close(tmp);
			unlink(tmpname);
			ok = 0;
			continue;
		}

		if (!mailboxlock(argv[i], 0, fd, LF_GET)) {
			warn("mailboxlock: %s", argv[i]);
			fclose(in_fp);
			fclose(out_fp);
			unlink(tmpname);
			ok = 0;
			continue;
		}

		if (sig_count > 0) {
			unlink(tmpname);
			mailboxlock(argv[i], 0, -1, LF_REL);
			errx(1, "exiting because of signal");
		}

		/*
		 * get the current atime/mtime, to put back if we change the
		 * file.
		 */
		if (fstat(fd, &sb) < 0) {
			warn("fstat: %d (%s)", fd, argv[i]);
			fclose(in_fp);
			fclose(out_fp);
			unlink(tmpname);
			mailboxlock(argv[i], 0, -1, LF_REL);
			ok = 0;
			continue;
		}

		switch (func(in_fp, out_fp)) {
		case RET_ERROR:
			warn("%s", argv[i]);
			/* FALLTHROUGH */
		case RET_LOCALERROR:
			ok = 0;
			/* FALLTHROUGH */
		case RET_NOCHANGE:
			fclose(in_fp);
			fclose(out_fp);
			mailboxlock(argv[i], 0, -1, LF_REL);
			unlink(tmpname);
			break;
		case RET_CHANGE:
			if (fclose(out_fp) != 0) {
				warn("fclose %s", tmpname);
				ok = 0;
			}
			fclose(in_fp);
			/*
			 * at this stage, the fcntl lock is no
			 * longer in effect, but the .lock file is,
			 * or should be.
			 */
			if (rename(tmpname, argv[i]) < 0) {
				warn("rename: %s %s", tmpname, argv[i]);
				ok = 0;
			}
			times[0].tv_sec = sb.st_atime;
			times[1].tv_sec = sb.st_mtime;

			if (utimes(argv[i], times) < 0)
				warn("utimes: %s", argv[i]);

			mailboxlock(argv[i], 0, -1, LF_REL);
			break;
		}

		if (sig_count > 0)
			errx(1, "exiting because of signal");
	}

	return (ok);
}

char *
inbox_check(char *file) {
	if (strcmp(file, "INBOX") != 0)
		return (file);
	else
		return (user_inbox());
}

/*
 * Get user's inbox.
 */
char *
user_inbox(void) {
	char *e;
	struct passwd *pw;
	static char buf[MAXPATHLEN];

	if ((e = getenv("MAIL")) != NULL)
		return (e);

	if ((pw = getpwuid(getuid())) == NULL)
		return (NULL);

	if (snprintf(buf, sizeof buf, "%s/%s",
	  _PATH_MAILDIR, pw->pw_name) >= sizeof buf)
		return (NULL);
	else
		return (buf);
}

/*
 * Get the user's home directory.
 */
char *
homedir(void) {
	struct passwd *pw;
	char *home;
	static char buf[MAXPATHLEN];

	if (buf[0] == '\0') {
		if ((home = getenv("HOME")) != NULL)
			strlcpy(buf, home, sizeof buf);
		else if ((pw = getpwuid(getuid())) != NULL)
			strlcpy(buf, pw->pw_dir, sizeof buf);
		else
			errx(1, "couldn't find your home directory");
	}
	return (buf);
}

/*
 * Get user's mail directory: checks for ~/mail and ~/Mail.
 */
char *
mailpath(void) {
	char *mail;
	char *env;
	int len, i;
	struct stat sb;
	const char *home = homedir();
	static char buf[MAXPATHLEN];

	if (buf[0] != '\0')
		return (buf);

	if ((env = getenv("MAILUTILS_DIR")) != NULL) {
		strlcpy(buf, env, sizeof buf);
		return (buf);
	}

	len = strlen(home);
	mail = malloc(len + sizeof "/mail");
	if (mail == NULL)
		errx(1, "malloc for mailpath failed");

	for (i = 0; i < 2; i++) {
		/* build the path, space will be replaced with 'm' or 'M' */
		strcpy(mail, home);
		strcat(mail, "/ ail"); 

		if (i == 0)
			mail[len + 1] = 'm';
		else
			mail[len + 1] = 'M';


		if (stat(mail, &sb) == 0 && S_ISDIR(sb.st_mode)) {
			strlcpy(buf, mail, sizeof buf);
			return (buf);
		}
	}

	errx(1, "couldn't find your mail directory");
}

unsigned int
str_to_int(char *p) {
	unsigned int ret;
	char *end;

	ret = (unsigned int)strtol(p, &end, 0);

	/* Cope with the G, M and K suffixes. */
	switch (*end) {
	case 'g':
	case 'G':
		if (ret > UINT_MAX / 1024)
			return (UINT_MAX);
		ret *= 1024;
		/* FALLTHROUGH */
	case 'm':
	case 'M':
		if (ret > UINT_MAX / 1024)
			return (UINT_MAX);
		ret *= 1024;
		/* FALLTHROUGH */
	case 'k':
	case 'K':
		if (ret > UINT_MAX / 1024)
			return (UINT_MAX);
		ret *= 1024;
		end++;
		break;
	case '\0':
		break;
	default:
		warnx("str_to_int: ignoring tailing garbage `%s'", end);
	}

	return (ret);
}

void
free_tree(TREE *p) {
	if (p == NULL)
		return;
	free_tree(p->left);
	free_tree(p->right);
	free(p->file);
	free(p);
}

void
check_tree(TREE *tp, void (*check_file)(char *)) {
	if (sig_count > 0 || tp == NULL)
		return;

	check_tree(tp->left, check_file);
	if (sig_count > 0)
		return;
	check_file(tp->file);
	check_tree(tp->right, check_file);
}

/* add string to a tree */
void
add_to_tree(TREE **tp, char *str) {
	if (tp == NULL)
		errx(1, "tp == NULL in add_to_tree()");

	if (*tp == NULL) {
		MALLOC(*tp, sizeof **tp);
		MALLOC((*tp)->file, strlen(str) + 1);

		strcpy((*tp)->file, str);
		(*tp)->left = (*tp)->right = NULL;

		return;
	}

	if (strcmp((*tp)->file, str) < 0)
		add_to_tree(&(*tp)->right, str);
	else
		add_to_tree(&(*tp)->left, str);

	return;
}

/*
 * convert an array (e.g. from argv) to the binary tree
 * format needed.
 */
void
array_to_tree(TREE **tp, char **array) {
	while (*array) {
		add_to_tree(tp, *array);
		array++;
	}
}

/*
 * walk through the directory tree starting at dir, adding all
 * regular files to the specified tree.
 */
void
files_to_tree(TREE **tp, const char *dir) {
	DIR *dirp;
	struct dirent *dp;
	char buf[MAXPATHLEN];
	int len;

	if (sig_count > 0)
		return;

	if ((dirp = opendir(dir)) == NULL)
		err(1, "%s", dir);

	while ((dp = readdir(dirp)) != NULL) {
		struct stat sb;

		/* skip files starting with a dot */
		if (dp->d_name[0] == '.')
			continue;

		/* skip *.gz, *.lock, *.lock.* */
		len = strlen(dp->d_name);
		if ((len >= 3 && strcmp(dp->d_name + len - 3, ".gz") == 0) ||
		    (len >= 5 && strcmp(dp->d_name + len - 5, ".lock") == 0) ||
		    strstr(dp->d_name, ".lock.") != NULL)
			continue;

		snprintf(buf, sizeof buf, "%s/%s", dir, dp->d_name);
		if (stat(buf, &sb) != 0) {
			warn("stat of %s failed", buf);
			continue;
		}

		if (is_maildir(buf) || S_ISREG(sb.st_mode))
			add_to_tree(tp, buf);
		else if (S_ISDIR(sb.st_mode)) {
			if (strcmp(dp->d_name, "archive") == 0)
				continue;

			files_to_tree(tp, buf);
		} else
			warnx("%s not regular or directory (%#o), skipping",
			  dp->d_name, sb.st_mode);
	}

	closedir(dirp);
}

int
getlock(int fd, int type) {
	struct flock fl;

	fl.l_start = 0;
	fl.l_whence = SEEK_SET;
	fl.l_len = 0;
	fl.l_pid = getpid();
	fl.l_type = type;

	return (fcntl(fd, F_SETLK, &fl));
}

int
mailboxlock(char *name, int maildir, int fd, int op) {
	int tries;
	int delay;
	int tmpfd;
	char hitchpost[MAXPATHLEN], lockfile[MAXPATHLEN];
	char newfile[MAXPATHLEN], *s;
	struct stat sb;
	time_t now;
	int gotlock = 0;
	int soft = 0;

	if (maildir) {
		/* kludge, so that releasing a lock on a /tmp/ file in a maildir
		 * will move it to /new/.  This is used in conjunction with
		 * open_mailbox really.
		 */
		if (fd != -1 || op != LF_REL) {
			errno = EINVAL;
			warnx("mailboxlock(%s, %d, %d, %d) called", name, maildir, fd, op);
			return (0);
		}

		/* check for "/tmp/" */
		if (strlen(name) >= sizeof newfile ||
		  (s = strrchr(name, '/')) == NULL || s < name + 4 ||
		  s[-4] != '/' || s[-3] != 't' || s[-2] != 'm' || s[-1] != 'p') {
			errno = EINVAL;
			warnx("mailboxlock: %s: bad filename", name);
			return (0);
		}

		/* copy, changing "tmp" to "new" */
		strcpy(newfile, name);
		memcpy(newfile + (s - name) - 3, "new", 3);
		return (rename(name, newfile) == 0);
	}

	if (op == LF_GET_SOFT) {
		op = LF_GET;
		soft = 1;
	}

	if (name) {
		snprintf(hitchpost, sizeof hitchpost,
		  "%s.lock.%s.%lx.%lx.XXXXXXXX", name, hostname(),
		  (unsigned long)getpid(), (unsigned long)time(NULL));
		snprintf(lockfile, sizeof lockfile, "%s.lock", name);
	}

	if (op == LF_REL) {
		if (fd >= 0)
			getlock(fd, F_UNLCK);

		if (name)
			unlink(lockfile);
		return (1);
	}

	if (fd < 0) {
		errno = EBADF;
		return (0);
	}

	delay = 200000;
	for (tries = 0; tries < 10; tries++) {
		if (getlock(fd, F_WRLCK) != -1) {
			gotlock = 1;
			break;
		}
		if (errno != EAGAIN)
			break;

		if (sig_count > 0)
			return (0);

		if (tries == 4)
			delay = 2000000;

		usleep(delay);
	}

	if (name) {
		/* create a hitching post */
		if ((tmpfd = mkstemp(hitchpost)) < 0)
			return (gotlock);
		close(tmpfd);

		/* get current time */
		if (time(&now) < 0) {
			unlink(hitchpost);
			return (gotlock);
		}

		/* check for stale lock -- race condition here? XXX */
		if (stat(lockfile, &sb) == 0 && now - sb.st_ctime > 1800)
			unlink(lockfile);

		/* attempt a link to the real lockfile */
		tries = 0;
		delay = 200000;
		while (link(hitchpost, lockfile) < 0) {
			if (tries == 4)
				delay = 2000000;

			if (sig_count > 0 || ++tries == 10 ||
			  errno != EEXIST)
			{
				unlink(hitchpost);
				return (gotlock);
			}

			/*
			 * check hitchpost link count, the link() may have
			 * succeeded after all, if the link count == 2.
			 */
			if (stat(hitchpost, &sb) < 0) {
				/* shouldn't happen */
				unlink(hitchpost);
				return (gotlock);
			}

			if (sb.st_nlink == 2)
				break;

			usleep(delay);
		}

		/* remove hitching post */
		unlink(hitchpost);
	}

	/* success */
	return (gotlock);
}

FILE *
open_mailbox(char *file, char **ret, int *maildir) {
	int fd, sverrno;
	FILE *fp;
	char buf[MAXPATHLEN];

	file = inbox_check(file);
	if (file == NULL)
		return (NULL);

	*maildir = (is_maildir(file));
	if (*maildir) {
		snprintf(buf, sizeof buf, "%s/tmp/%lu.%d_%d.%s",
		  file, (unsigned long)time(NULL), (int)getpid(),
		  maildir_seq++, hostname());
		if ((fd = open(buf, O_CREAT|O_EXCL|O_WRONLY, 0600)) < 0)
			return (NULL);
		if ((*ret = strdup(buf)) == NULL) {
			sverrno = errno;
			close(fd);
			unlink(buf);
			errno = sverrno;
			return (NULL);
		}

		if ((fp = fdopen(fd, "w")) == NULL) {
			sverrno = errno;
			close(fd);
			unlink(buf);
			free(*ret);
			errno = sverrno;
			return (NULL);
		}

		return (fp);
	}

	fd = open(file, O_WRONLY|O_CREAT, 0600);
	if (fd < 0)
		return (NULL);

	if (!mailboxlock(file, 0, fd, LF_GET)) {
		sverrno = errno;
		close(fd);
		errno = sverrno;
		return (NULL);
	}

	*ret = malloc(strlen(file) + 1);
	if (*ret == NULL) {
		sverrno = errno;
		close(fd);
		errno = sverrno;
		return (NULL);
	} else
		strcpy(*ret, file);

	fp = fdopen(fd, "w");
	if (fp == NULL) {
		sverrno = errno;
		free(*ret);
		close(fd);
		errno = sverrno;
		return (NULL);
	}

	if (fseek(fp, 0, SEEK_END) < 0) {
		sverrno = errno;
		free(*ret);
		fclose(fp);
		errno = sverrno;
		return (NULL);
	}

	return (fp);
}

static int
_checkdir(const char *d, const char *e) {
	char buf[MAXPATHLEN];
	struct stat sb;

	snprintf(buf, sizeof buf, "%s/%s", d, e);
	return (stat(buf, &sb) == 0 && S_ISDIR(sb.st_mode));
}

int
is_maildir(const char *d) {

	return (_checkdir(d, "new") && _checkdir(d, "tmp") && _checkdir(d, "cur"));
}

static int
_dirsize(const char *d, const char *e) {
	char dirbuf[MAXPATHLEN], filebuf[MAXPATHLEN];
	DIR *dp;
	struct dirent *dep;
	struct stat sb;
	int total = 0;

	snprintf(dirbuf, sizeof dirbuf, "%s/%s", d, e);
	if ((dp = opendir(dirbuf)) == NULL)
		return (-1);
	while ((dep = readdir(dp)) != NULL) {
		snprintf(filebuf, sizeof filebuf, "%s/%s", dirbuf, dep->d_name);
		if (stat(filebuf, &sb) != 0) {
			closedir(dp);
			return (-1);
		} else if (S_ISREG(sb.st_mode))
			total += (int)sb.st_size;
	}
	closedir(dp);
	return (total);
}

int
mailbox_size(const char *d, int *num) {
	struct stat sb;
	int n, c;

	if (!is_maildir(d)) {
		if (stat(d, &sb) != 0)
			return (-1);
		else
			return ((int)sb.st_size);
	}

	if ((n = _dirsize(d, "new")) < 0)
		return (-1);
	if ((c = _dirsize(d, "cur")) < 0)
		return (-1);
	return (n + c);
}

void
xerr(int code, char *fmt, ...) {
	va_list ap;

	if (quiet)
		exit(EX_TEMPFAIL);

	va_start(ap, fmt);
	verr(code, fmt, ap);
}

void
xerrx(int code, char *fmt, ...) {
	va_list ap;

	if (quiet)
		exit(EX_TEMPFAIL);

	va_start(ap, fmt);
	verrx(code, fmt, ap);
}

void
xfprintf(FILE *fp, char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	if (vfprintf(fp, fmt, ap) < 0)
		xerr(1, "vfprintf");
	va_end(ap);
}


syntax highlighted by Code2HTML, v. 0.9.1