/*
 * slattach	A program for handling dialup IP connecions.
 *		This program forces a TTY line to go into a special
 *		terminal line discipline, so that it can be used for
 *		network traffic instead of the regular terminal I/O.
 *
 * Usage:	slattach [-ehlmnqv] [ -k keepalive ] [ -o outfill ]
 * 			[-c cmd] [-s speed] [-p protocol] tty | -
 *
 * Version:	@(#)slattach.c	1.20  1999-05-29
 *
 * Author:      Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *		Copyright 1988-1993 MicroWalt Corporation
 *
 * Modified:
 *		Alan Cox, <A.Cox@swansea.ac.uk> , July 16 1994
 *		Miquel van Smoorenburg, <miquels@drinkel.ow.org>, October 1994
 *		George Shearer, <gshearer@one.net>, January 3, 1995
 *		Yossi Gottlieb, <yogo@math.tau.ac.il>, February 11, 1995
 *		Peter Tobias, <tobias@et-inf.fho-emden.de>, July 30 1995
 *		Bernd Eckenfels <net-tools@lina.inka.de>, May 29, 1999
 *			added some more printf's for debug and NOBLOCK to open
 *			this should be enough to support 2.2 ttyS-style locks
 *
 *		This program is free software; you can redistribute it
 *		and/or  modify it under  the terms of  the GNU General
 *		Public  License as  published  by  the  Free  Software
 *		Foundation;  either  version 2 of the License, or  (at
 *		your option) any later version.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <getopt.h>
#include <linux/if_slip.h>

#include "pathnames.h"
#include "net-support.h"
#include "version.h"
#include "config.h"
#include "intl.h"
#include "util.h"

#ifndef _PATH_LOCKD
#define _PATH_LOCKD	"/var/lock"		/* lock files   */
#endif
#ifndef _UID_UUCP
#define _UID_UUCP	"uucp"			/* owns locks   */
#endif
#ifndef _PATH_DEVPTMX
#define _PATH_DEVPTMX	"/dev/ptmx"		/* pseudo-terminal master */
#endif


#define DEF_PROTO	"cslip"


static char *Release = RELEASE, *Signature = "Fred N. van Kempen et al.";


struct {
  const char	*speed;
  int	code;
} tty_speeds[] = {			/* table of usable baud rates	*/
  { "50",	B50	}, { "75",	B75  	},
  { "110",	B110	}, { "300",	B300	},
  { "600",	B600	}, { "1200",	B1200	},
  { "2400",	B2400	}, { "4800",	B4800	},
  { "9600",	B9600	},
#ifdef B14400
  { "14400",	B14400	},
#endif
#ifdef B19200
  { "19200",	B19200	},
#endif
#ifdef B38400
  { "38400",	B38400	},
#endif
#ifdef B57600
  { "57600",	B57600	},
#endif
#ifdef B115200
  { "115200",	B115200	},
#endif
  { NULL,	0	}
};
struct termios	tty_saved,		/* saved TTY device state	*/
		tty_current;		/* current TTY device state	*/
int		tty_sdisc,		/* saved TTY line discipline	*/
		tty_ldisc,		/* current TTY line discipline	*/
		tty_fd = -1;		/* TTY file descriptor		*/
char *		path_pts = NULL;	/* slave pseudo-terminal device */
int		opt_c = 0;		/* "command" to run at exit	*/
int		opt_e = 0;		/* "activate only" flag		*/
int		opt_h = 0;		/* "hangup" on carrier loss	*/
#ifdef SIOCSKEEPALIVE
int		opt_k = 0;		/* "keepalive" value		*/
#endif
int		opt_l = 0;		/* "lock it" flag		*/
int		opt_L = 0;		/* 3-wire mode flag		*/
int		opt_m = 0;		/* "set RAW mode" flag		*/
int		opt_n = 0;		/* "set No Mesg" flag		*/
#ifdef SIOCSOUTFILL
int		opt_o = 0;		/* "outfill" value		*/
#endif
int		opt_q = 0;		/* "quiet" flag			*/
int		opt_d = 0;		/* debug flag			*/
int		opt_v = 0;		/* Verbose flag			*/

/* Disable any messages to the input channel of this process. */
static int
tty_nomesg(int fd)
{
  if (opt_n == 0) return(0);
  return(fchmod(fd, 0600));
}

