/* 
 * $Id: pfrom.c,v 1.8 1995/01/22 23:03:37 svalente Exp $
 * $Source: /mit/sipb/src/src/pfrom/RCS/pfrom.c,v $
 * $Author: svalente $
 *
 * This is the main source file for a KPOP version of the from command. 
 * It was written by Theodore Y. Ts'o, MIT Project Athena.
 * And later modified by Jerry Larivee, MIT Project Athena,
 * to support both KPOP and the old UCB from functionality.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <pwd.h>
#ifndef TIOCGWINSZ
#include <termios.h>
#endif
#if defined(linux) || defined(_AIX) || defined(__NetBSD__)
#include <paths.h>
#endif
#include "popmail.h"

static char version_string[] = "pfrom v2.0";

int main(int argc, char *argv[]);
int parse_args(int argc, char *argv[]);
void lusage(int exit_status);
int getmail_pop(char *user, char *host, int printhdr);
void header_scan(char *line);
int pop_scan(int msgno, void (*action)());
int list_compare(char *s, char **list);
void make_lower_case(char *s);
char *parse_from_field(char *str);
char *skip_comment(char *cp);
void print_report(char **headers, int num_headers, int winlength);
int getmail_unix(char *user);
int match (char *line, char *str);

/* this should be declared in <stdlib.h> */
extern int optind;
extern char *optarg;

#define MAX_HEADER_LINES 512

FILE 	*sfi, *sfo;
char 	Errmsg[80];

int	popmail_debug, verbose, unixmail, popmail, report, totals;
int	exuser, nonomail;
char	*progname, *sender, *user, *host;

char	*headers[MAX_HEADER_LINES];
int	num_headers, skip_message, last_header;

char *Short_List[] = {
	"from", NULL
};

char *Report_List[] = {
        "from", "subject", NULL
};

char *Verbose_List[] = {
	"to", "from", "subject", "date", NULL
};

	
int main(int argc, char *argv[])
{
	int locmail = -1, remmail = -1;

	parse_args(argc,argv);
	if (unixmail)
		locmail = getmail_unix(user);
	if (popmail)
		remmail = getmail_pop(user, host, unixmail && locmail);
	if (!nonomail && (report || verbose || totals) &&
	    (unixmail == 0 || locmail == 0) &&
	    (popmail == 0 || remmail == 0)) {
		if (exuser)
			printf ("%s doesn't have any mail waiting.\n", user);
		else
			puts("You don't have any mail waiting.");
	}
	return ((unixmail && locmail < 0) || (popmail && remmail < 0));
}

int parse_args(int argc, char *argv[])
{
	register struct passwd *pp;
	char *cp;
	int c;

	cp = strrchr(argv[0], '/');
	if (cp)
		progname = cp + 1;
	else
		progname = argv[0];
	verbose = popmail_debug = 0;
	host = user = sender = NULL;
	unixmail = 1;
	popmail = 1;
	
	optind = 1;
	while ((c = getopt(argc,argv,"rvdnpuats:h:UV")) != EOF)
		switch(c) {
		case 'r':
			/* report on no mail */
		        report = 1;
			break;
		case 'n':
			nonomail = 1;
			break;
		case 'v':
		        /* verbose mode */
			verbose++;
			report = 0;
			break;
		case 'd':
			/* debug mode */
			popmail_debug++;
			break;
		case 'p':
			/* check pop only */
			popmail = 1;
			unixmail = 0;
			break;
		case 'u':
			/* check unix mail only */
			popmail = 0;
			unixmail = 1;
			break;
		case 'a':
			/* check all */
			popmail = 1;
			unixmail = 1;
			break;
		case 's':
			/* check for mail from sender only */
			if (! optarg)
				lusage(1);
			sender = optarg;
			make_lower_case(sender);
			break;
		case 'h':
			/* specify pobox host */
			host = optarg;
			break;
		case 't':
			/* print total messages only */
			totals = 1;
			report = 0;
			verbose = 0;
			break;
		case 'V':
			printf ("%s\n", version_string);
			exit (0);
		case 'U':
			lusage(0);
		default:
			lusage(1);
		}
	/* check mail for user */
	if (optind < argc) {
		exuser = 1;
		user = argv[optind];
	} else {
		user = getenv ("USER");
		if (user == NULL)
			user = getlogin();
		if (!user) {
			pp = getpwuid(getuid());
			if (pp == NULL) {
				fprintf (stderr, "%s: user not in "
					 "password file\n", progname);
				exit(1);
			}
			user = pp->pw_name;
		}
	}
	if (popmail) {
		if (!host)
			host = pop_get_mailhost (user);
		if (!host) {
			if (exuser)
				fprintf(stderr, "%s: can't find post "
					"office server for user %s.\n",
					progname, user);
			else
				fprintf(stderr, "%s: can't find post "
					"office server.\n", progname);
			popmail = 0;
			return(1);
		}
	}
	return 0;
}

