/* $Id: popxmit.c,v 1.12 2009/12/16 05:51:12 onoe Exp $ */

/*-
 * Copyright (c) 1999 Atsushi Onoe
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <errno.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef USE_SSL	/*XXX*/
#define	APOP
#endif

#ifdef APOP
#ifdef USE_OPENSSL
#include <openssl/evp.h>
#else /* USE_OPENSSL */
#include <evp.h>
#endif /* USE_OPENSSL */
#endif /* APOP */

#define	POP3_PORT	110	/* default pop3 port */

enum {
	AUTH_POP3
	, AUTH_RPOP
#ifdef APOP
	, AUTH_APOP
#endif /* APOP */
} auth = AUTH_POP3;
int s = -1;
int debug;
char *ruser, *luser, *host, *pass;
char respbuf[BUFSIZ];

static void
usage(void)
{
	fprintf(stderr, "Usage: popxmit [-3");
	fprintf(stderr, "|-r");
#ifdef APOP
	fprintf(stderr, "|-a");
#endif /* APOP */
	fprintf(stderr, "] [user@]host\n");
	exit(1);
}

static void
put_command(const char *fmt, ...)
{
	va_list ap;
	int off, cc, len;
	char sndbuf[BUFSIZ];

	va_start(ap, fmt);
	vsnprintf(sndbuf, sizeof(sndbuf), fmt, ap);
	va_end(ap);
	if (debug)
		printf("<<< %s\n", sndbuf);
	strlcat(sndbuf, "\r\n", sizeof(sndbuf));
	len = strlen(sndbuf);

	for (off = 0; off < len; off += cc) {
		cc = write(s, sndbuf + off, len - off);
		if (cc < 0) {
			perror("write");
			break;
		}
	}
}

static void
get_response(void)
{
	int cc, off;

	off = 0;
	while (off < sizeof(respbuf)) {
		cc = read(s, respbuf + off, sizeof(respbuf) - off);
		if (cc < 0) {
			perror("read");
			exit(1);
		}
		off += cc;
		if (cc == 0)
			break;
		if (respbuf[off - 1] == '\n') {
			off--;
			if (respbuf[off - 1] == '\r')	/* must be */
				off--;
			break;
		}
	}
	respbuf[off] = '\0';
	if (off < 3 || strncasecmp(respbuf, "+OK", 3) != 0) {
		fprintf(stderr, "popxmit: %s\n", respbuf);
		put_command("quit");
		exit(1);
	}
	if (debug)
		printf(">>> %s\n", respbuf);
}

static void
user_init(void)
{
	char *p;
	int match;
	FILE *fp;
	struct passwd *pwd;
	char *def_user, *def_pass;
	char buf[BUFSIZ];
	static const char delim[] = " \t\n";

	if ((pwd = getpwuid(getuid())) == NULL) {
		fprintf(stderr, "unknown user (%d)\n", getuid());
		exit(1);
	}
	if ((luser = strdup(pwd->pw_name)) == NULL) {
		fprintf(stderr, "no more memory\n");
		exit(1);
	}
	strlcpy(buf, pwd->pw_dir, sizeof(buf));
	strlcat(buf, "/.netrc", sizeof(buf));
	if ((fp = fopen(buf, "r")) == NULL)
		return;
	match = 0;
	def_user = def_pass = NULL;
	while (fgets(buf, sizeof(buf), fp)) {
		if ((p = strtok(buf, delim)) == NULL)
			continue;
		if (strcmp(p, "default") == 0) {
			match = 1;	/* default */
			continue;
		}
		if (strcmp(p, "machine") == 0) {
			if ((p = strtok(NULL, delim)) == NULL
			||  strcmp(p, host) != 0)
				match = 0;
			else
				match = 2;
			continue;
		}
		if (match == 0)
			continue;
		if (strcmp(p, "login") == 0) {
			if (match == 1) {
				if (def_user == NULL)
					def_user = strdup(strtok(NULL, delim));
			} else {
				if (ruser == NULL)
					ruser = strdup(strtok(NULL, delim));
			}
			continue;
		}
		if (strcmp(p, "password") == 0) {
			if (match == 1) {
				if (def_pass == NULL)
					def_pass = strdup(strtok(NULL, delim));
			} else {
				if (pass == NULL)
					pass = strdup(strtok(NULL, delim));
			}
			continue;
		}
	}
	fclose(fp);
	if (ruser == NULL) {
		ruser = def_user;
		if (ruser == NULL)
			ruser = luser;
	}
	if (pass == NULL)
		pass = def_pass;
}