/* Check for an existing lock file on our device */
static int
tty_already_locked(char *nam)
{
  int  i = 0, pid = 0;
  FILE *fd = (FILE *)0;

  /* Does the lock file on our device exist? */
  if ((fd = fopen(nam, "r")) == (FILE *)0)
    return(0); /* No, return perm to continue */

  /* Yes, the lock is there.  Now let's make sure */
  /* at least there's no active process that owns */
  /* that lock.                                   */
  i = fscanf(fd, "%d", &pid);
  (void) fclose(fd);

  if (i != 1) /* Lock file format's wrong! Kill't */
    return(0);

  /* We got the pid, check if the process's alive */
  if (kill(pid, 0) == 0)      /* it found process */
      return(1);          /* Yup, it's running... */

  /* Dead, we can proceed locking this device...  */
  return(0);
}

/* Lock or unlock a terminal line. */
static int
tty_lock(char *path, int mode)
{
  static char saved_path[PATH_MAX];
  static int saved_lock = 0;
  struct passwd *pw;
  int fd;
  char apid[16];

  /* We do not lock standard input. */
  if ((opt_l == 0) || ((path == NULL) && (saved_lock == 0))) return(0);

  if (mode == 1) {	/* lock */
	sprintf(saved_path, "%s/LCK..%s", _PATH_LOCKD, path);
	if (tty_already_locked(saved_path)) {
		fprintf(stderr, _("slattach: /dev/%s already locked!\n"), path);
		return(-1);
	}
	if ((fd = creat(saved_path, 0644)) < 0) {
		if (errno != EEXIST)
			if (opt_q == 0) fprintf(stderr,
				_("slattach: tty_lock: (%s): %s\n"),
					saved_path, strerror(errno));
		return(-1);
	}
	sprintf(apid, "%10d\n", getpid());
	if (write(fd, apid, strlen(apid)) != strlen(apid)) {
		fprintf(stderr, _("slattach: cannot write PID file\n"));
		close(fd);
		unlink(saved_path);
		return(-1);
	}

	/* Make sure UUCP owns the lockfile.  Required by some packages. */
	if ((pw = getpwnam(_UID_UUCP)) == NULL) {
		if (opt_q == 0) fprintf(stderr, _("slattach: tty_lock: UUCP user %s unknown!\n"),
					_UID_UUCP);
		(void) close(fd);
		return(0);	/* keep the lock anyway */
	}
	if (fchown(fd, pw->pw_uid, pw->pw_gid))
		/* keep the lock anyway */;

	(void) close(fd);

	saved_lock = 1;
  } else {	/* unlock */
	if (saved_lock != 1) return(0);
	if (unlink(saved_path) < 0) {
		if (opt_q == 0) fprintf(stderr,
			"slattach: tty_unlock: (%s): %s\n", saved_path,
							strerror(errno));
		return(-1);
	}
	saved_lock = 0;
  }

  return(0);
}


/* Find a serial speed code in the table. */
static int
tty_find_speed(const char *speed)
{
  int i;

  i = 0;
  while (tty_speeds[i].speed != NULL) {
	if (!strcmp(tty_speeds[i].speed, speed)) return(tty_speeds[i].code);
	i++;
  }
  return(-EINVAL);
}


/* Set the number of stop bits. */
static int
tty_set_stopbits(struct termios *tty, char *stopbits)
{
  if (opt_d) printf("slattach: tty_set_stopbits: %c\n", *stopbits);
  switch(*stopbits) {
	case '1':
		tty->c_cflag &= ~CSTOPB;
		break;

	case '2':
		tty->c_cflag |= CSTOPB;
		break;

	default:
		return(-EINVAL);
  }
  return(0);
}


/* Set the number of data bits. */
static int
tty_set_databits(struct termios *tty, char *databits)
{
  if (opt_d) printf("slattach: tty_set_databits: %c\n", *databits);
  tty->c_cflag &= ~CSIZE;
  switch(*databits) {
	case '5':
		tty->c_cflag |= CS5;
		break;

	case '6':
		tty->c_cflag |= CS6;
		break;

	case '7':
		tty->c_cflag |= CS7;
		break;

	case '8':
		tty->c_cflag |= CS8;
		break;

	default:
		return(-EINVAL);
  }
  return(0);
}


/* Set the type of parity encoding. */
static int
tty_set_parity(struct termios *tty, char *parity)
{
  if (opt_d) printf("slattach: tty_set_parity: %c\n", *parity);
  switch(toupper(*parity)) {
	case 'N':
		tty->c_cflag &= ~(PARENB | PARODD);
		break;

	case 'O':
		tty->c_cflag &= ~(PARENB | PARODD);
		tty->c_cflag |= (PARENB | PARODD);
		break;

	case 'E':
		tty->c_cflag &= ~(PARENB | PARODD);
		tty->c_cflag |= (PARENB);
		break;

	default:
		return(-EINVAL);
  }
  return(0);
}


