/*
**  Copyright (c) 1991 Bolt Beranek and Newman, Inc.
**  All rights reserved.
**
**  Redistribution and use in source and binary forms are permitted
**  provided that: (1) source distributions retain this entire copyright
**  notice and comment, and (2) distributions including binaries display
**  the following acknowledgement:  ``This product includes software
**  developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the
**  documentation or other materials provided with the distribution and in
**  all advertising materials mentioning features or use of this software.
**  Neither the name of Bolt Beranek and Newman nor CREN/CSNET may be used
**  to endorse or promote products derived from this software without
**  specific prior written permission.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
**  WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
 * Copyright (c) 1992 Purdue University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived * from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <termios.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/sockio.h>
#include <sys/dlpi.h>
#include <time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <net/if.h>

#include "ppp.h"
#include "dp.h"
#include "dpio.h"
#include "dpd.h"

#ifdef	sun
#define GETDATAVAL(ifr)		(*(int *)ifr.ifr_data)
#define SETDATAVAL(ifr, i)	(*(int *)(ifr).ifr_data = (i))
#else
#define GETDATAVAL(ifr)		((int)ifr.ifr_data)
#define SETDATAVAL(ifr, i)	((ifr).ifr_data = (caddr_t)(i))
#endif	/* sun */

/*
** Local Variables
*/
static char	tty[12];	/* Modem in use				*/
static int	mypid;
static int	dialfout;	/* Modem descriptor			*/
static struct ifreq ifr;
static int	sock;		/* The global socket for use everywhere	*/
static time_t	starttime;	/* Time call started			*/
static time_t	stoptime;	/* Time call stopped			*/
static dp_stats_t startds;	/* Packet/byte/error counts at start	*/
static dl_unitdata_ind_t *calldlu; /* DL_UNITDATA_IND and packet...	*/
static char 	*callreq;	/* ...that initiated this call		*/
static REMOTE	*RP;


extern char	*strerror();
extern void	d_vlog(),
		writelog();


/*
 * Started on prototypes..  Should do more..
 */
#ifdef	__STDC__
static void set_rot_mod(MODEM *, int);
#else
static void set_rot_mod(/* MODEM *, int */);
#endif

#include <varargs.h>

/*
**  Log that a call failed.
*/
void
failcall(va_alist)
va_dcl
{
    char		*mesg;
    char		buff[256], *b = buff;
    static char		WHERE[] = "failcall";
    va_list		ap;
    char		stat;
    REMOTE		*rp;
    MODEM		*mp;
    void		if_callstat();

    va_start(ap);
    rp = va_arg(ap, REMOTE *);
    mp = va_arg(ap, MODEM *);
    stat = va_arg(ap, char);
    mesg = va_arg(ap, char *);

    if_callstat(rp, stat);

    (void)sprintf(b, "%s (%s)", rp->Device, rp->Sitename);
    b += strlen(b);
    if (mp) {
	(void)sprintf(b, " via %s", mp->Line);
	b += strlen(b);
    }
    (void)sprintf(b, ": %s", mesg);
    d_vlog(DLOG_GENERAL, WHERE, buff, ap);
    va_end(ap);
}

extern void collect_child(),
	    collect_ppp();

