/*
 *	POP2 Client routines.  Originally authored by Mike Stockett
 *	  (WA7DYX).
 *	Modified 12 May 1991 by Mark Edwards (WA6SMN) to use new timer
 *	facilities in NOS0423.  Fixed type mismatches spotted by C++.
 *	Modified 27 May 1990 by Allen Gwinn (N5CKP) for compatibility
 *	  with later releases (NOS0522).
 *	Added into NOS by PA0GRI (and linted into "standard" C)
 *
 *	Some code culled from previous releases of SMTP.
 *
 *	Client routines for Simple Mail Transfer Protocol ala RFC821
 *	A.D. Barksdale Garbee II, aka Bdale, N3EUA
 *	Copyright 1986 Bdale Garbee, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	  this notice is retained.
 * 	Modified 14 June 1987 by P. Karn for symbolic target addresses,
 *	  also rebuilt locking mechanism
 *	Copyright 1987 1988 David Trulli, All Rights Reserved.
 *	Permission granted for non-commercial copying and use, provided
 *	this notice is retained.
 */
#include "global.h"
#ifdef POP
#ifndef MSDOS
#include <setjmp.h>
#include <stdarg.h>
#include "mbuf.h"
#include "proc.h"
#include "socket.h"
#endif
#include "hardware.h"
#include "cmdparse.h"
#include "netuser.h"
#include "files.h"
#include "mailutil.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: popcli.c,v 1.17 1997/09/07 21:18:28 root Exp $";
#endif

#define BUF_LEN		257

/* POP client control block */

struct pop_ccb {
	int socket;		/* socket for this connection */
	char state;		/* client state */
#define	   CALL		0
#define	   NMBR		3
#define	   SIZE		5
#define	   XFER		8
#define	   EXIT		10
	char buf[BUF_LEN];	/* tcp input buffer */
	int folder_len;		/* number of msgs in current folder */
	long msg_len;		/* length of current msg */
	int msg_num;		/* current message number */
};
static struct pop_ccb *ccb;

#define NULLCCB		(struct pop_ccb *)0

static int Popquiet = 0;

static struct timer popcli_t;
static uint32 mailhost;
static char mailbox_name[10], mailbox_pathname[BUF_LEN], username[20],
     password[20], Workfile_name[] = "mbox.pop";

static int domailbox (int argc, char *argv[], void *p);
static int domailhost (int argc, char *argv[], void *p);
static int douserdata (int argc, char *argv[], void *p);
static int doquiet (int argc, char *argv[], void *p);
static int dotimer (int argc, char *argv[], void *p);
static struct pop_ccb *new_ccb (void);
static void delete_ccb (void);
static void pop_send (int unused, void *cb1, void *p);
static int popkick (int argc, char *argv[], void *p);
int dopop (int argc, char *argv[], void *p);
static void pop_csm (struct pop_ccb * theccb);
static void quit_session (struct pop_ccb * theccb);
extern void rrip (register char *s);

/* I don't know why this isn't static, it isn't called anywhere else {was} */
static int poptick (void);

static struct cmds Popcmds[] =
{
	{ "mailbox",	domailbox,	0, 0, NULLCHAR },
	{ "mailhost",	domailhost,	0, 0, NULLCHAR },
	{ "kick",	popkick,	0, 0, NULLCHAR },
	{ "quiet",	doquiet,	0, 0, NULLCHAR },
	{ "timer",	dotimer,	0, 0, NULLCHAR },
	{ "userdata",	douserdata,	0, 0, NULLCHAR },
	{ NULLCHAR,	0,		0, 0, NULLCHAR }
};


/* Command string specifications */

static char ackd_cmd[] = "ACKD\n";
static char login_cmd[] = "HELO %s %s\n";
static char quit_cmd[] = "QUIT\n";
static char read_cur_cmd[] = "READ\n";
static char retr_cmd[] = "RETR\n";
static const char *greeting_rsp = "+ POP2 ";

#ifdef POP_FOLDERS
static char fold_cmd[] = "FOLD %s\n";
#endif
#if 0
static char nack_cmd[] = "NACK\n";	/* Not implemented */
#endif


