/*
 *  PPPPAP.C	-- Password Authentication Protocol for PPP
 *
 *	This implementation of PPP is declared to be in the public domain.
 *
 *	Jan 91	Bill_Simpson@um.cc.umich.edu
 *		Computer Systems Consulting Services
 *
 *	Acknowledgements and correction history may be found in PPP.C
 */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "session.h"
#include "socket.h"
#include "ppp.h"
#include "pppfsm.h"
#include "ppppap.h"
#include "cmdparse.h"
#include "files.h"

static int dopap_user		__ARGS((int argc, char *argv[], void *p));

static void pap_input __ARGS((int mustask, void *v1, void *v2));
static void pap_pwdlookup __ARGS((struct pap_s *pap_p));
static void pap_getpassword __ARGS((struct fsm_s *fsm_p, int mustask));

static struct mbuf *pap_makereq __ARGS((struct fsm_s *fsm_p));

static int pap_verify __ARGS((char *username, char *password));
static void pap_shutdown __ARGS((struct fsm_s *fsm_p));
static void pap_opening __ARGS((struct fsm_s *fsm_p, int flag));

static int pap_request	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *hdr,
			struct mbuf *data));
static int pap_check	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *hdr,
			struct mbuf *data));
static void pap_timeout	__ARGS((void *vp));

static void pap_reset	__ARGS((struct fsm_s *fsm_p));
static void pap_free	__ARGS((struct fsm_s *fsm_p));
static void pap_init	__ARGS((struct ppp_s *ppp_p));


static struct fsm_constant_s pap_constants = {
	"Pap",
	PPP_PAP_PROTOCOL,
	0x000E,				/* codes 1-3 recognized */

	Pap,
	PAP_REQ_TRY,
	PAP_FAIL_MAX,
	0,
	PAP_TIMEOUT * 1000L,

	pap_free,

	fsm_no_action,		/* pap_reset, */
	fsm_no_action,		/* pap_starting, */
	fsm_no_action,		/* pap_opening, */
	fsm_no_action,		/* pap_closing, */
	fsm_no_action,		/* pap_stopping, */

	pap_makereq,
	fsm_no_check,		/* pap_request, */
	fsm_no_check,		/* pap_ack, */
	fsm_no_check,		/* pap_nak, */
	fsm_no_check,		/* pap_reject */
};


/****************************************************************************/

/* "ppp <iface> pap" subcommands */
static struct cmds Papcmds[] = {
	"timeout",	doppp_timeout,	0,	0,	NULLCHAR,
	"try",		doppp_try,	0,	0,	NULLCHAR,
	"user",		dopap_user,	0,	0,	NULLCHAR,
	NULLCHAR,
};


int
doppp_pap(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct iface *ifp = p;
	register struct ppp_s *ppp_p = ifp->extension;

	pap_init(ppp_p);
	return subcmd(Papcmds, argc, argv, &(ppp_p->fsm[Pap]));
}


/* Set user/password */
int
dopap_user(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct fsm_s *fsm_p = p;
	register struct pap_s *pap_p = fsm_p->pdv;

	if (argc < 2) {
		tprintf("%s\n",
			(pap_p->username == NULLCHAR) ? "None" : pap_p->username);
		return 0;
	}
	free(pap_p->username);
	pap_p->username = NULLCHAR;
	free(pap_p->password);
	pap_p->password = NULLCHAR;

	if (stricmp(argv[1],"none") != 0) {
		pap_p->username = strdup(argv[1]);
		if (argc > 2) {
			pap_p->password = strdup(argv[2]);
		} else {
			pap_getpassword( fsm_p, FALSE);
		}
	}
	return 0;
}


/****************************************************************************/
/* Bring up a session on the console for for the username/password.
 * Return a NULLCHAR in either username or password if aborted.
 */