/*
**  Catch signal and exit.
*/
void
hangup(sig)
    int			sig;
{
    static char	WHERE[] = "hangup";
    long		totalpkts;
    struct termios	termios;
    static long		duration;	/* Duration of call in seconds	*/
    dp_stats_t		stopds;

    /* Now ignore the signals since we're shutting down. */
    (void)signal(SIGHUP, SIG_IGN);
    (void)signal(SIGTERM, SIG_IGN);
    (void)signal(SIGCHLD, SIG_IGN);
    if (sig)
	d_log(DLOG_ALL, WHERE, "Process %d got signal %d for \"%s\"",
	    mypid, sig, RP->Device);

    /* Let go of the lock file */
    (void)uu_unlock(tty);
    unlock_pid();

    /* Log the statistics */
    (void)time(&stoptime);
    duration = stoptime - starttime;

    getpacketcounts(RP, &stopds);
    stopds.dps_ibytes   -= startds.dps_ibytes;
    stopds.dps_ipackets -= startds.dps_ipackets;
    stopds.dps_ierrors  -= startds.dps_ierrors;
    stopds.dps_obytes   -= startds.dps_obytes;
    stopds.dps_opackets -= startds.dps_opackets;
    stopds.dps_oerrors  -= startds.dps_oerrors;

    writelog(RP->Sitename, tty, starttime, stoptime,
	     &stopds, calldlu, callreq);

    totalpkts = stopds.dps_ipackets + stopds.dps_opackets;
    d_log(DLOG_INFO, WHERE, "Outbound interface \"%s\" up %ld seconds",
	RP->Device, duration);
    d_log(DLOG_DIAG, WHERE,
	"Inpackets = %ld, Outpackets = %ld, Average = %.2f packets/sec",
	stopds.dps_ipackets, stopds.dps_opackets,
	duration ? ((float)totalpkts) / ((float)duration) : 0);


    /*
     * Terminate the Point to Point Protocol, and make sure that
     * the PPP program has exitted.
     */
    kill_ppp(sig);
    collect_ppp();

    /* Drop DTR. */
    if (tcgetattr(dialfout, &termios) < 0) {
	if (errno == ENXIO)
	    goto hangup_done;
	d_log(DLOG_GENERAL, WHERE, "tcgetattr() failed on \"%s\", %m", RP->Device);
	exit(17);
    }

    cfsetospeed(&termios, B0);

    /*
     * Terminate the Point to Point Protocol, and make sure that
     * the PPP program has exitted.
     */
    kill_ppp(sig);
    collect_ppp();

    /* Drop DTR. */
    if (tcgetattr(dialfout, &termios) < 0) {
	if (errno == ENXIO)
	    goto hangup_done;
	d_log(DLOG_GENERAL, WHERE, "tcgetattr() failed on \"%s\", %m", RP->Device);
	exit(18);
    }

    cfsetospeed(&termios, B0);
    if (tcsetattr(dialfout, TCSANOW, &termios) < 0) {
	if (errno == ENXIO)
	    goto hangup_done;
	d_log(DLOG_GENERAL, WHERE, "Can't drop call on \"%s\", %m", RP->Device);
	exit(19);
    }

hangup_done:
    /* All done, report that we made it. */
    d_log(DLOG_GENERAL, WHERE, "Disconnected \"%s\"", RP->Device);
    exit(0);
}


/*
 * Get a modem from the list of usable ones for this system and return
 * the first one that isn't already in use.  NOTE:  We must respect locks
 * made by uucp, tip, cmdf et al
 * Also, if the open fails with EBUSY, we assume that the modem is in use.
 * We return
 *	file descriptor	- success
 *	-1		- no available modems
 */

find_modem(re, rop, mop)
REMOTE *re;
MODEM **rop, **mop;
{
    register int i, j;
    MODEM *m, **mp, *ro;
    int md;

    for (i = re->NModems, mp = re->Modems ; i-- ; mp++) {
	ro = *mp;
	if (ro->NModems) {
	    ro->LastUsed = get_rot_mod(ro);
	    for (j = ro->NModems ; j-- ; ) {
		ro->LastUsed = (ro->LastUsed + 1) % ro->NModems;
		m = ro->Modems[ro->LastUsed];
		if ((md = open_modem(m)) >= 0) {
		    *rop = ro;
		    *mop = m;
		    set_rot_mod(ro, ro->LastUsed);
		    return md;
		}
	    }
	}
	else {
	    m = ro;
	    if ((md = open_modem(m)) >= 0) {
		*rop = (MODEM *)0;
		*mop = m;
		return md;
	    }
	}
    }
    *rop = *mop = (MODEM *)0;
    return -1;
}

/*
 * Lock and open a modem.  Return open file descriptor on success.
 * On failure, make sure we don't leave a lock, and return -1;
 */
open_modem(m)
MODEM *m;
{
    char devtty[64];
    int md;
    static char		WHERE[] = "open_modem";

    if (uu_lock(m->Line))
	return -1;

    (void)sprintf(devtty, "/dev/%s", m->Line);

    if ((md = open(devtty, O_RDWR)) < 0) {
	(void)uu_unlock(m->Line);
	if (errno != EBUSY)
	    d_log(DLOG_GENERAL, WHERE, "open failed on \"%s\", %m", m->Line);
    }
    return md;
}

/*
 * Get or set the last used modem in a given rotary.
 */
#define	MAXMODEMDIGS	4

char *
rot_file(ro)
MODEM *ro;
{
    return expand_dir_file("$DPROTARY_DIR", ro->Name);
}

get_rot_mod(ro)
MODEM *ro;
{
    int m, mn = ro->NModems - 1;
    char mnum[MAXMODEMDIGS+2];
    char *rfile;

    rfile = rot_file(ro);

    if ((m = open(rfile, O_RDONLY)) < 0) {
	(void)free(rfile);
	return mn;
    }
    if (read(m, mnum, sizeof(mnum)) != sizeof(mnum)) {
	(void)close(m);
	(void)free(rfile);
	return mn;
    }
    (void)close(m);
    (void)free(rfile);
    mn = atoi(mnum);
    return (mn >= 0 && mn < ro->NModems) ? mn : ro->NModems - 1;
}

