/* this program talks to pop3 servers */
/* (C) Tim Graves, Sun Microsystems 1995 */
#include "talktopop.h"
#include <netinet/in.h>
#include <sys/uio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* info on the port we are dealing with etc */
struct sockaddr_in sin ;
int net ;
FILE *netinfd, *netoutfd ;

/* various strings etc that are set or reset from the command line */
char popserver[100] ;
char popname [100] ;
char protname [100] ;
char uname[100] ;
char passwd[100] ;
int filestartno = 0 ;
int dodel = FALSE ;
int direction = TRUE ;
int verbose = FALSE ;
int maxsize = MAXSIZE ;
int maxemail = MAXCOUNT ;
int hnset, pwset, unset ;


/* message information */
int messcnt = 0 ;

/* debug information */
int debug = FALSE ;
/* pointer to the message size array */
int * msglist ;

main(argc, argv, environ)
int argc ;
char * argv[] ;
char * environ[] ;
{
	int ret ;
	int startno ;
	init() ;
	/* handle the command line */
	cmdline(argc, argv) ;
	if (verbose == TRUE)
		dogreet() ;
	/* check that the arguments have worked out right and
	   get the password if required */
	checkflags() ;
	/* maxemail can viable be zero if there are more saved emails than
	   the default value of maxemail. in this case just crunch out */
	if (maxemail == 0)
		exit(0) ;
	/* setup the network connection */
	if (startnet() == NETERR)
	{
		printf("Network initialisation error\nExiting\n") ;
		return ;
	}
	ret = doconnect() ;
	if (ret == BADCMD)
	{
		printf("Pop server returns bad\n") ;
		return ;
	}
	ret = dologin() ;
	if (ret == GOODCMD)
	{
		dolist() ;
		if (direction == FALSE)
			startno = findfromlast() ;
		else
			startno = 1 ;
		doget(startno) ;
	}
	doquit() ;
	endnet() ;
}
checkflags() 
{
	char tmp[1024] ;
	char * pass ;
	/* ensure that we have a host name and user name */
	if (hnset == FALSE)
	{
		printf("Error: talktopop cannot find your pop server name in the config file or the command list") ;
		exit(1) ;
	}
	if (pwset == FALSE)
	{
		sprintf(tmp, "Please enter the password for user %s on machine %s:", uname, popserver) ;
		pass = getpass(tmp) ;
		strcpy(passwd, pass) ;
	}
}
init()
{
	confinit() ;
	getuname(uname) ;
	unset = TRUE ;
	readconf() ;
}
cmdline(argc, argv) 
int argc ;
char * argv[] ;
{
	int c ;
	extern int optind ;
	extern char * optarg ;
	int cset = FALSE ;
	while ((c = getopt(argc, argv, "h:u:p:c:s:flvdS:n:")) != EOF)
	{
		switch(c)
		{
			case 'h': /* hostname for pop server */
				strcpy(popserver, optarg) ;
				hnset = TRUE ;
				break ;
			case 'u': /* username to use */
				strcpy(uname, optarg) ;
				unset = TRUE ;
				break ;
			case 'p': /* password to use */
				strcpy(passwd, optarg) ;
				pwset = TRUE ;
				break ;
			case 'c': /* max number of emails to retrieve */
				maxemail = atoi(optarg) ;
				cset = TRUE ;
				break ;
			case 's': /* max size of email to retrieve */
				maxsize = atoi(optarg) ;
				break ;
			case 'f': /* retrieve maxemails from the start of the email box */
				direction = TRUE ;
				break ;
			case 'l': /* retrieve maxemails from the end of the email box */
				direction = FALSE ;
				break ;
			case 'v': /* be verbose for debugging */
				verbose = TRUE ;
				break ;
			case 'S': /* service name (defaults to pop-3) */
				strcpy(popname, optarg) ;
				break ;
			case 'n': /* number to start the file count at */
				filestartno = atoi(optarg) ;
				break ;
			case 'd': /* delete the messages once rtead */
				dodel = TRUE ;
				break ;
		}
	}
	/* calculate the number of emails to actually retrieve. 
	   this is maxemail - the findnumber.
	   if the count has been specificaly set dont do this */
	if (cset == FALSE)
	{
		maxemail = maxemail - filestartno ;
		if (maxemail < 0)
			maxemail = 0 ;
	}
}
dogreet()
{
	printf("Talktopop, Version 1.9\n(C) Tim Graves, Sun Microsystems 1995\n") ;
}
doquit()
{
	char tmp[MAXLINE] ;
	sprintf(tmp, "%s", QUITCMD) ;
	tonet(tmp) ;
	fromnet(tmp) ;
	free(msglist) ;
}
dodelete(i)
int i ;
{
	char tmp[MAXLINE] ;
	if (verbose == TRUE)
		printf("Deleting mail no %i\n", i) ;
	sprintf(tmp, "%s %d", DELECMD, i) ;
	tonet(tmp) ;
	fromnet(tmp) ;
	if (tmp[0] == BADCHAR)
		return(BADCMD) ;
	else
		return(GOODCMD) ;
}