/* Set the line speed of a terminal line. */
static int
tty_set_speed(struct termios *tty, const char *speed)
{
  int code;

  if (opt_d) printf("slattach: tty_set_speed: %s\n", speed);
  if ((code = tty_find_speed(speed)) < 0) return(code);
  tty->c_cflag &= ~CBAUD;
  tty->c_cflag |= code;
  return(0);
}


/* Put a terminal line in a transparent state. */
static int
tty_set_raw(struct termios *tty)
{
  int i;
  int speed;

  for(i = 0; i < NCCS; i++)
		tty->c_cc[i] = '\0';		/* no spec chr		*/
  tty->c_cc[VMIN] = 1;
  tty->c_cc[VTIME] = 0;
  tty->c_iflag = (IGNBRK | IGNPAR);		/* input flags		*/
  tty->c_oflag = (0);				/* output flags		*/
  tty->c_lflag = (0);				/* local flags		*/
  speed = (tty->c_cflag & CBAUD);		/* save current speed	*/
  tty->c_cflag = (HUPCL | CREAD);		/* UART flags		*/
  if (opt_L)
	tty->c_cflag |= CLOCAL;
  else
	tty->c_cflag |= CRTSCTS;
  tty->c_cflag |= speed;			/* restore speed	*/
  return(0);
}


/* Fetch the state of a terminal. */
static int
tty_get_state(struct termios *tty)
{
  if (ioctl(tty_fd, TCGETS, tty) < 0) {
	if (opt_q == 0) fprintf(stderr,
		"slattach: tty_get_state: %s\n", strerror(errno));
	return(-errno);
  }
  return(0);
}


/* Set the state of a terminal. */
static int
tty_set_state(struct termios *tty)
{
  if (ioctl(tty_fd, TCSETS, tty) < 0) {
	if (opt_q == 0) fprintf(stderr,
		"slattach: tty_set_state: %s\n", strerror(errno));
	return(-errno);
  }
  return(0);
}


/* Get the line discipline of a terminal line. */
static int
tty_get_disc(int *disc)
{
  if (ioctl(tty_fd, TIOCGETD, disc) < 0) {
	if (opt_q == 0) fprintf(stderr,
		"slattach: tty_get_disc: %s\n", strerror(errno));
	return(-errno);
  }
  return(0);
}


/* Set the line discipline of a terminal line. */
static int
tty_set_disc(int disc)
{
  if (disc == -1) disc = tty_sdisc;

  if (ioctl(tty_fd, TIOCSETD, &disc) < 0) {
	if (opt_q == 0) fprintf(stderr,
		"slattach: tty_set_disc(%d, %d): %s\n", tty_fd,
			disc, strerror(errno));
	return(-errno);
  }
  return(0);
}


/* Fetch the name of the network interface attached to this terminal. */
static int
tty_get_name(char *name)
{
  if (ioctl(tty_fd, SIOCGIFNAME, name) < 0) {
	if (opt_q == 0)
	    perror("tty_get_name");
	return(-errno);
  }
  return(0);
}


/* Hangup the line. */
static int
tty_hangup(void)
{
  struct termios tty;

  tty = tty_current;
  (void) tty_set_speed(&tty, "0");
  if (tty_set_state(&tty) < 0) {
	if (opt_q == 0) fprintf(stderr, _("slattach: tty_hangup(DROP): %s\n"), strerror(errno));
	return(-errno);
  }

  (void) sleep(1);

  if (tty_set_state(&tty_current) < 0) {
	if (opt_q == 0) fprintf(stderr, _("slattach: tty_hangup(RAISE): %s\n"), strerror(errno));
	return(-errno);
  }
  return(0);
}


/* Close down a terminal line. */
static int
tty_close(void)
{
  (void) tty_set_disc(tty_sdisc);
  (void) tty_hangup();
  (void) tty_lock(NULL, 0);
  return(0);
}