void lusage(int exit_status)
{
	fprintf(stderr, "Usage: %s [-v | -r | -t] [-p | -u] [-s sender] "
		"[-h host] [user]\n", progname);
	exit(exit_status);
}


int getmail_pop(char *user, char *host, int printhdr)
{
	int nmsgs, nbytes, linelength;
	char response[128];
	register int i, j;
	struct winsize windowsize;

	if (pop_init(host) == NOTOK) {
		fprintf(stderr, "pop: %s\n", Errmsg);
		return(1);
	}

	if ((getline(response, sizeof(response), sfi) != OK) ||
	    (*response != '+')){
		fprintf(stderr, "pop: %s\n", response);
		return -1;
	}

#ifdef KPOP
	if (pop_command("USER %s", user) == NOTOK || 
	    pop_command("PASS %s", user) == NOTOK)
#else
	if (pop_command("USER %s", user) == NOTOK || 
	    pop_command("RPOP %s", user) == NOTOK)
#endif
	{
		fprintf(stderr, "pop: %s\n", Errmsg);
		pop_command("QUIT");
		return -1;
	}

	if (pop_stat(&nmsgs, &nbytes) == NOTOK) {
		fprintf(stderr, "pop: %s\n", Errmsg);
		pop_command("QUIT");
		return -1;
	}
	if (nmsgs == 0) {
		pop_command("QUIT");
		return(0);
	}
	if (verbose || totals)
		printf("You have %d %s (%d bytes) on %s%c\n",
		       nmsgs, nmsgs > 1 ? "messages" : "message",
		       nbytes, host, verbose ? ':' : '.');
	if (totals) {
		pop_command("QUIT");
		return nmsgs;
	}
	if (printhdr)
		puts("POP mail:");

	/* find out how long the line is for the stdout */
	if ((ioctl(1, TIOCGWINSZ, (void *)&windowsize) < 0) || 
	    (windowsize.ws_col == 0))
		windowsize.ws_col = 80;		/* default assume 80 */
	/* for the console window timestamp */
	linelength = windowsize.ws_col - 4;
	if (linelength < 32)
		linelength = 32;
	
	for (i = 1; i <= nmsgs; i++) {
		if (verbose && !skip_message)
			putchar('\n');
		num_headers = skip_message = last_header = 0;
		if (pop_scan(i, header_scan) == NOTOK) {
			fprintf(stderr, "pop: %s\n", Errmsg);
			pop_command("QUIT");
			return -1;
		}
		if (!skip_message)
			if (report)
				print_report(headers, num_headers, linelength);
			else
				for (j=0; j<num_headers; j++)
					puts(headers[j]);
		for (j=0; j<num_headers; j++)
			free(headers[j]);
	}
	
	pop_command("QUIT");
	return nmsgs;
}