doretrieve(i, fname)
int i ;
char * fname ;
{
	FILE *fd ;
	char tmp[MAXLINE] ;
	if (verbose == TRUE)
		printf("Retrieving mail no %d to file %s\n", i, fname) ;
	if ((fd = fopen(fname, "w")) == NULL)
	{
		return(BADFILE) ;
	}
	sprintf(tmp, "%s %d", RETRCMD, i) ;
	tonet(tmp) ;
	/* do the first line, if there is a - there its an error */
	fromnet(tmp) ;
	if (tmp[0] == BADCHAR)
	{
		fclose(fd) ;
		return(BADCMD) ;
	}
	/* the line is OK, do the message */
	while(fromnet(tmp) == TRUE)
	{
		/* NOTE fromnet removes andy \r o \n stuff, we need to 
		   reinsert this into the file */
		fprintf(fd, "%s\n", tmp) ;
	}
	fclose(fd) ;
	return(GOODCMD) ;
}
int findfromlast()
{
	/* counting backwards from the last entry in msglist count 
	   maxemails which are <= maxsize back and return the number of 
	   the last one */
	int i, count ;
	count = 0 ;
	for ( i = messcnt ; ((i >= 0 ) && ( count < maxemail)) ; i--)
		if (msglist[i] <= maxsize)
			count ++ ;
	return(i) ;
}
/* get maxcount emails from the pop server and save then away */
doget(startno)
int startno ;
{
	int i, count;
	char tmp[100] ;
	if (verbose == TRUE)
		printf("Retrieving emails from %d\n", startno) ;
	count = 0 ;
	for (i = startno ; ((count < maxemail) && (i <= messcnt)) ; i ++)
	{
		/* is the email the right size ? */
		if (msglist[i] > maxsize)
			continue ;
		/* OK we should be looking at this email */
		/* create the filename */
		sprintf(tmp, INSKELETON, count+filestartno) ;
		/* get the file */
		doretrieve(i, tmp) ;
		/* are we deleting retrieved messages ? */
		if (dodel == TRUE)
			dodelete(i) ;
		/* increment the count */
		count ++ ;
	}
}
dolist()
{
	char tmp [MAXLINE] ;
	int i, mailno, mailsize ;
	if (verbose == TRUE)
		printf("Listing email size\n") ;
	for (i = 1 ; i <= messcnt; i ++) 
	{
		sprintf(tmp, "%s %d", LISTCMD, i) ;
		tonet(tmp) ;
		fromnet(tmp) ;
		if (tmp[0] == BADCHAR) /* this could get things out of sync but its usefull for debugging */
		{
			return(BADCMD) ;
		}
		/* skip over the "+OK " */
		sscanf(&tmp[4],"%d %d", &mailno, &mailsize) ;
		if (mailno > messcnt)
			return(BADCMD) ;
		if (mailno != i)
			return(BADCMD) ;
		msglist[i] = mailsize ;
	}
	return(GOODCMD) ;
}
doconnect()
{
	char tmp[MAXLINE] ;
	if (verbose == TRUE)
		printf("Connecting to pop server\n") ;
	fromnet(tmp) ;
	if (tmp[0] == BADCHAR)
		return(BADCMD) ;
	else
		return(GOODCMD) ;
}
int dologin()
{
	char tmp[MAXLINE] ;
	size_t i ;
	if (verbose == TRUE)
		printf("Logging in to pop server\n") ;
	/* request the user name */
	sprintf(tmp, "%s %s", USERCMD, uname) ;
	tonet(tmp) ;
	fromnet(tmp) ;
	if (tmp[0] == BADCHAR)
	{
		return(BADCMD) ;
	}
	sprintf(tmp, "%s %s", PASSCMD, passwd) ;
	tonet(tmp) ;
	fromnet(tmp) ;
	if (tmp[0] == BADCHAR)
	{
		return(BADCMD) ;
	}
	/* the format is something like 
	+OK 360 messages ready for tim in /usr/spool/mail/tim
	for a strict pop3 implementation but some are like
	+OK tim has 77 message(s) (709547 octets).
	find the first number and then extract it (assume the first
	number is the message count  */
	for (i = 0 ; i < strlen(tmp) ; i ++)
		if (isdigit(tmp[i]))
			break ;
	messcnt = atoi(&tmp[i]) ;
	msglist = calloc((size_t) messcnt + 1, (size_t) sizeof(int) );
	return(GOODCMD) ;
}
endnet()
{
	fclose(netinfd) ;
	fclose(netoutfd) ;
	close(net) ;
}
int startnet()
{
	/* hard coded at the moment, connect, list and quit */
	struct servent * sp ;
	struct hostent *hp ;
	int ret ;
	
	/* get the port information etc for pop */
	if ((sp = getservbyname(popname, protname)) == NULL)
	{
		printf("%s/%s: unknown service\n", popname, protname) ;
		return(NETERR) ;
	}
	if ((hp = gethostbyname(popserver)) == NULL)
	{
		printf("%s: unknown hostname\n", popserver) ;
		return(NETERR) ;
	}
	/* build up the sin structure */
	sin.sin_family = (short) AF_INET ;
	memcpy((char *) &sin.sin_addr, (char *) hp->h_addr, hp->h_length) ;
	sin.sin_port = htons((u_short) sp->s_port) ;
	/* create the socket */
	net = socket(AF_INET, SOCK_STREAM,0) ;
	if (net < 0)
	{
		printf("socket error\n") ;
		return(NETERR);
	}
	ret = connect(net, (struct sockaddr *) &sin, sizeof(sin)) ;
	if (ret < 0)
	{
		printf("connect error\n") ;
		return(NETERR) ;
	}
	netinfd = fdopen(net,"r") ;
	netoutfd = fdopen(net, "w") ;
	if ((netinfd == NULL) || (netoutfd == NULL))
	{
		printf("fdopen error\n") ;
		if (netinfd != NULL)
			fclose(netinfd) ;
		if (netoutfd != NULL) 
			fclose(netoutfd) ;
		close(net) ;
		return(NETERR) ;
	}
	return(NETOK) ;
}