static FILE *fd;



int
dopop (int argc, char *argv[], void *p)
{
	return subcmd (Popcmds, argc, argv, p);
}



static int
domailbox (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		if (mailbox_name[0] == '\0')
			tputs ("mailbox name not set yet\n");
		else
			tprintf ("%s\n", mailbox_name);
	} else
		strncpy (mailbox_name, argv[1], 10);

	return 0;
}



static int
domailhost (int argc, char *argv[], void *p OPTIONAL)
{
uint32 n;

	if (argc < 2)
		tprintf ("%s\n", inet_ntoa (mailhost));
	else if ((n = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	} else
		mailhost = n;

	return 0;
}



static int
doquiet (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Popquiet, "POP quiet", argc, argv);
}



static int
douserdata (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2)
		tprintf ("%s\n", username);
	else if (argc != 3) {
		tputs ("Usage: pop userdata <username> <password>\n");
		return 1;
	} else {
		sscanf (argv[1], "%18s", username);
		sscanf (argv[2], "%18s", password);
	}

	return 0;
}



/* Set scan interval */

static int
dotimer (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		tprintf ("%lu/%lu\n", read_timer (&popcli_t) / 1000L, dur_timer (&popcli_t) / 1000L);
		return 0;
	}
	popcli_t.func = (void (*)(void *)) poptick;	/* what to call on timeout */
	popcli_t.arg = NULL;	/* dummy value */
	set_timer (&popcli_t, atol (argv[1]) * 1000L);	/* set timer duration */
	start_detached_timer (&popcli_t);/* and fire it up */
	return 0;
}



static int
popkick (int argc OPTIONAL, char *argv[]OPTIONAL, void *p OPTIONAL)
{
	(void) poptick ();
	return 0;
}



static int
poptick ()
{
	if (ccb == NULLCCB) {
		/* Don't start if any of the required parameters have not been specified */

		if (mailhost == 0) {
			tputs ("mailhost not defined yet.(pop mailhost <host>)\n");
			return 0;
		}
		if (mailbox_name[0] == '\0') {
			tputs ("mailbox name not defined yet.(pop mailbox <name>)\n");
			return 0;
		}
		if (username[0] == '\0') {
			tputs ("username not defined yet. (pop user <name> <pass>)\n");
			return 0;
		}
		if (password[0] == '\0') {
			tputs (" Unknown password\n");
			return 0;
		}
		if ((ccb = new_ccb ()) == NULLCCB) {
			tcmdprintf ("*** Unable to allocate CCB\n");
			return 0;
		}
		(void) newproc ("Auto-POP Client", 1024, pop_send, 0, ccb, NULL, 0);
	}
	/* Restart timer */

	start_detached_timer (&popcli_t);
	return 0;
}



/* this is the master state machine that handles a single SMTP transaction */
/* it is called with a queue of jobs for a particular host. */

static void
pop_send (int unused OPTIONAL, void *cb1, void *p OPTIONAL)
{
char const *cp;
struct sockaddr_in fsocket;
struct pop_ccb *theccb;

	theccb = (struct pop_ccb *) cb1;
	fsocket.sin_family = AF_INET;
	fsocket.sin_addr.s_addr = mailhost;
	fsocket.sin_port = IPPORT_POP;

	theccb->socket = socket (AF_INET, SOCK_STREAM, 0);

	theccb->state = CALL;

	if (connect (theccb->socket, (char *) &fsocket, SOCKSIZE) == 0)
		log (theccb->socket, "Connected to mailhost %s", inet_ntoa (mailhost));
	else {
		cp = sockerr (theccb->socket);
		log (theccb->socket, "Connect to mailhost %s failed: %s", inet_ntoa (mailhost),
		     (cp != NULLCHAR) ? cp : "");
	}

	for (;;) {
		if (recvline (theccb->socket, (unsigned char *) theccb->buf, BUF_LEN) == -1)
			break;

		rrip (theccb->buf);
		pop_csm (theccb);
		if (theccb->state == EXIT)
			break;
	}

	log (theccb->socket, "Connection closed to mailhost %s", inet_ntoa (mailhost));
	close_s (theccb->socket);
	if (fd != NULLFILE)
		fclose (fd);
	delete_ccb ();
}



