/*
 * 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.
 */

/*
 * check for unread mail in all mailboxes under ~/mail
 */

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

#include "misc.h"

void usage(void);
void check_file(char *);

#define SIG_EXIT() {				\
	warnx("exiting because of signal");	\
	exit(1);				\
	}

int hlen, mlen, total_newmail, some_newmail;
char *home, *mail, *inbox;
int batch, count, thorough, mailbox_fd;

int
main(int argc, char *argv[]) {
	int ch;
	TREE *tree;
	struct passwd *pw;
	char *mail_env;

	batch = count = thorough = 0;
	some_newmail = total_newmail = 0;
	mailbox_fd = -1;

	sig_count = 0;
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);

	while ((ch = getopt(argc, argv, "cf:st")) != -1)
		switch (ch) {
		case 'c':
			count = 1;
			if (thorough < 1)
				thorough = 1;
			break;
		case 'f':
			if (thorough < 2)
				thorough = 2;
			mailbox_fd = atoi(optarg);
			if (mailbox_fd < 0)
				errx(1, "Bad fd for -f option");
			break;
		case 's':
			batch = 1;
			break;
		case 't':
			thorough++;
			break;
		default:
			usage();
		}

	argc -= optind;
	argv += optind;

	/* -f option mustn't have real files specified as well */
	if (mailbox_fd >= 0 && argc != 0)
		errx(1, "must specify no files with -f option");

	/* -s option needs exactly one file to work on */
	if (batch && argc != 1 && mailbox_fd < 0)
		errx(1, "must specify exactly one file for -s option");

	tree = NULL;

	/* find mail directory */
	home = homedir();
	mail = mailpath();

	hlen = strlen(home);
	mlen = strlen(mail);

	/* check for this case early */
	if (mailbox_fd >= 0) {
		check_file("user supplied fd");
		return (0);
	}

	/* get inbox filename */
	if ((mail_env = getenv("MAIL")) != NULL) {
		MALLOC(inbox, strlen(mail_env) + 1);
		strcpy(inbox, mail_env);
	} else {
		if ((pw = getpwuid(getuid())) == NULL)
			err(1, "couldn't get username");
		MALLOC(inbox, sizeof _PATH_MAILDIR + strlen(pw->pw_name) + 1);
		strcpy(inbox, _PATH_MAILDIR);
		strcat(inbox, "/");
		strcat(inbox, pw->pw_name);
	}

	if (argc == 0)
		files_to_tree(&tree, mail);
	else if (argc == 1) {
		tree = NULL;
		check_file(argv[0]);
	} else
		array_to_tree(&tree, argv);

	check_tree(tree, check_file);

	if (sig_count > 0)
		SIG_EXIT();

	/*
	 * check inbox, if it wasn't checked earlier, and if no files
	 * on command line.
	 */
	if (argc == 0 && inbox[0] != '\0')
		check_file(inbox);

	if (count && total_newmail > 0)
		printf("\nOverall, %d new message%s\n", total_newmail,
		  (total_newmail == 1) ? "" : "s");

	return (0);
}