tonet(str)
char * str ;
{
	int len ;
	int ret ;
	char tmp[MAXLINE] ;
	sprintf(tmp, "%s\r\n", str) ;
	len = strlen(tmp) ;
	if (debug == TRUE)
		printf("Sending :%slength %d\n", tmp, len) ;
	ret = fprintf(netoutfd, "%s", tmp) ;
	fflush(netoutfd) ;
	if (ret == -1)
	{
		printf("error in send\n") ;
	}
	return ;
}
int fromnet(str)
char * str ;
{
	char tmp[MAXLINE] ;
	char ch ;
	int i, ret ;
	
	ch = '\0' ;
	i = 0 ;
	while (ch != '\n')
	{
		ch = getc(netinfd) ;
		if (ch == EOF)
		{
			printf("recv error") ;
			tmp[i] = '\0' ;
			return ;
		}
		tmp[i] = ch ;
		if (debug == TRUE)
			printf("%c", ch );
		i ++ ;
	}
	/* to get here we must have  tmp[i-1] == '\n' so remove it */
	tmp[i-1] = '\0' ;
	/* if tmp[i-2] is '\r' remove it */
	if (tmp[i-2] == '\r') 
		tmp[i-2] = '\0' ;
	/* if the first char is . and the length is greater than 1 remove the first .
	   (pop3 replaces .<text> with ..<text> and also uses . by itself to mean
	   end of the multi line message */
	if (tmp[0] == '.') 
	{
		if (tmp[1] == '\0')
		{
			/* end of multi line stuff */
			/* empty the return string */
			str[0] = '\0' ;
			return (FALSE) ;
		}
		else
		{
			/* the origional text started with a . remove the padding */
			strcpy(str, &tmp[1]) ;
			return(TRUE) ;
		}
	}
	/* no special handling required */
	strcpy(str, tmp) ;
	return(TRUE) ;
}
