/*
 *	slattach(8) utility for MintNet (w) 1994, Kay Roemer.
 *
 *	Options:
 *
 *	-h			Don't hang the line up when exiting.
 *
 *	-e			Exit after setting up the connection.
 *
 *	-d			Don't add default route.
 *
 *	-t <termial line>	Specify terminal device to use. If this
 *				option is missing use stdin instead.
 *
 *	-r <remote host>	Specify the IP address of the remote host.
 *
 *	-l <local host>		Specify the IP address of the local host.
 *
 *	-p <protcol>		Specify protocol type (slip, cslip or ppp).
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <support.h>
#include <netdb.h>

#define _PATH_IFCONFIG	"/etc/ifconfig"
#define _PATH_ROUTE	"/etc/route"

#define SIOCSLNKFLAGS	(('S' << 8) | 25)
#define SIOCGLNKFLAGS	(('S' << 8) | 26)
#define	SLF_COMPRESS	0x08
#define SLF_AUTOCOMP	0x10

#define SIOCSIFLINK	(('S' << 8) | 11)

struct iflink {
	char	ifname[16];
	char	device[128];
};

static int ttyfd;
static char ifname[16];
static int exit_opt = 0, hangup_opt = 1, defrt_opt = 1;
static struct sgttyb savesg;

extern int errno;

/*
 * simple system() without shell invocation.
 */

int
mysystem (char *command, ...)
{
	int status;
	pid_t pid;

	pid = vfork ();
	if (pid < 0) {
		perror ("fork");
		exit (1);
	} else if (pid == 0) {
		execv (command, &command);
		fprintf (stderr, "cannot execute %s: %s\n", command,
			strerror (errno));
		exit (1);
	}
	if (waitpid (pid, &status, 0) < 0) {
		perror ("waitpid");
		exit (1);
	}
	return WIFEXITED (status) ? WEXITSTATUS (status) : 0;
}

/*
 * link device to network interface.
 */
static void
do_link (device, ifname)
	char *device, *ifname;
{
	extern int _unx2dos (const char *, char *);
	struct iflink ifl;
	int sockfd;
	long r;

	sockfd = socket (PF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0) {
		perror ("cannot open socket");
		exit (1);
	}
	_unx2dos (device, ifl.device);
	strncpy (ifl.ifname, ifname, sizeof (ifl.ifname));
	r = ioctl (sockfd, SIOCSIFLINK, &ifl);
	if (r < 0) {
		fprintf (stderr, "cannot link %s to an interface: %s\n",
			device, strerror (errno));
		exit (1);
	}
	close (sockfd);
	strncpy (ifname, ifl.ifname, sizeof (ifl.ifname));
}

/*
 * Setup routines for PPP, SLIP, (CSLIP).
 */

int
ppp_init (tty, iname)
	char *tty, *iname;
{
	strcpy (iname, "ppp");
	do_link (tty, iname);
	return 0;
}

int
slip_init (tty, iname)
	char *tty, *iname;
{
	struct ifreq ifr;
	int sock;

	strcpy (iname, "sl");
	do_link (tty, iname);

	sock = socket (PF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		perror ("cannot open socket");
		exit (1);
	}
	strcpy (ifr.ifr_name, iname);
	if (ioctl (sock, SIOCGLNKFLAGS, &ifr) < 0) {
		fprintf (stderr, "%s: cannot get LINK LEVEL FLAGS: %s\n",
			iname, strerror (errno));
		exit (1);
	}
	ifr.ifr_flags &= ~SLF_COMPRESS;
	ifr.ifr_flags |= SLF_AUTOCOMP;
	if (ioctl (sock, SIOCSLNKFLAGS, &ifr) < 0) {
		fprintf (stderr, "%s: cannot set LINK LEVEL FLAGS: %s\n",
			iname, strerror (errno));
		exit (1);
	}
	close (sock);
	return 0;
}

int
cslip_init (tty, iname)
	char *tty, *iname;
{
	struct ifreq ifr;
	int sock;

	strcpy (iname, "sl");
	do_link (tty, iname);

	sock = socket (PF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		perror ("cannot open socket");
		exit (1);
	}
	strcpy (ifr.ifr_name, iname);
	if (ioctl (sock, SIOCGLNKFLAGS, &ifr) < 0) {
		fprintf (stderr, "%s: cannot get LINK LEVEL FLAGS: %s\n",
			iname, strerror (errno));
		exit (1);
	}
	ifr.ifr_flags |= SLF_COMPRESS;
	if (ioctl (sock, SIOCSLNKFLAGS, &ifr) < 0) {
		fprintf (stderr, "%s: cannot set LINK LEVEL FLAGS: %s\n",
			iname, strerror (errno));
		exit (1);
	}
	close (sock);
	return 0;
}

struct protocol {
	char	*name;
	int	(*init) (char *tty, char *iname);
};

struct protocol allprotos[] = {
	{ "slip", slip_init },
	{ "ppp", ppp_init },
	{ "cslip", cslip_init },
	{ 0, 0 }
};

void
if_setup (tty, proto, iname)
	char *tty, *proto, *iname;
{
	struct protocol *p;

	for (p = allprotos; p->name; ++p) {
		if (!stricmp (proto, p->name)) {
			(*p->init) (tty, iname);				
			return;
		}
	}
	fprintf (stderr, "if_setup: %s: unknown protocol\n", proto);
	exit (1);
}

/*
 * TTY stuff.
 */