static void
set_rot_mod(ro, mn)
MODEM *ro;
int mn;
{
    int m;
    char mnum[MAXMODEMDIGS+2];
    char *rfile;

    rfile = rot_file(ro);
    (void)sprintf(mnum, "%*d\n", MAXMODEMDIGS, mn);

    if ((m = open(rfile, O_WRONLY|O_CREAT, 0644)) >= 0) {
	(void)write(m, mnum, sizeof(mnum));
	(void)close(m);
    }
    (void)free(rfile);
}

#define	SPEED(rotary, modem)	\
	((modem)->Speed ? (modem)->Speed :	\
			  ((rotary) ? (rotary)->Speed : 0))
#define	DIALSCRIPT(rotary, modem)	\
	((modem)->DialScript ? (modem)->DialScript :	\
			       ((rotary) ? (rotary)->DialScript : (char *)0))

#define	DIALCHARMAP(rotary, modem)	\
	((modem)->DialCharMap ? (modem)->DialCharMap :	\
			       ((rotary) ? (rotary)->DialCharMap : (char *)0))

#define	ASYNCMAP(remote, rotary, modem)	\
	((remote)->AsyncMap |	\
	 ((rotary) ? (rotary)->AsyncMap : 0) |	\
	 (modem)->AsyncMap)


static int
makecall_real(rp, dlu, pkt)
    REMOTE		*rp;
    dl_unitdata_ind_t	*dlu;
    char		*pkt;
{
    static char		WHERE[] = "makecall";
    char		devtty[30];
    FILE		*fp;
    struct termios	termios;
    char		*tname;
    int			i;
    MODEM		*rotp, *mp;
    int			baudext;
    int			speed;

#if	0
    /* Open our own path to the terminal and close the old connections */
    if ((i = open("/dev/tty", O_RDONLY)) >= 0) {
	(void)ioctl(i, TIOCNOTTY, (caddr_t)0);
	(void)close(i);
    }
#else
    (void)setsid();
#endif
    (void)fclose(stdin);
    (void)fclose(stdout);

    (void)signal(SIGHUP, SIG_IGN);
    (void)signal(SIGTERM, SIG_IGN);
    (void)signal(SIGCHLD, collect_child);

    /*
     * The find_modem function will attempt to find a modem that is not in
     * use. On success, it will be "uu_lock"ed.
     */
    if ((dialfout = find_modem(rp, &rotp, &mp)) < 0) {
	failcall(rp, mp, DP_NO_MODEM, "No modem -- check lock files");
	return CALL_NOMODEM;
    }

    RP = rp;
    (void)strcpy(tty, mp->Line);
    (void)sprintf(devtty, "/dev/%s", tty);
    record_pid(rp->Device, getpid(), -1);
    calldlu = dlu;
    callreq = pkt;
    getpacketcounts(rp, &startds);

    /* Get the TTY modes. */
    if (tcgetattr(dialfout, &termios) < 0) {
	failcall(rp, mp, "tcgetattr failed, %s", strerror(errno));
	(void)uu_unlock(tty);
	return CALL_IOCTL_ERR;
    }

    /*						peter@micromuse.co.uk
     * Set the line into an 8-bit clean state. Maybe someone wants
     * to add a 'parity' option to the config files ?
     */

    termios.c_iflag |=   IGNPAR;
    termios.c_oflag &= ~ OPOST;
    termios.c_cflag &= ~ (PARENB | CSIZE);
    termios.c_cflag |=   (CS8 | HUPCL);
    termios.c_lflag &= ~ (ICANON | ECHO);
    termios.c_cc[VMIN]  = 1;
    termios.c_cc[VTIME] = 0;
    speed = mapspeed(SPEED(rotp, mp), &baudext);
    cfsetospeed(&termios, speed);
    cfsetispeed(&termios, 0);


    /* Set the new mode. */
    if (tcsetattr(dialfout, TCSANOW, &termios) < 0) {
	failcall(rp, mp, "tcsetattr failed, %s", strerror(errno));
	(void)uu_unlock(tty);
	return CALL_IOCTL_ERR;
    }

#ifdef	STS
    /*
     * This is an ugly extension of the normal UNIX baud rates for use
     * on Central Data SCSI terminal servers.  It's not pretty, but
     * it works.
     */
    if (stsbaudext(dialfout, baudext) < 0 && baudext > 0) {
	failcall(rp, mp, "Central Data Baud Extension ioctl failed, %s",
		 strerror(errno));
	(void)uu_unlock(tty);
	return CALL_IOCTL_ERR;
    }
#endif

    /* Open up the modem port. */
    if ((fp = fdopen(dialfout, "r+")) == NULL) {
	failcall(rp, mp, DP_FAILURE, "fdopen failed, %s", strerror(errno));
	(void)uu_unlock(tty);
	return CALL_PERMISSION;
    }

    /* Set up the name of the transaction file. */
    tname = rp->Transcript[0] ? rp->Transcript : NULL;

    /* Now run the script. */
    d_log(DLOG_GENERAL, WHERE, "Dialing %s (%s) via %s",
	rp->Device, rp->Sitename, tty);
    if ((i = runscript(rp, fp, DIALSCRIPT(rotp, mp), tname,
		       DIALCHARMAP(rotp, mp))) < 0) {
	failcall(rp, mp, DP_FAILURE, "script %s failed",
		 (i == -1) ? DIALSCRIPT(rotp, mp) : rp->Script);
	(void)uu_unlock(tty);
	return CALL_FAIL;
    }

#if	0
    /* Connection set up, set the line discipline. */
    if ((stat = duconnect(rp->Device)) != CALL_SUCCESS) {
	(void)uu_unlock(tty);
	return stat;
    }
#endif

    /* Connection made - get things going */
    d_log(DLOG_GENERAL, WHERE, "Connected to \"%s\" via \"%s\"",
	rp->Sitename, rp->Device);

    if (start_ppp(rp, devtty, 0, ASYNCMAP(rp, rotp, mp)) < 0) {
	d_log(DLOG_GENERAL, WHERE, "Failed to start PPP to \"%s\"",
	      rp->Sitename);
	return CALL_FAIL;
    }

    d_log(DLOG_DIAG, WHERE, "PPP started to \"%s\"", rp->Sitename);

    return CALL_SUCCESS;
}