#ifdef HAVE_GETADDRINFO
static void
setup_conn(void)
{
	struct addrinfo hints, *res0, *res;
	int error;
	char *cause = "unknown";

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	error = getaddrinfo(host, "pop3", &hints, &res0);
	if (error) {
		fprintf(stderr, "popxmit: %s", gai_strerror(error));
		exit(1);
	}

	if (s >= 0) {
		close(s);
		s = -1;
	}
	for (res = res0; res; res = res->ai_next) {
		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (s < 0) {
			cause = "socket";
			continue;
		}

		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
			cause = "connect";
			close(s);
			s = -1;
			continue;
		}

		break;
	}
	freeaddrinfo(res0);
	if (s < 0) {
		perror(cause);
		exit(1);
	}

	get_response();
}
#else
static void
setup_conn(void)
{
	int i;
	struct hostent *hp;
	struct servent *sp;
	struct sockaddr_in sin;

	if (s < 0) {
		if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			perror("socket");
			exit(1);
		}
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;

	if ((sp = getservbyname("pop3", "tcp")) == NULL)
		sin.sin_port = htons(POP3_PORT);
	else
		sin.sin_port = sp->s_port;

	if ((hp = gethostbyname(host)) == NULL) {
		fprintf(stderr, "popxmit: -ERR %s: %s\n", host,
#if HAVE_HSTRERROR
			hstrerror(h_errno)
#else
			"Host not found"
#endif
			);
		exit(1);
	}
	for (i = 0; ; i++) {
		if (hp->h_addr_list[i] == NULL) {
			fprintf(stderr, "popxmit: -ERR %s: %s\n", host,
				strerror(errno));
			exit(1);
		}
		memcpy(&sin.sin_addr, hp->h_addr_list[i], hp->h_length);
		if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == 0)
			break;
	}
	get_response();
}
#endif

#ifdef APOP
static void
login_apop(void)
{
	char *sp, *ep;
	int i;
	char apass[BUFSIZ];
	EVP_MD_CTX md_ctx;
	u_char mdbuf[EVP_MAX_MD_SIZE];
	u_int mdlen;

	if ((sp = strchr(respbuf, '<')) == NULL
	||  (ep = strchr(sp, '>')) == NULL) {
		fprintf(stderr, "server not support APOP\n");
		exit(1);
	}
	ep++;
	if (pass == NULL)
		pass = getpass("Password:");
	EVP_DigestInit(&md_ctx, EVP_md5());
	EVP_DigestUpdate(&md_ctx, sp, ep - sp);
	EVP_DigestUpdate(&md_ctx, pass, strlen(pass));
	EVP_DigestFinal(&md_ctx, mdbuf, &mdlen);
	for (i = 0, sp = apass; i < mdlen; i++, sp += 2)
		snprintf(sp, sizeof(apass) - (sp - apass), "%02x", mdbuf[i]);
	put_command("apop %s %s", ruser, apass);
}
#endif /* APOP */

static void
login_pop(void)
{

	switch (auth) {
	case AUTH_RPOP:
	case AUTH_POP3:
		put_command("user %s", ruser);
		get_response();
		if (auth == AUTH_RPOP)
			put_command("rpop %s", luser);
		else {
			if (pass == NULL)
				pass = getpass("Password:");
			put_command("pass %s", pass);
		}
		break;
#ifdef APOP
	case AUTH_APOP:
		login_apop();
		break;
#endif /* APOP */
	}
	get_response();
}

static void
xmit_mail(void)
{
	char buf[BUFSIZ];
	char *p;

	put_command("xtnd xmit");
	get_response();

	while (fgets(buf, sizeof(buf), stdin)) {
		if ((p = strchr(buf, '\n')) != NULL)
			*p = '\0';
		if (buf[0] == '.' && buf[1] == '\0')
			strlcpy(buf, "..", sizeof(buf));
		put_command("%s", buf);
	}
	put_command(".");
	get_response();
}

int
main(int argc, char **argv)
{
	int c;
	int port;
	extern int optind;

	while ((c = getopt(argc, argv, "ra3d")) != EOF) {
		switch (c) {
		case 'r':
			auth = AUTH_RPOP;
			break;
#ifdef APOP
		case 'a':
			auth = AUTH_APOP;
			break;
#endif /* APOP */
		case '3':
			auth = AUTH_POP3;
			break;
		case 'd':
			debug = 1;
			break;
		default:
			usage();
		}
	}
	if (argc - optind != 1)
		usage();
	if (auth == AUTH_RPOP) {
		if ((s = rresvport(&port)) < 0) {
			perror("rresvport");
			exit(1);
		}
	}
	if (setuid(getuid()) < 0) {
		perror("setuid");
		exit(1);
	}

	ruser = argv[optind];
	if ((host = strrchr(ruser, '@')) != NULL) {
		*host++ = '\0';
	} else {
		host = ruser;
		ruser = NULL;
	}

	user_init();
	setup_conn();
	login_pop();
	xmit_mail();

	put_command("quit");
	exit(0);
}