void
set_raw_tty (fd)
	int fd;
{
	int r;
	struct sgttyb sg;

	r = gtty (fd, &sg);
	if (r < 0) {
		perror ("cannot get terminal flags");
		exit (1);
	}
	savesg = sg;
	sg.sg_flags |= RAW;
	sg.sg_flags &= ~(CBREAK|ECHO|CRMOD|XKEY|TANDEM|EVENP|ODDP|TOSTOP);
	r = stty (fd, &sg);
	if (r < 0) {
		perror ("cannot set terminal flags");
		exit (1);
	}
}

void
set_canon_tty (fd)
	int fd;
{
	int r;

	r = stty (fd, &savesg);
	if (r < 0) {
		perror ("cannot set teminal flags");
		exit (1);
	}
}

static void
hangup (void)
{
	struct timeval tm = { 0, 600000L };
	struct sgttyb sg;
	char ispeed, ospeed;

	if (ioctl (ttyfd, TIOCGETP, &sg) < 0) {
		perror ("cannot hangup the line");
		return;
	}
	ispeed = sg.sg_ispeed;
	ospeed = sg.sg_ospeed;
	sg.sg_ispeed = B0;
	sg.sg_ospeed = B0;
	if (ioctl (ttyfd, TIOCSETN, &sg) < 0) {
		perror ("cannot hangup the line");
		return;
	}
	select (0, 0, 0, 0, &tm);
	sg.sg_ispeed = ispeed;
	sg.sg_ospeed = ospeed;
	if (ioctl (ttyfd, TIOCSETN, &sg) < 0) {
		perror ("cannot hangup the line");
		return;
	}
}

void
assert_dtr (void)
{
	struct sgttyb sg;

	if (ioctl (ttyfd, TIOCGETP, &sg) < 0) {
		perror ("cannot raise DTR");
		return;
	}
	if (sg.sg_ispeed == B0)
		sg.sg_ispeed = B9600;
	if (sg.sg_ospeed == B0)
		sg.sg_ospeed = B9600;
	if (ioctl (ttyfd, TIOCSETN, &sg) < 0) {
		perror ("cannot raise DTR");
		return;
	}
}

void
sig_handler (sig)
	int sig;
{
	long r;

	set_canon_tty (ttyfd);

	r = mysystem (_PATH_IFCONFIG, ifname, "down", NULL);
	if (r != 0)
		fprintf (stderr, "cannot shut down interface %s\n", ifname);

	if (hangup_opt)
		hangup ();

	signal (SIGINT, SIG_DFL);
	raise (SIGINT);
}

static char *message =
"usage: slattach [options] -r <remote host> -l <local host>\n"
"options:\n"
"\t[-t <tty device>] [-p {ppp, slip, cslip}]\n"
"\t[-h] [-e] [-d]\n";

void
usage (void)
{
	printf ("%s", message);
	exit (1);
}

int
main (argc, argv)
	int argc;
	char *argv[];
{
	char *tty = NULL, *lhost = NULL, *rhost = NULL, *proto = "slip";
	extern char *optarg;
	int c, r;

	while ((c = getopt (argc, argv, "dhet:l:r:p:")) != EOF) switch (c) {
	case 't':
		tty = optarg;
		break;

	case 'r':
		rhost = optarg;
		break;

	case 'l':
		lhost = optarg;
		break;

	case 'e':
		exit_opt = 1;
		break;

	case 'h':
		hangup_opt = 0;
		break;

	case 'd':
		defrt_opt = 0;
		break;		

	case 'p':
		proto = optarg;
		break;

	case '?':
		usage ();
		break;
	}
	if (tty == NULL) {
		tty = ttyname (0);
		ttyfd = 0;
	} else {
		ttyfd = open (tty, O_RDONLY);
		if (ttyfd < 0) {
			fprintf (stderr, "cannot open %s: %s\n",
				tty, strerror (errno));
			exit (1);
		}
	}
	if (lhost == NULL || rhost == NULL || tty == NULL)
		usage ();

	signal (SIGQUIT, sig_handler);
	signal (SIGINT, sig_handler);
	signal (SIGTERM, sig_handler);
	signal (SIGHUP, sig_handler);

	assert_dtr ();

	/*
	 * Link device 'tty' to a free network interface of type 'proto'.
	 * Leaves actual interface name in 'ifname'.
	 */
	if_setup (tty, proto, ifname);
	/*
	 * Setup the addresses of the interface using ifconfig.
	 */
	r = mysystem (_PATH_IFCONFIG, ifname, "addr", lhost, "dstaddr",
		rhost, "up", NULL);
	if (r != 0) {
		fprintf (stderr, "cannot activate interface %s\n", ifname);
		exit (1);
	}
	/*
	 * Install route to remote system.
	 */
	r = mysystem (_PATH_ROUTE, "add", rhost, ifname, NULL);
	if (r != 0) {
		fprintf (stderr, "cannot install routing entry\n");
		exit (1);
	}
	/*
	 * If appropriate install default route to go over remote.
	 */
	if (defrt_opt) {
		r = mysystem (_PATH_ROUTE, "add", "default", ifname, "gw",
			rhost, NULL);
		if (r != 0) {
			fprintf (stderr, "cannot install default route\n");
			exit (1);
		}
	}
	/*
	 * Make TTY raw and wait for shutdown
	 */
	set_raw_tty (ttyfd);
	if (!exit_opt) for (;;)
		pause ();
	return 0;
}