/* Open and initialize a terminal line. */
static int
tty_open(char *name, const char *speed)
{
  char pathbuf[PATH_MAX];
  char *path_open, *path_lock;
  int fd;

  /* Try opening the TTY device. */
  if (name != NULL) {
	if (name[0] != '/') {
		if (strlen(name + 6) > sizeof(pathbuf)) {
			if (opt_q == 0) fprintf(stderr,
				_("slattach: tty name too long\n"));
			return (-1);
		}
		sprintf(pathbuf, "/dev/%s", name);
		path_open = pathbuf;
		path_lock = name;
	} else if (!strncmp(name, "/dev/", 5)) {
		path_open = name;
		path_lock = name + 5;
	} else {
		path_open = name;
		path_lock = name;
	}
	if (opt_d) printf("slattach: tty_open: looking for lock\n");
	if (tty_lock(path_lock, 1)) return(-1); /* can we lock the device? */
	if (opt_d) printf("slattach: tty_open: trying to open %s\n", path_open);
	if ((fd = open(path_open, O_RDWR|O_NDELAY)) < 0) {
		if (opt_q == 0) fprintf(stderr,
			"slattach: tty_open(%s, RW): %s\n",
					path_open, strerror(errno));
		return(-errno);
	}
	tty_fd = fd;
	if (opt_d) printf("slattach: tty_open: %s (fd=%d)\n", path_open, fd);
	if (!strcmp(path_open, _PATH_DEVPTMX)) {
		if (opt_d) printf("slattach: tty_open: trying to grantpt and unlockpt\n");
		if (grantpt(fd) < 0) {
		    if (opt_q == 0) fprintf(stderr,
			    "slattach: tty_open: grantpt: %s\n", strerror(errno));
		    return(-errno);
		}
		if (unlockpt(fd) < 0) {
		    if (opt_q == 0) fprintf(stderr,
			    "slattach: tty_open: unlockpt: %s\n", strerror(errno));
		    return(-errno);
		}
		path_pts = ptsname(fd);
		if (path_pts == NULL) {
		    if (opt_q == 0) fprintf(stderr,
			    "slattach: tty_open: ptsname: %s\n", strerror(errno));
		    return(-errno);
		}
		if (opt_d) printf("slattach: tty_open: %s: slave pseudo-terminal is %s\n",
				  path_open, path_pts);
	}
  } else {
	tty_fd = 0;
  }

  /* Fetch the current state of the terminal. */
  if (tty_get_state(&tty_saved) < 0) {
	if (opt_q == 0) fprintf(stderr, _("slattach: tty_open: cannot get current state!\n"));
	return(-errno);
  }
  tty_current = tty_saved;

  /* Fetch the current line discipline of this terminal. */
  if (tty_get_disc(&tty_sdisc) < 0) {
	if (opt_q == 0) fprintf(stderr, _("slattach: tty_open: cannot get current line disc!\n"));
	return(-errno);
  }
  tty_ldisc = tty_sdisc;

  /* Put this terminal line in a 8-bit transparent mode. */
  if (opt_m == 0) {
	if (tty_set_raw(&tty_current) < 0) {
		if (opt_q == 0) fprintf(stderr, _("slattach: tty_open: cannot set RAW mode!\n"));
		return(-errno);
	}

	/* Set the default speed if we need to. */
	if (speed != NULL) {
		if (tty_set_speed(&tty_current, speed) != 0) {
			if (opt_q == 0) fprintf(stderr, _("slattach: tty_open: cannot set %s bps!\n"),
						speed);
			return(-errno);
		}
	}

	/* Set up a completely 8-bit clean line. */
	if (tty_set_databits(&tty_current, "8") ||
	    tty_set_stopbits(&tty_current, "1") ||
	    tty_set_parity(&tty_current, "N")) {
		if (opt_q == 0) fprintf(stderr, _("slattach: tty_open: cannot set 8N1 mode!\n"));
		return(-errno);
  	}

	/* Set the new line mode. */
	if ((fd = tty_set_state(&tty_current)) < 0) return(fd);
  }

  /* OK, line is open.  Do we need to "silence" it? */
  (void) tty_nomesg(tty_fd);

  return(0);
}


/* Catch any signals. */
static void
sig_catch(int sig)
{
/*  (void) signal(sig, sig_catch); */
  tty_close();
  exit(0);
}


static void
usage(int rc)
{
  FILE *fp = rc ? stderr : stdout;
  char *usage_msg = "Usage: slattach [-ehlLmnqv] "
#ifdef SIOCSKEEPALIVE
	  "[-k keepalive] "
#endif
#ifdef SIOCSOUTFILL
	  "[-o outfill] "
#endif
	  "[-c cmd] [-s speed] [-p protocol] tty | -\n"
	  "       slattach -V | --version\n";

  fputs(usage_msg, fp);
  exit(rc);
}


static void
version(void)
{
    printf("%s\n%s\n", Release, Signature);
    exit(E_VERSION);
}