static void
pap_input(mustask, v1, v2)
int mustask;
void *v1;
void *v2;
{
	struct iface *iface = v1;
	struct pap_s *pap_p = v2;
	char buf[21];
	struct session *sp;

	/* Allocate a session control block */
	if((sp = newsession("PPP/PAP Auth",PPPPASS,0)) == NULLSESSION){
		tprintf("Too many sessions\n");
		return;
	}

	if (mustask)
		tprintf("\n%s: PPP/PAP Password Authentication Failed;"
			" enter ID and password again\n",
			iface->name);
	else
		tprintf("\n%s: PPP/PAP Password Authentication Required\n",
			iface->name);
	tprintf("%s: PPP/PAP  user: ",iface->name);
	usflush(sp->output);

	/* Only ask for the user if it is unknown */
	if (pap_p->username == NULLCHAR) {
		if (recvline(sp->input,buf,20) < 0) {
			pap_p->username = NULLCHAR;
		} else {
			rip(buf);
			pap_p->username = strdup(buf);
		}
	} else {
		tprintf("%s\n",pap_p->username);
	}

	if (pap_p->username != NULLCHAR) {
		/* turn off echo */
		sp->ttystate.echo = 0;
		tprintf("%s: PPP/PAP Password: ",iface->name);
		usflush(sp->output);
		if (recvline(sp->input,buf,20) < 0)
			pap_p->password = NULLCHAR;
		else {
			rip(buf);
			pap_p->password = strdup(buf);
		}
		tprintf("\n");
		/* Turn echo back on */
		sp->ttystate.echo = 1;
	}

	freesession(sp);
	psignal(pap_p,0);
}


/* Check the FTP userfile for this user; get password if available */
static void
pap_pwdlookup(pap_p)
struct pap_s *pap_p;
{
	char *buf;
	char *password;
	int permission;

	if ( pap_p->username == NULLCHAR )
		return;

	if ( (buf = userlookup( pap_p->username, &password, NULLCHARP,
			&permission, NULL )) == NULLCHAR )
		return;

	/* Check permissions for this user */
	if ( (permission & PPP_PWD_LOOKUP) == 0 ) {
		/* Not in ftpuser file for password lookup */
		free(buf);
		return;
	}

	/* Save the password from this userfile record */
	pap_p->password = strdup(password);
	free(buf);
}


/* Get user ID and password
 * Return a NULLCHAR in either username or password if undefined
 */
static void
pap_getpassword(fsm_p,mustask)
struct fsm_s *fsm_p;
int mustask;
{
	struct pap_s *pap_p = fsm_p->pdv;

	if (mustask) {
		free(pap_p->username);
		pap_p->username = NULLCHAR;
		free(pap_p->password);
		pap_p->password = NULLCHAR;
	} else if ( pap_p->username != NULLCHAR
		 && pap_p->password == NULLCHAR) {
		pap_pwdlookup(pap_p);
	}

	if ((pap_p->username == NULLCHAR)
	 || (pap_p->password == NULLCHAR)) {
		char *ifn = if_name( fsm_p->ppp_p->iface, " PAP" );
		newproc( ifn,
			256, pap_input, mustask, fsm_p->ppp_p->iface, pap_p, 0);
		free( ifn );
		pwait( fsm_p->pdv );
	}
}

/*******************************************/
/* Verify user and password sent by remote host */
static int
pap_verify(username,password)
char *username;
char *password;
{
	int privs;
	char *path;
	int anony = 0;

	/* Use same login as FTP server */
	path = mallocw(128);
	privs = userlogin(username,password,&path,128,&anony);
	free(path);

	/* Check privs for this user */
	if (privs == -1) {
		log(-1,"PAP: username/password incorrect or not found: %s",
				username);
		return -1;
	}

	if ((privs & PPP_ACCESS_PRIV) == 0) {
		log(-1,"PAP: no permission for PPP access: %s",
				username);
		return -1;
	}
	return 0;
}


/****************************************************************************/
/* Build a request to send to remote host */
static struct mbuf *
pap_makereq(fsm_p)
struct fsm_s *fsm_p;
{
	struct pap_s *pap_p = fsm_p->pdv;
	struct mbuf *req_bp = NULLBUF;
	register char *cp;
	int len;

	PPP_DEBUG_ROUTINES("pap_makereq()");

	if ( pap_p->username == NULLCHAR
	 ||  pap_p->password == NULLCHAR ) {
		fsm_log( fsm_p, "NULL username or password" );
		return NULLBUF;
	}

#ifdef PPP_DEBUG_OPTIONS
	if (PPPtrace & PPP_DEBUG_OPTIONS)
		log(-1, "    making user id %s", pap_p->username);
#endif