/* free the message struct and data */

static void
delete_ccb ()
{
	if (ccb == NULLCCB)
		return;

	free ((char *) ccb);
	ccb = NULLCCB;
}



/* create a new  pop control block */

static struct pop_ccb *
new_ccb ()
{
register struct pop_ccb *theccb;

	if ((theccb = (struct pop_ccb *) callocw (1, sizeof (struct pop_ccb))) == NULLCCB)
		        return (NULLCCB);

	return (theccb);
}



/* ---------------------- pop client code starts here --------------------- */

static void
pop_csm (struct pop_ccb *theccb)
{
FILE *mf;

	switch (theccb->state) {
		case CALL:
			if (strncmp (theccb->buf, greeting_rsp, strlen (greeting_rsp)) == 0) {
				(void) usprintf (theccb->socket, login_cmd, username, password);
				theccb->state = NMBR;
			} else
				quit_session (theccb);
			break;

		case NMBR:
			switch (theccb->buf[0]) {
				case '#':
					if ((fd = fopen (Workfile_name, "a+")) == NULLFILE) {
						perror ("Unable to open work file");
						quit_session (theccb);
						return;
					}
					fseek (fd, 0, SEEK_SET);
					theccb->folder_len = atoi (&(theccb->buf[1]));
					(void) usprintf (theccb->socket, read_cur_cmd);
					theccb->state = SIZE;
					break;

				case '+':

					/* If there is no mail (the only time we get a "+"
					 * response back at this stage of the game),
					 * then just close out the connection, because
					 * there is nothing more to do!! */

				default:
					quit_session (theccb);
					break;
			}
			break;

		case SIZE:
			if (theccb->buf[0] == '=') {
				theccb->msg_len = atol (&(theccb->buf[1]));
				if (theccb->msg_len > 0) {
					(void) usprintf (theccb->socket, retr_cmd);
					theccb->state = XFER;
				} else {
					log (theccb->socket, "POP client retrieved %d messages",
					     theccb->folder_len);

					/* All done, so do local cleanup */

					if (mlock (Mailspool, mailbox_name)) {
						tprintf ("\n*** Local mailbox locked, new mail in file %s\n", Workfile_name);
						quit_session (theccb);
						return;
					}
					sprintf (mailbox_pathname, "%s/%s.txt", Mailspool, mailbox_name);
					if ((mf = fopen (mailbox_pathname, "a+")) == NULL) {
						tprintf ("\n*** Unable to open local mailbox, new mail in file %s\n", Workfile_name);
						quit_session (theccb);
						return;
					}
					fseek (fd, 0, SEEK_SET);

					while (!feof (fd)) {
						if (fgets (theccb->buf, BUF_LEN, fd) != NULLCHAR)
							fputs (theccb->buf, mf);
					}
					fclose (mf);
					fclose (fd);
					fd = NULL;
					tprintf ("New mail arrived for %s from mailhost <%s>%c\n",
						 mailbox_name, inet_ntoa (mailhost), Popquiet ? ' ' : '\007');
					rmlock (Mailspool, mailbox_name);
					unlink (Workfile_name);
					quit_session (theccb);
				}
			} else
				quit_session (theccb);
			break;

		case XFER:
			fprintf (fd, "%s\n", theccb->buf);

			theccb->msg_len -= (long) (strlen (theccb->buf) + 2);	/* Add CRLF */

			if (theccb->msg_len > 0)
				return;

			(void) usprintf (theccb->socket, ackd_cmd);

			theccb->msg_num++;
			theccb->state = SIZE;
			break;

		case EXIT:
			if (fd != NULLFILE)
				fclose (fd);
			break;

		default:
			break;
	}
}



static void
quit_session (struct pop_ccb *theccb)
{
	(void) usprintf (theccb->socket, quit_cmd);

	theccb->state = EXIT;
}

#endif /* POP */
