/*************************************************************************

		Ascend Password Protocol (APP) Server
		For use with Software realease 4.5 or later.

		Ascend Communications, Inc

*************************************************************************/

#include <stdio.h>		/* the usual suspects */
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/socket.h>
#if defined(SUNOS)
# include <sys/socketvar.h>
#endif
#if defined(SUNOS) || defined(BSDI)
# include <errno.h>
#endif
#include <netinet/in.h>		/* inet_addr() and friends */
#include <arpa/inet.h>
#include <netdb.h>		/* gethostbyname() and friends */
#include <sys/param.h>		/* MAXHOSTNAMELEN */
#include <memory.h>		/* memset() */
#include <stdarg.h>		/* variable argument list */
#include <time.h>		/* time, ctime */
#include <signal.h>
#include <setjmp.h>
#include <syslog.h>
#include <termios.h>
#include <string.h>

#ifdef SUNOS
  extern int openlog (char *s, int f, int l); 
  extern int socket (int a, int t, int p); 
  extern int syslog (int l, char *f, ...); 
  extern int getsockname (int s, struct sockaddr *name, int *namelen); 
  extern int printf (char *s, ...); 
  extern int recvfrom (int s, char *b, int l, int f,
		     struct sockaddr *fr, int *fl); 
  extern int fflush (struct _iobuf *f); 
  extern int fprintf (struct _iobuf *f, char *s, ...); 
  extern int sendto (int s, char *m, int l, int f,
		   struct sockaddr *t, int tl); 
  extern int perror (char *s); 
  extern int bind (int s, struct sockaddr *n, int l); 
  extern int gethostname (char *n, int l); 
  extern char *index (char *s, char c); 
  extern int getopt (int argc, char **argv, char *optstr); 
  extern int _filbuf (struct _iobuf *f); 
  extern int setsockopt(int s, int level, int optname,
     			char *optval, int optlen);
#endif

#if defined(SOLARIS)
	/* for some reason these don't already have prototypes under Solaris */
  extern int gethostname (char *n, int l); 
  extern int getopt (int, char *const *, const char *);
  extern int kill (pid_t pid, int sig);
#endif

	/* Program return values */
typedef enum retvals {
	ARG_ERR	= -100,
	FILE_ERR,
	MALLOC_ERR,
	INPUT_ERR,
	HELP_ERR,
	HOST_ERR,
	SERVENT_ERR,
	SOCK_CREATE_ERR,
	SOCK_SENDTO_ERR,
	SOCK_READ_ERR,
	SOCK_WRITE_ERR,
	SOCK_RECVFROM_ERR,
	SOCK_SETOPT_ERR,
	BIND_ERR,
	CONNECT_ERR,
	LISTEN_ERR,
	ACCEPT_ERR,
	APP_BAD_TYPE,
	SUCCESS	= 0
} retvals;

	/* typedef for file descriptor */
typedef int	fd_T;

	/* typedef for socket descriptor */
typedef int	sd_T;

	/* typedef 'Boolean' for consistent usage */
typedef enum bool {
	FALSE,
	TRUE
} Boolean;

	/* some useful IP typedefs */
typedef struct sockaddr		sockaddr_T;
typedef struct sockaddr_in	sockaddr_in_T;
typedef struct hostent		hostent_T;
typedef struct in_addr		in_addr_T;
typedef struct servent		servent_T;


#define	PROGNAME		"appServer"
#define SERVICE_NAME		"appServer"
#define SERVICE_PROTO		"udp"
#define DEFAULT_SERVICE_PORT	(7001)	/* your choice */

#define FROMNAME_LEN		(64)
#define MAX_CHALLENGE_LEN	(64)
#define MAX_PASSWORD_LEN	(64)
#define VARS_MAX		(FROMNAME_LEN + MAX_CHALLENGE_LEN)
#define MAX_APP_FRAMELEN	(sizeof(AppFrame) + VARS_MAX + 1)
#define NIL			((void *)0)


#define PRINTF( x )     if (_debug) printf x;

static int broadcasting = 0;
static int _debug = 0;
static int fakeflag = 0;
static int xflag = 0;
static int sflag = 0;
jmp_buf jmpbuf;