	/* Get buffer for authenticate request packet */
	len = 2 + strlen(pap_p->username) + strlen(pap_p->password);
	if ((req_bp = alloc_mbuf(len)) == NULLBUF)
		return NULLBUF;

	/* Load user id and password for authenticate packet */
	cp = req_bp->data;
	*cp++ = (char)strlen(pap_p->username);
	if ( strlen(pap_p->username) > 0 )
		cp = stpcpy(cp, pap_p->username);

	*cp++ = (char)strlen(pap_p->password);
	if ( strlen(pap_p->password) > 0 )
		cp = stpcpy(cp, pap_p->password);

	req_bp->cnt += len;
	return(req_bp);
}


/****************************************************************************/

/* abandon PAP attempt; shutdown LCP layer */
static void
pap_shutdown(fsm_p)
struct fsm_s *fsm_p;
{
	struct ppp_s *ppp_p = fsm_p->ppp_p;

	PPP_DEBUG_ROUTINES("pap_shutdown()");

	if (PPPtrace > 1)
		fsm_log( fsm_p, "Failed; close PPP connection" );

	ppp_p->phase = pppTERMINATE;
	psignal(ppp_p, 0);
}


/* Configuration negotiation complete */
static void
pap_opening(fsm_p, flag)
struct fsm_s *fsm_p;
int flag;
{
	register struct ppp_s *ppp_p = fsm_p->ppp_p;

	fsm_log(fsm_p, "Open");

	stop_timer(&(fsm_p->timer));

	if ( !((fsm_p->flags &= ~flag) & (PPP_AP_LOCAL | PPP_AP_REMOTE)) ) {
		fsm_p->state = fsmOPENED;
	}
	if ( !((ppp_p->flags &= ~flag) & (PPP_AP_LOCAL | PPP_AP_REMOTE)) ) {
		ppp_p->phase = pppREADY;
		psignal(ppp_p, 0);
	}
}


/****************************************************************************/
/* Check request from remote host */
static int
pap_request(fsm_p, hdr, data)
struct fsm_s *fsm_p;
struct config_hdr *hdr;
struct mbuf *data;
{
	struct pap_s *pap_p = fsm_p->pdv;
	struct mbuf *reply_bp;
	int result;
	char *message;
	int mess_length;
	char *username = NULLCHAR;
	int userlen;
	char *password = NULLCHAR;
	int passwordlen;

	PPP_DEBUG_ROUTINES("pap_request()");

	/* Extract userID/password sent by remote host */
	if ( (userlen = pullchar(&data)) != -1 ) {
		register int i;
		register char *cp;

		cp = username = mallocw(userlen+1);
		for ( i = userlen; i-- > 0; ) {
			*cp++ = PULLCHAR(&data);
		}
		*cp = '\0';
	}

#ifdef PPP_DEBUG_OPTIONS
	if (PPPtrace & PPP_DEBUG_OPTIONS)
		log(-1,"    checking user: %s", username);
#endif

	if ( (passwordlen = pullchar(&data)) != -1 ) {
		register int i;
		register char *cp;

		cp = password = mallocw(passwordlen+1);
		for ( i = passwordlen; i-- > 0; ) {
			*cp++ = PULLCHAR(&data);
		}
		*cp = '\0';
	}

#ifdef PPP_DEBUG_OPTIONS
	if (PPPtrace & PPP_DEBUG_OPTIONS)
		log(-1,"    checking password: %s", password);
#endif

	if (pap_verify(username,password) == 0) {
		free( pap_p->peername );
		pap_p->peername = strdup(username);
		result = CONFIG_ACK;
		message = " Welcome";
	} else {
		result = CONFIG_NAK;
		message = " Invalid username or password";
	}

	/* the space at the beginning of the message is crucial */
	mess_length = strlen(message);
	reply_bp = qdata(message,mess_length);
	reply_bp->data[0] = (char)(mess_length - 1);

	fsm_send(fsm_p, result, hdr->id, reply_bp);