int
makecall(rp, dlu, pkt)
    REMOTE		*rp;
    dl_unitdata_ind_t	*dlu;
    char		*pkt;
{
    static char		WHERE[] = "makecall";
    int			uptime;
    int			old_if;
    time_t		now;

    /* Record the PID */
    mypid = getpid();

    (void)if_dtimeouts(rp);

    if (makecall_real(rp, dlu, pkt) != CALL_SUCCESS)
	return;


    /* Create a socket for gathering info. */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	d_log(DLOG_GENERAL, WHERE, "Can't create socket for \"%s\", %m",
	    rp->Device);
	unlock_pid();
	exit(1);
    }

    /* Set up info, collect starting stats. */
    (void)strcpy(ifr.ifr_name, rp->Device);
    (void)time(&starttime);

    /* Now keep tabs on the interface */
    for (old_if = 0, uptime = 0; ; ) {
	/* See if the IF flags changed. */
	if (ioctl(sock, SIOCGIFFLAGS, (caddr_t)&ifr) < 0)
	    d_log(DLOG_GENERAL, WHERE, "Can't SIOCGIFFLAGS, %m");
	else if (ifr.ifr_flags != old_if) {
	    d_log(DLOG_ALL, WHERE,
		"\"%s\" IF flags changed from 0x%04x to 0x%04x",
		rp->Device, old_if, ifr.ifr_flags);
	    old_if = ifr.ifr_flags;
	}

#if	0
	/* See if the soft flags changed. */
	if (ioctl(sock, SIOCGSOFTFLAGS, (caddr_t)&ifr) < 0)
	    d_log(DLOG_GENERAL, WHERE, "Can't SIOCGSOFTFLAGS, %m");
	else if (GETDATAVAL(ifr) != old_soft) {
	    d_log(DLOG_ALL, WHERE,
		"\"%s\" SOFT flags changed from 0x%04x to 0x%04x",
		rp->Device, old_soft, GETDATAVAL(ifr));
	    old_soft = GETDATAVAL(ifr);
	}
#endif

	(void)sleep(UPTIME_INTERVAL * 60);
	(void)time(&now);
	uptime += UPTIME_INTERVAL;
	d_log(DLOG_INFO, WHERE, "Outbound interface \"%s\" up %d:%02d",
	    rp->Device, (now - starttime)/60, (now - starttime)%60);
    }
}

mapspeed(speed, ext)
int speed;
int *ext;
{
    *ext = 0;
    switch (speed) {
     case 1200:			return B1200;
     case 2400:			return B2400;
     case 4800:			return B4800;
     case 9600:			return B9600;
     case 19200:		return B19200;
     case 38400:		return B38400;
#ifdef	STS
     case 57600:    *ext = 1;	return B38400;
#endif	STS
#ifdef	FASTZS
     case 76800:    		return B50;
#endif	FASTZS
     default:		return -1;
    }
}