#if defined(SOLARIS)
void sighand(int sig)
{
    longjmp(jmpbuf, sig);
}
#else
void sighand(int sig, int code, struct sigcontext *scp, char *addr)
{
    longjmp(jmpbuf, sig);
}
#endif



    /*
     * Format of frames sent between the PAP_CHALLENGE_CLIENT
     * and the Host-based Password Server
     *
     * type:		Challenge Request or Password Response
     *
     * id:		sequence number
     *
     * totalLen:	total length of frame
     *
     * reqIpAddr:	Pipeline IP address
     *
     * reqIpPort:	Pipeline Port address
     *
     * fromLen:		length of remote unit name
     *
     * from:		name of remote (hub) unit
     *
     * msgLen:		length of contained message
     *
     * msg:		contained message
     *
     * NB: the from field immediately follows the fromLen field
     *     the msgLen and msg fields follow the from field
     *
     */
typedef struct AppFrame {
	u_char		type;		/* Chall Req or Pwd Rsp */
	u_char		id;		/* frame identifier */
	u_short		totalLen;	/* total frame length */
	u_long		reqIpAddr;	/* IP addr of requestor */
	u_short		reqIpPort;	/* IP port of requestor */
	u_char		fromLen;	/* length of remote name */
#if( VARIABLE_LENGTH_FIELDS_IN_STRUCTS )
	char		[];		/* remote router name */
	u_char		msgLen;		/* len of contained msg */
	char		msg[];		/* the contained msg */
#endif
} AppFrame;

    /* well this is because of struct packing (or lack thereof) */
#define APP_FLEN(f) ((&(f)->fromLen - &(f)->type) + 1)	/* Gack, thhpppt */

    /* values for AppFrame.type */
typedef enum FrameTypes {
	challengeReq	= 1,
	passwordResp	= 2
} FrameTypes;


    /*
     * Ascend Password Protocol Info
     *
     * lastReqLen:	length of the last challenge frame
     *
     * lastReq:		pointer to last challenge frame
     *
     * lastRspLen:	length of the last response frame
     *
     * lastRsp:		pointer to last response frame
     *
     * sockid:		our socket identifier
     *
     * localaddr:	our local address info
     *
     * remoteaddr:	our remote address info
     *
     */
typedef struct AppInfo {
    int			lastReqLen;	/* length of last request frame */
    AppFrame		*lastReq;	/* for checking duplicate requests */
    int			lastRspLen;	/* length of last response frame */
    AppFrame		*lastRsp;	/* if needed for retransmission */
    sd_T		sockid;		/* our socket identifier */
    sockaddr_in_T	localaddr;	/* our local address info */
    sockaddr_T		remoteaddr;	/* our remote address info */
} AppInfo;

	/* keep all the APP info here */
AppInfo	_appInfo;

#define PRINTSIZE	(128)


	/* function prototypes */
void main(int argc, char **argv);
static int _appServerInit(AppInfo *app);
static int _appServer(AppInfo *app);
static Boolean _isDuplicate(AppInfo *app, AppFrame *rcvReq);
static int _processDupMsg(AppInfo *app);
static int _processNewMsg(AppInfo *app, AppFrame *rcvReq);
static int _txFrame(AppInfo *app, AppFrame *frame, int len);
static int _bindSocket(sd_T sock, sockaddr_in_T *hostaddr);

	/*
	 * Main program entry
	 */
void
main(int argc, char **argv)
{
    int stat;
    int c,
    errflag = 0;

    /* 
     * extern char *optarg;
     * extern int optind;
     */

    while ((c = getopt(argc, argv, "bdxsf")) != -1)
    {
	switch (c) {
	case 'b':
	    broadcasting++;
	    break;
	case 'd':
	    _debug++;
	    break;
	case 'f':
	    fakeflag++;
	    break;
	case 's':
	    sflag++;
	    break;
	case 'x':
	    xflag++;
	    break;
	case '?':
	    errflag++;
	}
    }
    
    if (errflag) {
	(void)fprintf(stderr, "usage: %s [-bdfsx]\n", argv[0]);
	exit (2);
    }

    openlog("appsrvr", LOG_ODELAY, LOG_AUTH);
    
	/* init the Ascend Password Protocol Info */
    _appInfo.lastReqLen	= 0;
    _appInfo.lastReq	= NULL;
    _appInfo.lastRspLen	= 0;
    _appInfo.lastRsp	= NULL;
    _appInfo.sockid	= 0;
    memset(&_appInfo.localaddr, 0, sizeof(_appInfo.localaddr));
    memset(&_appInfo.remoteaddr, 0, sizeof(_appInfo.remoteaddr));

	/* init the server */
    stat = _appServerInit(&_appInfo);
    if( stat >= SUCCESS ) {	/* invoke the server */
	stat = _appServer(&_appInfo);
    }

    if( _appInfo.lastReq ) {
    	free(_appInfo.lastReq);
    }
    if( _appInfo.lastRsp ) {
    	free(_appInfo.lastRsp);
    }

    exit( stat );
}

	/*
	 * Initialise the server
	 */