int
main(int argc, char *argv[])
{
  char path_buf[128] = "";
  char *path_dev;
  char buff[128];
  const char *speed = NULL;
  const char *proto = DEF_PROTO;
  const char *extcmd = NULL;
  int s;
  static struct option longopts[] = {
    { "version", 0, NULL, 'V' },
    { NULL, 0, NULL, 0 }
  };

  path_dev = path_buf;

  /* Scan command line for any arguments. */
  opterr = 0;
  while ((s = getopt_long(argc, argv, "c:ehlLmnp:qs:vdVk:o:", longopts, NULL)) != EOF) switch(s) {
	case 'c':
		extcmd = optarg;
		break;

	case 'e':
		opt_e = 1 - opt_e;
		break;

	case 'h':
		opt_h = 1 - opt_h;
		break;

#ifdef SIOCSKEEPALIVE
	case 'k':
		opt_k = atoi(optarg);
		break;
#endif

	case 'L':
		opt_L = 1 - opt_L;
		break;

	case 'l':
		opt_l = 1 - opt_l;
		break;

	case 'm':
		opt_m = 1 - opt_m;
		break;

	case 'n':
		opt_n = 1 - opt_n;
		break;

#ifdef SIOCSOUTFILL
	case 'o':
		opt_o = atoi(optarg);
		break;
#endif

	case 'p':
		proto = optarg;
		break;

	case 'q':
		opt_q = 1 - opt_q;
		break;

	case 's':
		speed = optarg;
		break;

	case 'd':
		opt_d = 1 - opt_d;
		break;

	case 'v':
		opt_v = 1 - opt_v;
		break;

        case 'V':
		version();
		/*NOTREACHED*/

	default:
		usage(E_OPTERR);
		/*NOTREACHED*/
  }

  if (setvbuf(stdout,0,_IOLBF,0)) {
	if (opt_q == 0) fprintf(stderr, _("slattach: setvbuf(stdout,0,_IOLBF,0) : %s\n"),
				strerror(errno));
	exit(1);
  }

  activate_init();

  if (!strcmp(proto, "tty"))
       opt_m++;

  /* Is a terminal given? */
  if (optind != (argc - 1)) usage(E_OPTERR);
  safe_strncpy(path_buf, argv[optind], sizeof(path_buf));
  if (!strcmp(path_buf, "-")) {
	opt_e = 1;
	path_dev = NULL;
	if (tty_open(NULL, speed) < 0) { return(3); }
  } else {
	path_dev = path_buf;
	if (tty_open(path_dev, speed) < 0) { return(3); }
  }

  /* Start the correct protocol. */
  if (!strcmp(proto, "tty")) {
	tty_sdisc = N_TTY;
	tty_close();
	return(0);
  }
  if (activate_ld(proto, tty_fd))
        return(1);
  if ((opt_v == 1) || (opt_d == 1)) {
        if (tty_get_name(buff)) { return(3); }
	printf(_("%s started"), proto);
	if (path_dev != NULL) printf(_(" on %s"), path_dev);
	if (path_pts != NULL) printf(_(" ptsname %s"), path_pts);
	printf(_(" interface %s\n"), buff);
  }

  /* Configure keepalive and outfill. */
#ifdef SIOCSKEEPALIVE
  if (opt_k && (ioctl(tty_fd, SIOCSKEEPALIVE, &opt_k) < 0))
	  fprintf(stderr, "slattach: ioctl(SIOCSKEEPALIVE): %s\n", strerror(errno));
#endif
#ifdef SIOCSOUTFILL
  if (opt_o && (ioctl(tty_fd, SIOCSOUTFILL, &opt_o) < 0))
	  fprintf(stderr, "slattach: ioctl(SIOCSOUTFILL): %s\n", strerror(errno));
#endif

  (void) signal(SIGHUP, sig_catch);
  (void) signal(SIGINT, sig_catch);
  (void) signal(SIGQUIT, sig_catch);
  (void) signal(SIGTERM, sig_catch);

  /* Wait until we get killed if hanging on a terminal. */
  if (opt_e == 0) {
	while(1) {
		if(opt_h == 1) { /* hangup on carrier loss */
			int n = 0;

		        ioctl(tty_fd, TIOCMGET, &n);
			if(!(n & TIOCM_CAR))
				break;
			sleep(15);
		}
		else
			sleep(60);
	};

	tty_close();
	if(extcmd)	/* external command on exit */
		exit(system(extcmd));
  }
  exit(0);
}