void header_scan(char *line)
{
	char	*keyword, **search_list, *mail_from;
	register int	i;
	
	if (last_header && isspace(*line)) {
		headers[num_headers++] = xstrdup(line);
		return;
	}

	for (i=0;line[i] && line[i]!=':';i++) ;
	keyword = xmalloc(i+1);
	strncpy(keyword,line,i);
	keyword[i]='\0';
	make_lower_case(keyword);
	if (sender && !strcmp(keyword,"from")) {
		mail_from = parse_from_field(line+i+1);
		make_lower_case(mail_from);
		if (strcmp(sender, mail_from))
			skip_message++;
		free (mail_from);
	}
	if (verbose)
		search_list = Verbose_List;
	else if (report)
		search_list = Report_List;
	else
		search_list = Short_List;
	if (list_compare(keyword, search_list)) {
		last_header = 1;
		headers[num_headers++] = xstrdup(line);
	} else {
		last_header = 0;	
	}
	free(keyword);
}

int pop_scan(int msgno, void (*action)())
{	
	char buf[4096];
	int	headers = 1, status;

#ifdef HAVE_POP3_TOP
	status = pop_command("TOP %d 0", msgno);
#else
 	status = pop_command("RETR %d", msgno);
#endif
	if (status == NOTOK)
		return (NOTOK);

	while (headers) {
		switch (multiline(buf, sizeof(buf), sfi)) {
		case OK:
			if (!*buf)
				headers = 0;
			(*action)(buf);
			break;
		case DONE:
			return(OK);
		case NOTOK:
			return(NOTOK);
		}
	}
	while (1) {
		switch (multiline(buf, sizeof(buf), sfi)) {
		case OK:
			break;
		case DONE:
			return(OK);
		case NOTOK:
			return(NOTOK);
		}
	}
}

int list_compare(char *s, char **list)
{
	for (; *list; list++)
		if (! strcmp(s, *list))
			return(1);
	return(0);
}

void make_lower_case(char *s)
{
	for (; *s; s++)
		if (isupper (*s)) *s = tolower (*s);
}

/*
 * Skin an arpa net address according to the RFC 822 interpretation
 * of "host-phrase."
 */
char *
parse_from_field(name)
	char *name;
{
	register int c;
	register char *cp, *cp2;
	char *bufend;
	int gotlt, lastsp;
	char nbuf[1024];

	if (name == NULL)
		return(NULL);
	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
	    && strchr(name, ' ') == NULL)
		return(xstrdup(name));
	gotlt = 0;
	lastsp = 0;
	bufend = nbuf;
	for (cp = name, cp2 = bufend; *cp; cp++) {
		c = *cp;
		switch (c) {
		case '(':
			cp = skip_comment(cp);
			lastsp = 0;
			break;

		case '"':
			/*
			 * Start of a "quoted-string".
			 * Copy it in its entirety.
			 */
			while (*cp) {
				c = *cp++;
				if (c == '"')
					break;
				if (c != '\\')
					*cp2++ = c;
				else if (*cp) {
					c = *cp;
					*cp2++ = c;
					cp++;
				}
			}
			lastsp = 0;
			break;

		case ' ':
			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
				cp += 3, *cp2++ = '@';
			else
			if (cp[0] == '@' && cp[1] == ' ')
				cp += 2, *cp2++ = '@';
			else
				lastsp = 1;
			break;

		case '<':
			cp2 = bufend;
			gotlt++;
			lastsp = 0;
			break;

		case '>':
			if (gotlt) {
				gotlt = 0;
				while ((c = *cp) && c != ',') {
					cp++;
					if (c == '(')
						cp = skip_comment(cp);
					else if (c == '"')
						while (*cp) {
							c = *cp++;
							if (c == '"')
								break;
							if (c == '\\' && *cp)
								cp++;
						}
				}
				lastsp = 0;
				break;
			}
			/* Fall into . . . */

		default:
			if (lastsp) {
				lastsp = 0;
				/* usernames don't start with a space */
				if (cp2 > nbuf)
					*cp2++ = ' ';
			}
			*cp2++ = c;
			if (c == ',' && !gotlt) {
				*cp2++ = ' ';
				for (; *cp == ' '; cp++)
					;
				lastsp = 0;
				bufend = cp2;
			}
		}
	}
	*cp2 = 0;

	return(xstrdup(nbuf));
}