static int
_appServerInit(AppInfo *app)
{
    int		stat;		/* generic status var */
    int		arg;

	/* create a socket */
    if( (app->sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	perror("socket");
	return SOCK_CREATE_ERR;
    }

    arg = 1;
    if ( setsockopt( app->sockid, SOL_SOCKET, SO_REUSEADDR,
		     (char*) &arg, sizeof(arg)) < 0 ) {
	perror("setsockopt SO_REUSEADDR");
	return SOCK_SETOPT_ERR;
    }

    if( broadcasting ) {
	if ( setsockopt( app->sockid, SOL_SOCKET, SO_BROADCAST,
		     (char*) &arg, sizeof(arg)) < 0 ) {
	    perror("setsockopt SO_BROADCAST");
	    return SOCK_SETOPT_ERR;
	}
    }

    /* bind the socket to a port */
    if( (stat = _bindSocket(app->sockid, &app->localaddr)) != SUCCESS ) {
	return stat;
    } else {
	int len = sizeof(app->localaddr);
	if( getsockname(app->sockid, (sockaddr_T *)&app->localaddr, &len) != 0 ) {
	    perror("getsockname");
	    return BIND_ERR;
	} else {
		/* print server port info */
	    if (_debug > 1)
		PRINTF(("socket %d bound to port %d\n",
			app->sockid, ntohs(app->localaddr.sin_port)));
	}
    }
    return SUCCESS;
}

	/*
	 * Here it is:  All the real work occurs here
	 *
	 * Loop:
	 *	receive a request
	 *	service the request
	 * Never end!
	 *
	 */
static int
_appServer(AppInfo *app)
{
    int		stat;		/* generic status var */
    AppFrame	*rcvReq;	/* received APP frame */
    int		addrlen;
    int		lastId = 0;
    char	*data;

	/* loop forever:
		receive a message from a client
		process the message
		send the response to the client
	*/
    while( TRUE ) {
	rcvReq = malloc(MAX_APP_FRAMELEN);
	if( !rcvReq ) {
	    perror("malloc");
	    return MALLOC_ERR;
	}
	addrlen = sizeof(app->remoteaddr);
	memset(&app->remoteaddr, 0, addrlen);

	if (xflag) {			/* close icon */
	    printf("\033[2t");	/* openwin close sequence */
	    fflush( stdout );
	}

	if (fakeflag)
	{
	    sleep (10);

	    /* don't receive from the net, just fake a challenge */
	    rcvReq->type = challengeReq;
	    rcvReq->id = lastId++;
	    rcvReq->reqIpAddr = htonl(987654);
	    rcvReq->reqIpPort = htons(1357);
	    data = (char *)((char *)rcvReq + APP_FLEN(rcvReq));
	    if( gethostname(data, FROMNAME_LEN) ) {
		strcpy(data, "DUMMY");
	    }
	    rcvReq->fromLen = strlen(data) + 1;
	    data += rcvReq->fromLen;
	    *data = 27;
	    data++;

	    sprintf(data, "Challenge: 1 %06x", rand() & 0xffffff);

	    rcvReq->totalLen = stat = APP_FLEN(rcvReq)+rcvReq->fromLen+27+1;
	    memcpy(&app->remoteaddr, &app->localaddr, sizeof(app->remoteaddr));
	}
	else
	{
	    stat = recvfrom(app->sockid, (char *)rcvReq, MAX_APP_FRAMELEN, 0,
			    &app->remoteaddr, &addrlen);
	}

	if (xflag) {			/* open seasame */
	    printf("\033[1t");	/* openwindows open sequence */
	    fflush( stdout );
	}

	if( stat < (APP_FLEN(rcvReq) + 1) ) {
	    perror("recvfrom");
	    free(rcvReq);
	    continue;
	}

		/* validate the APP frame */
	if( rcvReq->type != challengeReq ) {
	    sockaddr_in_T *rsa = (sockaddr_in_T *)&app->remoteaddr;
	    if( rsa->sin_addr.s_addr != ~0L ) {
		syslog(LOG_ERR, "Bad msg type in rcvd message (%d).",
		   rcvReq->type);
	    }
	    free(rcvReq);
	    continue;
	}
	if( !_isDuplicate(app, rcvReq) ) {	/* new msg */
	    _processNewMsg(app, rcvReq);
	} else {				/* old msg */
	    free(rcvReq);
	    _processDupMsg(app);
	}
    }
    return SUCCESS;
}

	/*
	 * Check for duplicate request
	 */
static Boolean
_isDuplicate(AppInfo *app, AppFrame *rcvReq)
{
    if( !app->lastReq ) {
    	return FALSE;
    } else if( (rcvReq->id == app->lastReq->id)
	    && (rcvReq->reqIpAddr == app->lastReq->reqIpAddr)
	    && (rcvReq->reqIpPort == app->lastReq->reqIpPort) )
    {
	return TRUE;
    } else {
	return FALSE;
    }
}

	/*
	 * This is a duplicate request...just return the last
	 * response - don't bug the user for a new password
	 */
static int
_processDupMsg(AppInfo *app)
{
    char *from;
    u_char fromlen;
    char *msg;

    fromlen = app->lastReq->fromLen;
    from = (char *)((char *)app->lastReq + APP_FLEN(app->lastReq));
    msg = from + fromlen + 1;
    syslog(LOG_ERR,
	   "duplicate request, id: %d host: 0x%x port: %d from<%s>, challenge<%s>",
		app->lastReq->id,
	        ntohl(app->lastReq->reqIpAddr),
		ntohs(app->lastReq->reqIpPort),
		from, msg);

    return _txFrame(app, app->lastRsp, app->lastRspLen);
}


int readPassword(char  *buf, /* volatile for longjmp */
		  int   len,
		  int   timeout,
		  char  *from,
		  char  *prompt)
{
    int sig, s;
    volatile int rderror = 0;
    struct termios save, noecho;
    int status;
    
 again:
    if (tcgetattr(0, &save) < 0)
	perror("tcgetattr");

    noecho = save;
    noecho.c_lflag &= ~(ECHO|ECHONL);

    /*
     * make sure we get back to echo mode even if user types ^C, ^Z etc
     * (or other bad unpecified things happen.)
     */
    if ((sig = setjmp(jmpbuf)) == 0)
    {
	char *lbuf = buf;
	int   llen = len;
	
	for(s = 1 ; s < 32 ; s++)
	    (void) signal(s, sighand);

	if (tcsetattr(0, TCSANOW, &noecho) < 0)
	    perror("tcsetattr");

	alarm(timeout);

	/*
	 * Flush any stray garbage already in the input buffers.  
	 */

	tcflush(0, TCIFLUSH);
	printf("\007(%s) %s\nPassword: \007", from, prompt);
	fflush(stdout);

	while (1) {
	    char c;
	    int cnt;

	    cnt = read(0, &c, 1);
	    
	    if (sflag)		/* strip it */
		c &= 0x7f;

	    if (cnt <= 0)
	    {
		rderror = 1;
		break;
	    }

	    if ((c == '\n') || (c == '\r'))
		break;

	    if (llen > 1)	/* save one slot for '\0' */
	    {
		*lbuf++ = c;		
		llen--;
	    }
	}
	*lbuf = '\0';
    }

    alarm(0);

    if (tcsetattr(0, TCSANOW, &save) < 0)
	perror("tcsetattr");

    for(s = 1 ; s < 32 ; s++)
	(void) signal(s, SIG_DFL);

    if (sig) {
	memset(buf, 0, len);	/* timed out */
	status = 1;
    }
    else {
	status = 0;
    }

    if (sig == SIGALRM)
	printf(" *** timeout ***");
    else if (sig == SIGTSTP)
    {
	kill(getpid(), sig);
	goto again;
    }
    else if (sig == SIGINT)
    {
	printf(" *** interrupt ***\n");
	exit(1);
    }
    else if (sig == SIGQUIT)
    {
	printf(" *** quit ***\n");
	exit(1);
    }
    else if (sig)
    {
	printf(" *** signal %d ***\n", sig);
	exit(1);
    }
    printf("\n");
    fflush(stdout);

    if (rderror)
    {				/* EOF */
	exit(1);
    }

    return status;
}


	/*
	 * We have a received a new request for a dynamic
	 * password.  Display the info for the user and prompt
	 * for a new password, then return this info to the
	 * client.
	 */
static int
_processNewMsg(AppInfo *app, AppFrame *rcvReq)
{
    AppFrame *rsp;
    char pwd[MAX_PASSWORD_LEN];
    char *p;
    char *from;
    u_char fromlen;
    char *msg;
    int pwdlen;
    int frameSize;

    fromlen = rcvReq->fromLen;
    from = (char *)rcvReq;
    from += APP_FLEN(rcvReq);
    msg = from + fromlen + 1;

    if (_debug > 1)
    syslog(LOG_INFO,
	   "new request, id: %d, host: %ld, port: %d from<%s> challenge<%s>",
		rcvReq->id,
		ntohl(rcvReq->reqIpAddr),
		ntohs(rcvReq->reqIpPort),
		from, msg);

    if( app->lastReq ) {
    	free(app->lastReq);
    }

    app->lastReq = rcvReq;
    app->lastReqLen = rcvReq->totalLen;

    if( !readPassword(pwd, sizeof(pwd), 55, from, msg) ) {
	pwdlen = strlen(pwd) + 1;

	/* ha ha! now we print it */
	if (_debug)
	    fprintf(stderr, "debug: your password was '%s' (%d bytes)\n",
		pwd, pwdlen - 1);
    }
    else {	/* error */
	pwdlen = 0;
    }

    printf("\n");

    if (xflag) {		/* close it up again. */
	printf("\033[2t");
	fflush( stdout );
    }

    frameSize = APP_FLEN((AppFrame *)0) + fromlen + 1 + pwdlen;
    rsp = malloc(frameSize);
    if( !rsp ) {
    	return MALLOC_ERR;
    }

    rsp->type = passwordResp;
    rsp->id = rcvReq->id;
    rsp->totalLen = frameSize;
    rsp->reqIpAddr = rcvReq->reqIpAddr;
    rsp->reqIpPort = rcvReq->reqIpPort;
    rsp->fromLen = fromlen;
    p = (char *)rsp;
    p += APP_FLEN(rsp);
    strcpy(p, from);
    p += fromlen;		/* &rsp->msgLen */
    *p = pwdlen;
    p++;			/* &rsp->msg */
    memcpy(p, pwd, pwdlen);
    return _txFrame(app, rsp, rsp->totalLen);
}

	/*
	 * Transmit a password response to the requestor
	 */
static int
_txFrame(AppInfo *app, AppFrame *frame, int len)
{
    int stat;

    if( app->lastRsp && app->lastRsp != frame ) {
    	free(app->lastRsp);
    }

    app->lastRspLen = len;
    app->lastRsp = frame;

    if( broadcasting ) {
	sockaddr_in_T *rsa = (sockaddr_in_T *)&app->remoteaddr;
	if( rsa->sin_addr.s_addr == 0L ) {
	    /* convert to broadcast address */
	    rsa->sin_addr.s_addr = ~0L;
	}
    }
    stat = sendto(app->sockid, (char *)frame, len, 0,
    		&app->remoteaddr, sizeof(app->remoteaddr));
    if( stat != len ) {
	perror("socket");
	return SOCK_SENDTO_ERR;
    }

    if (_debug > 1)
    {
	char *host;
	u_char pwdLen;
	char *pwd;

	host = (char *)((char *)frame + APP_FLEN(frame));
	pwdLen = *(host + frame->fromLen);
	pwd = (char *)(host + frame->fromLen + 1);
	syslog(LOG_INFO, "txFrame: sent %d bytes, host<%s>, pwd<%s>",
	       len, host, pwd);
    }

    return SUCCESS;
}

	/*
	 *	Bind the local socket to a local port
	 */
int _bindSocket(sd_T sock, sockaddr_in_T *hostaddr)
{
    servent_T	*serviceEntry;

    memset(hostaddr, 0, sizeof(*hostaddr));
    hostaddr->sin_family = AF_INET;
    hostaddr->sin_addr.s_addr = htonl(INADDR_ANY);
    serviceEntry = getservbyname(SERVICE_NAME, SERVICE_PROTO);
    if( !serviceEntry ) {
	syslog(LOG_ERR,
	       "getservbyname: unknown service %s/%s, using default port %d\n",
	    SERVICE_NAME, SERVICE_PROTO, DEFAULT_SERVICE_PORT);
        hostaddr->sin_port = htons(DEFAULT_SERVICE_PORT);
    } else {
	hostaddr->sin_port = serviceEntry->s_port;
    }

    if( bind(sock, (sockaddr_T *)hostaddr, sizeof(*hostaddr)) != 0) {
	perror("bind");
	return BIND_ERR;
    }
    return SUCCESS;
}