	if (result == CONFIG_NAK) {
		if ( fsm_p->retry_nak > 0 ) {
			fsm_p->retry_nak--;
		} else {
			pap_shutdown(fsm_p);
		}
	}
	free_p(data);
	free(username);
	free(password);
	return (result != CONFIG_ACK);
}


/* Check acknowledgement from remote host */
static int
pap_check(fsm_p, hdr, data)
struct fsm_s *fsm_p;
struct config_hdr *hdr;
struct mbuf *data;
{
	struct pap_s *pap_p = fsm_p->pdv;
	char *message;
	int mess_length;
	int full_length;
	int len;

	PPP_DEBUG_ROUTINES("pap_check()");

	/* ID field must match last request we sent */
	if (hdr->id != fsm_p->lastid) {
		PPP_DEBUG_CHECKS("PAP: wrong ID");
		tprintf ("id mismatch hdrid=%d, lastid=%d\n",
			hdr->id, fsm_p->lastid);
		free_p(data);
		return -1;
	}

	/* Log ASCII message from remote host, if any */
	if ( (mess_length = pullchar(&data)) != -1 ) {
		message = mallocw( mess_length+1 );
		full_length = len_p(data);
		len = dqdata(data, message, mess_length);
		message[len] = '\0';

		free( pap_p->message );
		pap_p->message = message;

		if (PPPtrace) {
			log(-1,"%s: PPP/PAP %s %s: %s",
				fsm_p->ppp_p->iface->name,
				(len < mess_length) ? "Short"
				   : (mess_length < full_length) ? "Long"
					: "Valid",
				(hdr->code == CONFIG_ACK) ? "Ack" : "Nak",
				message);
		}
		return (len < mess_length  ||  mess_length < full_length);
	}
	free_p(data);
	PPP_DEBUG_CHECKS( "PAP: missing message count" );
	return -1;
}


/************************************************************************/
/*			E V E N T   P R O C E S S I N G			*/
/************************************************************************/

/* Process incoming packet */
void
pap_proc(fsm_p,bp)
struct fsm_s *fsm_p;
struct mbuf *bp;
{
	struct pap_s *pap_p = fsm_p->pdv;
	struct config_hdr hdr;

	PPPtrace = fsm_p->ppp_p->trace;

	ntohcnf(&hdr, &bp);
	if (PPPtrace > 1)
		log(-1,	"%s: PPP/%s Recv,"
			"  option: %s, id: %d, len: %d",
			fsm_p->ppp_p->iface->name,
			fsm_p->pdc->name,
			fsmCodes[hdr.code],
			hdr.id,	hdr.len);

	hdr.len -= CONFIG_HDR_LEN;		/* Length includes envelope */
	trim_mbuf(&bp, hdr.len);		/* Trim off padding */

	switch(hdr.code) {
	case CONFIG_REQ:
		if ( pap_request(fsm_p, &hdr, bp) == 0) {
			pap_opening(fsm_p, PPP_AP_LOCAL);
		}
		break;

	case CONFIG_ACK:
		if (pap_check(fsm_p, &hdr, bp) == 0) {
			pap_opening(fsm_p, PPP_AP_REMOTE);
		}
		break;

	case CONFIG_NAK:
		if (pap_check(fsm_p, &hdr, bp) == 0) {
			/* Must have sent a bad user or password */
			/* Get the password again */
			pap_getpassword(fsm_p, TRUE);

			if (pap_p->username == NULLCHAR
			 || pap_p->password == NULLCHAR) {
				pap_shutdown(fsm_p);
			} else {
				fsm_sendreq(fsm_p);
			}
		}
		break;

	default:
		if (PPPtrace)
			log(-1, "%s: PPP/Pap: Unknown packet type: %d;"
				" dropping packet",
				fsm_p->ppp_p->iface->name,
				hdr.code);
		free_p(bp);
		break;
	}
}


/* Timeout while waiting for reply from remote host */
static void
pap_timeout(vp)
void *vp;
{
	struct fsm_s *fsm_p = (struct fsm_s *)vp;

	PPPtrace = fsm_p->ppp_p->trace;

	fsm_log( fsm_p, "Timeout" );

	if (fsm_p->retry > 0) {
		fsm_sendreq(fsm_p);
	} else {
		fsm_log(fsm_p, "Request retry exceeded");
		pap_shutdown(fsm_p);
	}
}