/*
 * Start of a "comment".
 * Ignore it.
 */
char *
skip_comment(cp)
	register char *cp;
{
	register nesting = 1;

	for (; nesting > 0 && *cp; cp++) {
		switch (*cp) {
		case '\\':
			if (cp[1])
				cp++;
			break;
		case '(':
			nesting++;
			break;
		case ')':
			nesting--;
			break;
		}
	}
	return cp;
}

void print_report(char **headers, int num_headers, int winlength)
{
	int j, len;
	char *p, *from_field, *subject_field, *buf, *buf1;
  
	from_field = NULL;
	subject_field = NULL;
	buf = NULL;
	buf1 = NULL;

	for(j = 0; j < num_headers; j++) {
		p = strchr(headers[j], ':');
		if (p == NULL)
			continue;

		if (strncmp("From", headers[j], 4) == 0) {
			p++;
			while (isspace(p[0]))
				p++;
			from_field = p;
			if (subject_field)
				break;
		}
		else if (strncmp("Subject", headers[j], 7) == 0) {
			p++;
			while (isspace(p[0]))
				p++;
			subject_field = p;
			if (from_field)
				break;
		}
	}

	if (from_field == NULL)
		from_field = "<<unknown sender>>";

	buf = xmalloc(winlength+1);  /* add 1 for the NULL terminator */
	strncpy(buf, from_field, winlength);
	buf[winlength] = 0;

	len = strlen(buf);
	if  (len < 30)
		len = 30;
	len = winlength-len;

	if (len > 0 && subject_field != NULL) {
		buf1 = xmalloc(len+1);  /* add 1 for the NULL terminator */
		strncpy(buf1, subject_field, len);
		buf1[len] = 0;
	}

	if (buf1 == NULL)
		printf("%s\n", buf);
	else
		printf("%-30s %s\n", buf, buf1);

	free(buf);
	if (buf1 != NULL)
		free(buf1);
}

#ifndef _PATH_MAILDIR
#if defined(sun)
#define _PATH_MAILDIR "/var/mail"
#else
#define _PATH_MAILDIR "/var/spool/mail"
#endif
#endif

/*
 * Do the old unix mail lookup.
 */
int getmail_unix(char *user)
{
	char lbuf[BUFSIZ];
	char lbuf2[BUFSIZ];
	int havemail = 0, stashed = 0;

	if (chdir(_PATH_MAILDIR) < 0) {
		unixmail = 0;
		return -1;
	}
	if (freopen(user, "r", stdin) == NULL) {
		if(!popmail)
			fprintf(stderr, "Can't open %s/%s.\n",
				_PATH_MAILDIR, user);
		else
			unixmail = 0;
		return -1;
	}

	while (fgets(lbuf, sizeof(lbuf), stdin) != NULL)
		if (lbuf[0] == '\n' && stashed) {
			stashed = 0;
			if (!havemail && !totals && popmail)
				puts("Local mail:");
			if (!totals)
				fputs(lbuf2, stdout);
			havemail++;
		} else if (strncmp(lbuf, "From ", 5) == 0 &&
			   (sender == NULL || match(&lbuf[4], sender))) {
			strcpy(lbuf2, lbuf);
			stashed = 1;
		}
	if (stashed && !totals)
		fputs(lbuf2, stdout);
	if (totals && havemail) {
		struct stat st;
		if (fstat(0, &st) != -1)
			printf("You have %d local message%s (%u bytes).\n",
			       havemail, havemail > 1 ? "s" : "",
			       (unsigned int) st.st_size);
		else
			printf("You have %d local message%s.\n",
			       havemail, havemail > 1 ? "s" : "");
	}
	return havemail;
}

int match (char *line, char *str)
{
	register char ch;

	while (*line == ' ' || *line == '\t')
		++line;
	if (*line == '\n')
		return (0);
	while (*str && *line != ' ' && *line != '\t' && *line != '\n') {
		ch = isupper(*line) ? tolower(*line) : *line;
		if (ch != *str++)
			return (0);
		line++;
	}
	return (*str == '\0');
}