static void
check_file0(char *file) {
	FILE *fp;
	char *line;
	int statusfound, newmail, header, maildir = 0;
	size_t len;
	struct stat sb;
	struct timeval times[2];

	/* usec for atime/mtime always zero */
	times[0].tv_usec = times[1].tv_usec = 0;

	newmail = 0;
	maildir = (mailbox_fd < 0) && is_maildir(file);

	if (maildir) {
		char buf[MAXPATHLEN];
		DIR *d;
		struct dirent *dp;

		snprintf(buf, sizeof buf, "%s/new", file);
		if ((d = opendir(buf)) == NULL) {
			warn("%s", buf);
			return;
		}
		while ((dp = readdir(d)) != NULL) {
			if (dp->d_name[0] != '.') {
				newmail++;
				if (!count)
					break;
			}
		}
		total_newmail += newmail;
		closedir(d);

		/* check for bad files */
		if (thorough > 0) {
			char *n;
			const char *ok[] = { ".", "..", "cur", "new", "tmp" };
			const int nok = sizeof(ok) / sizeof(ok[0]);
			int i;
			if ((d = opendir(file)) == NULL) {
				warn("%s", file);
				return;
			}
			while ((dp = readdir(d)) != NULL) {
				n = dp->d_name;
				for (i = 0; i < nok; i++)
					if (strcmp(n, ok[i]) == 0)
						break;
				if (i >= nok)
					warnx("%s: contains bad file %s", file, n);
			}
			closedir(d);
		}
	} else if (mailbox_fd < 0) {
		/* stuff not done if user gives -f option */
		if (stat(file, &sb) < 0) {
			warn("%s", file);
			return;
		}

		/* exit quickly if zero size */
		if (sb.st_size == 0) {
			if (batch)
				exit(2);
			else
				return;
		}

		/* ignore ~/mail/sent-mail */
		if (strlen(file) == mlen + 10 &&
		  strncmp(file, mail, mlen) == 0 &&
		  strcmp(file + mlen, "/sent-mail") == 0)
				return;

		if (!S_ISREG(sb.st_mode)) {
			warnx("%s not a regular file", file);
			return;
		}

		if (thorough < 2)
			newmail = sb.st_mtime > sb.st_atime;
	}

	if (!maildir && (thorough >= 2 || (!newmail && thorough == 1) || (newmail && count))) {
		if (count)
			newmail = 0;

		if (mailbox_fd >= 0) {
			if ((fp = fdopen(mailbox_fd, "r+")) == NULL) {
				warn("fdopen: %d", mailbox_fd);
				return;
			}
		} else if ((fp = fopen(file, "r+")) == NULL) {
			warn("%s", file);
			return;
		}

		/* lock the file to prevent updates while checking */
		if (mailbox_fd < 0 &&
		  !mailboxlock(file, 0, fileno(fp), LF_GET)) {
			warn("mailboxlock: %s", file);
			fclose(fp);
			return;
		}

		header = 1;
		statusfound = 0;

		while ((line = gl_getline(fp)) != NULL) {
			len = strlen(line);
			if (sig_count > 0)
				break;

			chop(line, len);

			if (!header && is_from(line, 1)) {
				header = 1;
				statusfound = 0;
			} else if (header &&
			  strncasecmp(line, "Status:", 7) == 0 &&
			  strchr(line + 7, 'R') != NULL)
				statusfound = 1;
			else if (header && *line == '\0') {
				header = 0;
				if (!statusfound)
					newmail++;
			}

			if (newmail && !count)
				break;
		}

		total_newmail += newmail;

		gl_destroy(fp);
		fclose(fp);

		if (mailbox_fd < 0) {
			times[1].tv_sec = sb.st_mtime;

			/* if there's new mail, but mtime <= atime, fudge it */
			if (newmail && sb.st_mtime <= sb.st_atime)
				times[0].tv_sec = sb.st_mtime - 1;
			else if (!newmail && sb.st_mtime >= sb.st_atime)
				times[0].tv_sec = sb.st_mtime + 1;
			else
				times[0].tv_sec = sb.st_atime;

			if (utimes(file, times) < 0)
				warn("utimes: %s", file);

			mailboxlock(file, 0, -1, LF_REL);
		}

		if (sig_count > 0)
			SIG_EXIT();
	}

	if (newmail) {
		if (!batch && !some_newmail)
			printf("You have new mail in:\n");
		some_newmail = 1;
		if (batch)
			exit(0);
		printf(" %s", print_mailbox(file));

		if (count)
			printf(" (%d)", newmail);
		printf("\n");
	} else if (batch)
		exit(2);
}

void check_file(char *file) {

	check_file0(file);

	/*
	 * if we check the user's inbox here, reset the inbox string so
	 * it isn't checked again.
	 */
	if (strcmp(file, inbox) == 0)
		inbox[0] = '\0';
}

void
usage(void) {
	fprintf(stderr, "usage: newmail [-f fd] [-cst] [mailbox ...]\n");
	exit(EX_USAGE);
}


syntax highlighted by Code2HTML, v. 0.9.1