/************************************************************************/
/*			I N I T I A L I Z A T I O N			*/
/************************************************************************/

/* Reset state machine */
static void
pap_reset(fsm_p)
struct fsm_s *fsm_p;
{
	PPP_DEBUG_ROUTINES("pap_reset()");

	fsm_p->state = fsmCLOSED;
	fsm_p->retry = fsm_p->try_req;
	fsm_p->retry_nak = fsm_p->try_nak;
}


/* Initialize state machine for local */
int
pap_local(ppp_p)
struct ppp_s *ppp_p;
{
	struct fsm_s *fsm_p = &(ppp_p->fsm[Pap]);

	if (ppp_p->fsm[Pap].pdv == NULL)
		pap_init(ppp_p);

	PPPtrace = fsm_p->ppp_p->trace;

	PPP_DEBUG_ROUTINES("pap_local()");

	pap_reset(fsm_p);
	fsm_p->state = fsmLISTEN;
	fsm_p->flags |= PPP_AP_LOCAL;
	return 0;
}


/* Initialize state machine for remote */
int
pap_remote(ppp_p)
struct ppp_s *ppp_p;
{
	struct fsm_s *fsm_p = &(ppp_p->fsm[Pap]);
	struct pap_s *pap_p = fsm_p->pdv;

	if (ppp_p->fsm[Pap].pdv == NULL)
		pap_init(ppp_p);

	PPPtrace = fsm_p->ppp_p->trace;

	PPP_DEBUG_ROUTINES("pap_remote()");

	/* We need to send REQ to remote host */
	/* Get the user and password we will send */
	if ((pap_p->username == NULLCHAR)
	 || (pap_p->password == NULLCHAR)) {
		pap_getpassword(fsm_p, FALSE);

		if ((pap_p->username == NULLCHAR)
		 || (pap_p->password == NULLCHAR)) {
			return -1;
		}
	}

	pap_reset(fsm_p);
	fsm_p->state = fsmREQ_Sent;
	fsm_p->flags |= PPP_AP_REMOTE;
	return(fsm_sendreq(fsm_p));
}


void
pap_down(fsm_p)
struct fsm_s *fsm_p;
{
	if ( fsm_p->pdv == NULL )
		return;

	PPPtrace = fsm_p->ppp_p->trace;

	fsm_log(fsm_p, "Down");

	fsm_p->flags = FALSE;

	switch ( fsm_p->state ) {
	case fsmREQ_Sent:
		stop_timer(&(fsm_p->timer));
		/* fallthru */
	case fsmOPENED:
	case fsmLISTEN:
	case fsmTERM_Sent:
		fsm_p->state = fsmCLOSED;
		break;

	case fsmCLOSED:
		/* Already closed; nothing to do */
		break;
	}
}


static void
pap_free(fsm_p)
struct fsm_s *fsm_p;
{
	struct pap_s *pap_p = fsm_p->pdv;

	free( pap_p->username );
	free( pap_p->password );
	free( pap_p->peername );
	free( pap_p->message );
}


/* Initialize configuration structure */
static void
pap_init(ppp_p)
struct ppp_s *ppp_p;
{
	struct fsm_s *fsm_p;
	struct timer *t;

	PPPtrace = ppp_p->trace;

	PPP_DEBUG_ROUTINES("pap_init()");

	if (ppp_p->fsm[Pap].pdv != NULL)
		return;		/* already initialized */

	fsm_p = &(ppp_p->fsm[Pap]);
	fsm_p->ppp_p = ppp_p;
	fsm_p->pdc = &pap_constants;
	fsm_p->pdv = callocw(1,sizeof(struct pap_s));

	fsm_p->try_req = fsm_p->pdc->try_req;
	fsm_p->try_nak = fsm_p->pdc->try_nak;
	fsm_p->try_terminate = fsm_p->pdc->try_terminate;

	pap_reset(fsm_p);

	/* Initialize timer */
	t = &(fsm_p->timer);
	t->func = (void (*)())pap_timeout;
	t->arg = (void *)fsm_p;
	set_timer(t, fsm_p->pdc->timeout);
	fsm_timer(fsm_p);
	stop_timer(t);
}


