//
// NOTE: COLUMNS 132 TABSTOP 4 - GET A REAL TERMINAL!!!
//
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***                                                                     ***
// *** Copyright 2001-2007 by Jack Bates                                   ***
// *** TaborRampart at FloatingDogHead d0t net                             ***
// ***                                                                     ***
// *** LEGAL DISCLAIMER AND RIGHTS POSTING:                                ***
// ***                                                                     ***
// *** THIS IS A FREE TOOL OF HACK BY JACK.                                ***
// ***                                                                     ***
// *** 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.                     ***
// ***                                                                     ***
// *** This program is distributed in the hope that it will be useful, but ***
// *** but WITHOUT ANY WARRANTY; without even the implied warranty of      ***
// *** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   ***
// *** General Public License for more details.                            ***
// ***                                                                     ***
// *** You should have received a copy of the GNU General Public License   ***
// *** along with this program; if not, write to:                          ***
// ***                                                                     ***
// ***     Free Software Foundation, Inc.                                  ***
// ***     59 Temple Place - Suite 330                                     ***
// ***     Boston, MA  02111-1307, USA.                                    ***
// ***                                                                     ***
// *** THE FULL GPL LICENSE TEXT IS ALSO AVAILABLE AT http://www.fsf.org   ***
// ***                                                                     ***
// *** IN ADDITION:                                                        ***
// ***                                                                     ***
// *** IF YOU FEEL THE NEED TO INCORPORATE THIS CODE (OR DERIVATIVE) INTO  ***
// *** ANY PRODUCT THAT DOES NOT USE THE GPL AS ITS COPYRIGHT ENFORCEMENT  ***
// *** MECHANISM, YOU ARE OBLIGATED TO ARRANGE LICENSING WITH ME.          ***
// ***                                                                     ***
// *** FURTHERMORE:                                                        ***
// ***                                                                     ***
// *** I ACCEPT NO LIABILITY WHATSOEVER IN REGARDS TO YOUR USE OF THIS     ***
// *** SOURCE CODE.  MY RELEASE OF THIS FILE IS A FREE PUBLIC SERVICE.  I  ***
// *** DIDN'T WRITE THIS TO GET SUED, IT IS INTENDED FOR EDUCATIONAL USE   ***
// *** IN THE STUDY OF THE COMPUTING SCIENCES AND HOW THE INTERNET WORKS.  ***
// ***                                                                     ***
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************

#define VERSION "1.6"

// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
//
// TaborRampart VolksVPN client/server control software
//
// Shameless:     I provide consulting services in regards to computer
//                security and secure remote access mechanisms.  If you
//                are interested in contracting such services, please
//                feel free to drop me a line using the contact infomation
//                listed above.
//
// Comment:       VPN controller process for systems that have ssh, sshd
//                and pppd available with auto gateway-to-gateway static
//                route instantiation.
//
//                This creates a very compute-intensive VPN, but it works
//                nicely on reliable links with zippy endpoint machines.
//                A 486-33 server handles my DSL bandwidth with no problem.
//                Bandwidth I get is somewhere between 256 and 384 Kb/s.
//
//                I recommend this for general single-access-point VPN
//                services for 802.11 WLANs.
//
//                I also recommend this for linking office locations
//                together for effective, secure telecommuting and such.
//
//                PPP cannot ferry broadcast traffic so MS LAN-based stuff
//                may not generally work across a link of this type.
//
//                using SSH to tunnel PPP, over which TCP may travel (i.e.
//                TCP in TCP) has serious weaknesses over non-reliable
//                or slow links.  think about retry multiplication of the
//                TCP being tunnelled vs. the tunnelling SSH TCP stream.
//                Also think about packet fragmentation.  Ouch!
//                In practice, for everything except bulk data transfer,
//                this method causes no noticable issues.
//
// Tested under:  Fedora Core 3 i386, RedHat-9 i386, RedHat-8.0 i386,
//                RedHat-7.3 i386 and many other linuxen.
//
//                   openssh various versions
//                   openssl various versions
//                   pppd various versions
//
//                on various AMD/Intel/TransMeta processors
//
// Compilation:   Standard compile: gcc -Wall -o vpn vpn.c
//
// Symlinks:      To enable client and server personalities:
//
//                  ln -s vpn vpnc
//                  ln -s vpn vpns
//
// Requires:      client: /usr/bin/ssh and /usr/sbin/pppd
//                server: /usr/sbin/sshd (running) and /usr/sbin/pppd
//
// Warnings:      pppd sets host routes for the two ip addresses of the 
//                connection on both sides of the connection.
//                Being able to set additional static routes requires root
//                privilege on the side of the connection that the route
//                is being instantiated on.  These routes are controlled
//                in the server's ~/.ssh/authorized_keys file.  
//
// Example:
//                An example of what needs to appear in an authorized_keys
//                file is given below:
//
// command="/usr/local/sbin/vpns 192.168.1.1 192.168.1.89 192.168.1.0/255.255.255.0,0.0.0.0/0.0.0.0 192.168.1.89/255.255.255.255",no-port-forwarding,no-X11-forwarding,no-agent-forwarding <contents of the user's id_rsa.pub (RSA public key) or id_dsa.pub (DSA public key) file>
//
//                All of the above should be on a single line.  Here is
//                what it all means:
//
//                command="/usr/local/sbin/vpns
//                    this is the command we are running, the vpn server.
//                192.168.1.1
//                    the IP address that we want the client to know the
//                    server as.
//                192.168.1.89
//                    the IP address that we want the server to know the
//                    client as.  If this is within a LAN connected to the
//                    server, proxyarp will automatically be invoked.
//                192.168.1.0/255.255.255.0,0.0.0.0/0.0.0.0
//                    comma-delimited list of routes that we want the server
//                    to provide to the client.  0.0.0.0/0.0.0.0 is a default
//                    route for the purpose of this system.
//                192.168.1.89/255.255.255.255",
//                    comma-delimited list of routes that we want the client
//                    to provide to the server.  In this case 
//                    192.168.1.89/255.255.255.255 indicates a host route
//                    only and is automatically provided by pppd, but we list
//                    it anyway here as a placeholder.  it will be ignored.
//                no-port-forwarding,
//                    disallow the client from setting up port forwarding
//                    on the server.  This is optional, of course.
//                no-X11-forwarding,
//                    disallow the client from setting up X11 forwarding
//                    on the server.  This is optional, of course.
//                no-agent-forwarding,
//                    disallow the client for setting up ssh-agent forwarding
//                    on the server.  This is optional, of course
//                <contents of the user's id_rsa.pub (RSA public key) file
//                or id_dsa.pub (DSA public key) file>
//                    if you are generating keys in the default manner the
//                    content that you will be pasting into the file at this
//                    point will begin with the string "ssh-rsa " (and a bunch
//                    of garbage-looking letters and numbers - finally
//                    followed by something that looks like an email address)
//                    an example key-generation invocation:
//
//                        user% ssh-keygen -t rsa
//
//                All of this data should exist on one (long) line of text
//                in the authorized_keys file.  It may be necessary to add
//                the noauth qualifier to the /etc/ppp/options file.  This
//                allows the client to have an IP address that is routable
//                from the server.  
//
//                                PARANOID RAMBLINGS
//
//                RUNNING ANY PROGRAM WITH ROOT PRIVILEGE IS DANGEROUS!!!
//
//                         SSH'S AUTHENTICATION IS _VERY_ GOOD
//
//                       I _THINK_ THAT I'VE DONE A GOOD JOB HERE
//
//                           DO YOU TRUST YOUR VPN CLIENTS???
//
//                       THIS IS NOT ENTERPRISE-GRADE SOFTWARE!!!
//
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
//
// NOTICE: AS THE U.S. FEDERAL GOVERNMENT CONSIDERS PROGRAMS OF THIS NATURE
//         TO BE MUNITIONS (i.e. weapons of war), THE PUBLIC POSTING OF THIS
//         SOURCE CODE HAS BEEN REGISTERED WITH THE U.S. FEDERAL BUREAU OF
//         EXPORT ADMINISTRATION.  Isn't it silly that I had to register my
//         (free?) speech a priori with our bozo political establishment?
//
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
//
// Evolutional Litany:
//
//    1.6   2007/01/10  drop nobsdpty command-line arg.
//                      cmd line: noresolv->resolv
//                      cmd line: nocompress->compress
//                      cmd line: nodefault->defaultroute
//                      cmd line: reconnect
//                      SIGHUP fixes.
//
//    1.5   2004/12/05  review/cosmetics on pointer variable declarations.
//
//    1.4   2004/12/02  enhance tty code to use Unix98 so that this
//                      software can work under FC3 as a client.
//
//    1.3   2004/03/30  added ability to specify identity file to client.
//
//    1.2   2003/07/09  further anal-retentive attention to commentary.
//
//    1.1   2003/01/13  clear client segfault on ssh connect failure.
//
//    1.0   2003/01/13  redefine the meaning of nocompress to control
//                      (only) whether ssh compresses data.
//
//    0.9   2002/11/16  sys_errlist[] is deprecated in gcc 3.2,
//                      changed all references to strerror().
//                      cosmetic code formatting.
//
//    0.8   2002/06/02  decided it best to remove the nov2 option.
//
//    0.7   2002/05/31  further refinements in documentation.
//                      updated copyright notice to 2002.
//    
//    0.6   2002/05/08  updated directions for SSH protocol 2, which I've
//                      been using for a long time now.  I discovered that
//                      that the doc in this file was lagging.
//
//    0.5   2001/10/09  strictly cosmetic in commentary.
//
//    0.4   2001/10/05  fixed bug in default route instantiation.
//
//    0.3   2001/10/02  added nov2 command line option.
//
//    0.2   2001/09/28  stronger parse of target supplied on command line.
//
//    0.1   2001/08/19  initial release, primitive but working.
//
//          2001/06/07  laid off from Lucent Technologies.
//                      what a stupid company.
//
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termio.h>
#include <time.h>
#include <unistd.h>
#include <wait.h>
#include <arpa/inet.h>
#include <linux/fs.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

extern char* basename(const char*);

#define MAX_ARGS				64
#define DEVICE_LEN				32
#define IP_ADDR_LEN				32
#define POLL_WAIT				7
#define KILLCONNECT_WAIT		8
#define PPPD_BINARY				"/usr/sbin/pppd"
#define SSH_BINARY				"/usr/bin/ssh"
#define NAMESERVER				"nameserver"
#define CONFIG					"config"
#define PPP_ACTIVE_WAIT			60
#define NUM_IFRS				128
#define RESOLV_LEN				128
#define RMT_CFG_LEN				256
#define RMT_CFG_WAIT			30
#define FD_STDIN				0
#define FD_STDOUT				1
#define FD_STDERR				2
#define ROUTE_BUF_LEN			4096
#define ROUTE_FILENAME			"/proc/net/route"

// session parameters stored in global variables
static char*					target					=	NULL;
static unsigned long			pvt_ip					=	0xFFFFFFFF;
static unsigned long			lcl_ip					=	0xFFFFFFFF;
static int						port					=	22;
static int						ssh_pid					=	-1;
static int						pppd_pid				=	-1;
static char*					rmt_routes				=	NULL;
static int						defaultroute			=	0;
static char*					ns1						=	"";
static char*					ns2						=	"";
static int						resolv					=	0;
static int						compress				=	0;
static int						reconnect				=	0;
static char*					identity				=	NULL;
char*							bn						=	NULL;

// must be initialized in main()
static char						target_ip[IP_ADDR_LEN];

static int loopy_readline(int fd, void *buf, size_t len)
{
	char*						cursor;
	int							got;

	for (cursor = buf; len > 0; cursor++)
	{
		got = read(fd, cursor, 1);
		if (got == 0)
			return -1;
		if (got < 0)
			return -1;
		if (*cursor == '\n')
		{
			*cursor = '\0';
			return 0;
		}
	}

	return 1;
}

// "safer" way of getting an ASCII version of an IP address
static char *inet_ltoa(unsigned long l, char *put)
{
	struct in_addr				ia;

	ia.s_addr = l;
	strncpy(put, inet_ntoa(ia), IP_ADDR_LEN - 1);
	put[IP_ADDR_LEN - 1] = '\0';
	return put;
}

// log an interface as being up
static void log_up(struct ifreq *ifr)
{
	char						dst[IP_ADDR_LEN];

	syslog(LOG_DAEMON | LOG_INFO,
			"%s to %s up",
			ifr->ifr_name, inet_ltoa((((struct sockaddr_in *) (&(ifr->ifr_ifru.ifru_dstaddr)))->sin_addr.s_addr), dst));
}

// down an interface
static void if_down(struct ifreq *ifr)
{
	char						dst[IP_ADDR_LEN];
	int							sockfd;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"socket %s",
			strerror(errno));
		return;
	}

	syslog(LOG_DAEMON | LOG_INFO,
		"%s to %s downing",
		ifr->ifr_name, inet_ltoa((((struct sockaddr_in *) (&(ifr->ifr_ifru.ifru_dstaddr)))->sin_addr.s_addr), dst));
	ifr->ifr_ifru.ifru_flags = 0;
	if (ioctl(sockfd, SIOCSIFFLAGS, ifr) < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"SIOCSIFFLAGS %s",
			strerror(errno));
	}

	close(sockfd);

	// is this the proper place for this?
	sleep(KILLCONNECT_WAIT);
}

// find out if any ppp interface has a matching destination address
// call f() if you find a match
static int ppp_dst_match(unsigned long addr, void (*f)(struct ifreq *))
{
	struct ifconf				ifc;
	struct ifreq				ifrs[NUM_IFRS];
	char						s_addr[IP_ADDR_LEN];
	int							sockfd;
	int							i;
	
	inet_ltoa(addr, s_addr);

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"socket %s",
			strerror(errno));
		return -1;
	}

	ifc.ifc_buf = (void *) ifrs;
	ifc.ifc_len = sizeof(ifrs);
	if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"SIOCGIFCONF %s",
			strerror(errno));
		close(sockfd);
		return -1;
	}

	for (i = 0; i * sizeof(struct ifreq) < ifc.ifc_len; i++)
	{
		if (!strncmp(ifrs[i].ifr_name, "ppp", 3))
		{
			if (ioctl(sockfd, SIOCGIFFLAGS, &ifrs[i]) < 0)
			{
				syslog(LOG_DAEMON | LOG_INFO,
					"SIOCGIFFLAGS %s",
					strerror(errno));
				close(sockfd);
				return -1;
			}

			// up and running?
			if ((ifrs[i].ifr_ifru.ifru_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) continue;

			if (ioctl(sockfd, SIOCGIFDSTADDR, &ifrs[i]) < 0)
			{
				syslog(LOG_DAEMON | LOG_INFO,
					"SIOCGIFDSTADDR %s",
					strerror(errno));
				close(sockfd);
				return -1;
			}

			if ((((struct sockaddr_in *) (&(ifrs[i].ifr_ifru.ifru_dstaddr)))->sin_addr.s_addr) == addr)
			{
				f(&ifrs[i]);
				close(sockfd);
				return 0;
			}
		}
	}

	close(sockfd);
	return -1;
}

// add a gateway to the kernel routing table
static int gw(unsigned long net, unsigned long netmask, unsigned long gw)
{
	struct rtentry					rt;
	char							s_net[IP_ADDR_LEN];
	char							s_netmask[IP_ADDR_LEN];
	char							s_gw[IP_ADDR_LEN];
	int								sockfd;

	syslog(LOG_DAEMON | LOG_INFO,
		"gw %s/%s %s\n",
		inet_ltoa(net, s_net), inet_ltoa(netmask, s_netmask), inet_ltoa(gw, s_gw));

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"socket %s",
			strerror(errno));
		return -1;
	}

	memset (&rt, '\0', sizeof (rt));
	memset((char *) &rt.rt_dst, 0, sizeof(rt.rt_dst));
	rt.rt_dst.sa_family = AF_INET;
	(((struct sockaddr_in *) (&(rt.rt_dst)))->sin_addr.s_addr) = net;
	rt.rt_gateway.sa_family = AF_INET;
	(((struct sockaddr_in *) (&(rt.rt_gateway)))->sin_addr.s_addr) = gw;
	rt.rt_genmask.sa_family = AF_INET;
	(((struct sockaddr_in *) (&(rt.rt_genmask)))->sin_addr.s_addr) = netmask;
	rt.rt_flags = RTF_UP | RTF_GATEWAY;

	if (ioctl(sockfd, SIOCADDRT, &rt) < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"SIOCADDRT %s",
			strerror(errno));
		close(sockfd);
		return -1;
	}

	close(sockfd);
	return 0;
}

// set up the /etc/resolv.conf file per server provision and our restrictions
// note that this _only_ sets up nameservers - no domain or search commands are given
static void resolv_conf()
{
	FILE*						resolv_file;

	// resolv setup is allowed and ns1 exists?
	if (resolv && strlen(ns1) > 0 && inet_addr(ns1) != 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"ns1 %s ns2 %s",
			ns1, ns2);

		resolv_file = fopen("/etc/resolv.conf", "w");
		if (resolv_file == NULL)
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"unable to open resolv.conf - nameservers not updated");
		} else
		{
			fprintf(resolv_file, "nameserver %s\n",
				ns1);
			if (strlen(ns2) > 0 && inet_addr(ns2) != 0)
				fprintf(resolv_file, "nameserver %s\n",
					ns2);
		}
		fclose(resolv_file);
		resolv_file = NULL;
	}
}

// setup the gateways per server offerings and client restrictions
static void setup_gateways(unsigned int rmt_addr, char *gw_list)
{
	char*						s_net;
	char*						s_netmask;
	char*						s_pair;
	struct in_addr				ia;

	s_pair = strtok(gw_list, ",");
	while (s_pair != NULL)
	{
		s_net = s_pair;
		s_netmask = s_net;
		while (*s_netmask != '\0' && *s_netmask != '/') s_netmask++;
		if (*s_netmask == '\0')
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"invalid gateway list");
			return;
		}
		*s_netmask = '\0';
		s_netmask++;
		if (strlen(s_net) == 0 || strlen(s_netmask) == 0)
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"invalid gateway list");
			return;
		}

		ia.s_addr = rmt_addr;
		syslog(LOG_DAEMON | LOG_INFO,
			"gateway list element: %s/%s %s",
			s_net, s_netmask, inet_ntoa(ia));

		if (strcmp(s_netmask, "255.255.255.255") != 0)
		{
			if (!defaultroute && !strcmp(s_netmask, "0.0.0.0"))
			{
				syslog(LOG_DAEMON | LOG_INFO,
					"default gateway offered but not set");
			} else
			{
				gw(inet_addr(s_net), inet_addr(s_netmask), rmt_addr);
			}
		}

		s_pair = strtok(NULL, ",");
	}
}

/* the following three defines and function are Copyright (c) 1997 Magosányi Árpád under the GPL */
/* Jack Bates: I've hacked on this code a bit, creating a derived work */

#define PTY00					"/dev/ptyXX"
#define PTY10					"pqrs"
#define PTY01					"0123456789abcdef"
static int getPtyMaster(char *slave, int len)
{
	int							fd;
	char*						unix98slave;

	// try Unix98 - oh so clean...
	extern int getpt(void);
	fd = getpt();
	if (fd < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"no available pty");
		return -1;
	}
	extern int grantpt(int);
	if (grantpt(fd) < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"grantpt");
		return -1;
	}
	extern int unlockpt(int);
	if (unlockpt(fd) < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"unlockpt");
		return -1;
	}
	extern char* ptsname(int);
	unix98slave = ptsname(fd);
	if (unix98slave == NULL)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"ptsname");
		return -1;
	}
	snprintf(slave, len, "%s", unix98slave);
	slave[len - 1] = '\0';		// safety
	return fd;
}        

static void killssh()
{
	int							status;
	kill(ssh_pid, SIGHUP);
	waitpid(ssh_pid, &status, 0);
	syslog(LOG_DAEMON | LOG_INFO,
		"ssh exit status %d",
		status);
	ssh_pid = -1;
}

// create the attachment (pty/tty) and fork off sshd and pppd
static int vpnc_start_session()
{
	int							argc					=	0;
	char*						delims					=	" \t";
	char*						argv[MAX_ARGS];
	int							master_fd, slave_fd;
	char						device[DEVICE_LEN];
	char						tmp_addr[IP_ADDR_LEN];
	char						s_pvt_ip[IP_ADDR_LEN];
	char						s_lcl_ip[IP_ADDR_LEN];
	fd_set						rdfds;
	struct timeval				tv;
	char						rmt_cfg_buf[RMT_CFG_LEN];
	char*						rmt_cfg_ptr;
	char*						s_cfg;
	int							ok;
	struct termios				ti;

	master_fd = getPtyMaster(device, sizeof(device));
	if (master_fd < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"fatal: unable to obtain master pty");
		return -1;
	}

	ssh_pid = fork();
	if (ssh_pid < 0) 
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"fatal: fork for ssh %s",
			strerror(errno));
		return -2;

	} else if (ssh_pid == 0)
	{

		dup2(master_fd, FD_STDIN);
		dup2(master_fd, FD_STDOUT);
		fflush(NULL);

		argc = 0;
		argv[argc++] = SSH_BINARY;
		if (compress)
			argv[argc++] = "-C";						// compression requested?
		if (identity != NULL)							// identity file specified?
		{
			argv[argc++] = "-i";
			argv[argc++] = identity;
		}
		
		argv[argc++] = "-2";							// ssh version 2 protocol only
		argv[argc++] = "-a";							// disable agent forwarding
		argv[argc++] = "-c";							// select cipher
		argv[argc++] = "blowfish";						// blowfish cipher
		argv[argc++] = "-e";							// escape chars
		argv[argc++] = "none";							// no escape chars
		if (strlen(target) > 0)
		{
			argv[argc++] = "-l";						// specify the user on the server
			argv[argc++] = target;
		}
		argv[argc++] = "-p";							// select port
		snprintf(tmp_addr, IP_ADDR_LEN,
			"%d",
			port);
		tmp_addr[IP_ADDR_LEN - 1] = '\0';
		argv[argc++] = tmp_addr;						// port
		argv[argc++] = "-t";							// obtain pty
		argv[argc++] = "-x";							// disable X11 forwarding
		argv[argc++] = target_ip;						// IP address we're going to
		argv[argc] = NULL;

		// we did not specify any port forwardings
		// all we need is the single connection
		// the server should also enforce a "dry" policy such as this

		execv(argv[0], argv);
		syslog(LOG_DAEMON | LOG_ERR,
			"fatal: execv for ssh %s",
			strerror(errno));
		return -3;
	}

	// at this point we don't need this anymore
	close(master_fd);

	slave_fd = open(device, O_RDWR);
	if (slave_fd < 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"slave fd open %s %s",
			device, strerror(errno));
		killssh();
		return -4;
	}

	if (ioctl(slave_fd, TCGETS, &ti) != 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"slave fd TCGETS %s %s",
			device, strerror(errno));
		close(slave_fd);
		killssh();
		return -5;
	}

	ti.c_iflag = 0;
	ti.c_oflag = 0;
	ti.c_lflag = 0;

	if (ioctl(slave_fd, TCSETS, &ti) != 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"slave fd TCSETS %s %s",
			device, strerror(errno));
		close(slave_fd);
		killssh();
		return -6;
	}

	syslog(LOG_DAEMON | LOG_INFO,
		"await remote configuration");

	// bound the amount of time we'll wait for remote configuration to start showing up
	// we don't bound the "completion time", however.  the server should attempt to use
	// buffering to make anything sent up to and including the configuration happen
	// in one packet.
	FD_ZERO(&rdfds);
	FD_SET(slave_fd, &rdfds);
	tv.tv_sec = RMT_CFG_WAIT;
	tv.tv_usec = 0;
	if (select(slave_fd + 1, &rdfds, NULL, NULL, &tv) == 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"rmt cfg timeout killing ssh pid %d",
			ssh_pid);
		close(slave_fd);
		killssh();
		return -7;
	}

	rmt_cfg_ptr = NULL;
	while (loopy_readline(slave_fd, rmt_cfg_buf, RMT_CFG_LEN) == 0)
	{
		if (strncmp(rmt_cfg_buf, CONFIG, strlen(CONFIG)) == 0)
		{
			rmt_cfg_ptr = rmt_cfg_buf + strlen(CONFIG) + 1;
			break;
		}
		syslog(LOG_DAEMON | LOG_INFO,
			"server said: \"%s\"",
			rmt_cfg_buf);
	}

	if (rmt_cfg_ptr == NULL)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"read %s",
			strerror(errno));
		close(slave_fd);
		killssh();
		return -8;
	}

	syslog(LOG_DAEMON | LOG_INFO,
		"received remote config \"%s\"",
		rmt_cfg_ptr);

	if (rmt_routes != NULL)
	{
		// as we're about to re-strdup() it...
		free(rmt_routes);
		rmt_routes = NULL;
	}

	// extract remote config
	ok = 0;
	s_cfg = strtok(rmt_cfg_ptr, delims);
	if (s_cfg != NULL)
	{
		pvt_ip = inet_addr(s_cfg);
		s_cfg = strtok(NULL, delims);
		if (s_cfg != NULL)
		{
			// the ip address that we will be known by to the server
			lcl_ip = inet_addr(s_cfg);

			s_cfg = strtok(NULL, delims);
			// the routes that the server will provide
			if (s_cfg != NULL) rmt_routes = strdup(s_cfg);

			// the routes that we will provide the server
			// we ignore the actual values here for now
			if (s_cfg != NULL) s_cfg = strtok(NULL, delims);
			// we've obtained minimal configuration
			if (s_cfg != NULL) ok = 1;

			// nameserver (1) present?
			if (s_cfg != NULL) s_cfg = strtok(NULL, delims);
			if (s_cfg != NULL)
			{
				if (strcmp(s_cfg, NAMESERVER) == 0)
				{
					s_cfg = strtok(NULL, delims);
					if (s_cfg != NULL) ns1 = strdup(s_cfg);
					else ok = 0;
				} else ok = 0;
			}

			// nameserver (2) present?
			if (s_cfg != NULL) s_cfg = strtok(NULL, delims);
			if (s_cfg != NULL)
			{
				if (strcmp(s_cfg, NAMESERVER) == 0)
				{
					s_cfg = strtok(NULL, delims);
					if (s_cfg != NULL) ns2 = strdup(s_cfg);
					else ok = 0;
				} else ok = 0;
			}
		}
	}

	// remote config failed for any reason?
	if (!ok)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"remote config parse error, kill sesssion");
		close(slave_fd);
		killssh();
		return -9;
	}

	// dump remote config to log
	syslog(LOG_DAEMON | LOG_INFO,
		"target %s pvt_ip %s lcl_ip %s rmt_routes %s ns1 %s ns2 %s",
		target, inet_ltoa(pvt_ip, s_pvt_ip), inet_ltoa(lcl_ip, s_lcl_ip),
		rmt_routes, ns1, ns2);

	pppd_pid = fork();
	if (pppd_pid < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"fork %s",
			strerror(errno));
		close(slave_fd);
		killssh();
		return -10;

	} else if (pppd_pid == 0)
	{
		// child code
		// make slave tty stdin/stdout
		dup2(slave_fd, FD_STDIN);
		dup2(slave_fd, FD_STDOUT);
		fflush(NULL);

		syslog(LOG_DAEMON | LOG_INFO,
			"wait rmt pppd");

		FD_ZERO(&rdfds);
		FD_SET(slave_fd, &rdfds);
		tv.tv_sec = PPP_ACTIVE_WAIT;
		tv.tv_usec = 0;
		if (select(slave_fd + 1, &rdfds, NULL, NULL, &tv) == 0)
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"rmt cfg timeout");
			killssh();
			return -11;
		}

		syslog(LOG_DAEMON | LOG_INFO,
			"start lcl pppd");
		argv[argc++] = PPPD_BINARY;
		// if there is _any_ compression, it's ssh doing it,
		// not the low-level ppp protocol - let's only compress once...
		argv[argc++] = "nodeflate";
		argv[argc++] = "nobsdcomp";
		argv[argc] = NULL;

		execv(argv[0], argv);

		syslog(LOG_DAEMON | LOG_ERR,
			"execv pppd %s",
			strerror(errno));
		return -12;
	}

	close(slave_fd);

	syslog(LOG_DAEMON | LOG_INFO,
		"device %s ssh_pid %d pppd_pid %d",
		device, ssh_pid, pppd_pid);

	return 0;
}

// the basic polling loop that drives the client
static void vpnc_service()
{
	int							status;
	int							pid;

	while (1)
	{
		if (vpnc_start_session() == 0)
			break;
		if (!reconnect)
			return;
		sleep(5);
	}

	// death cult: begin the obsession...
	while (1)
	{
		// dead ssh?
		pid = 0;
		status = 0;
		if (ssh_pid != -1) pid = waitpid(ssh_pid, &status, WNOHANG);
		if (pid > 0)
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"fatal: ssh pid %d returns %d",
				ssh_pid, status);
			ssh_pid = -1;
			kill(pppd_pid, SIGHUP);
			waitpid(pppd_pid, &status, 0);
			pppd_pid = -1;
			break;
		}

		// dead pppd?
		pid = 0;
		status = 0;
		if (pppd_pid != -1) pid = waitpid(pppd_pid, &status, WNOHANG);
		if (pid > 0)
		{
			syslog(LOG_DAEMON | LOG_INFO,
				"fatal: pppd pid %d returns %d",
				pppd_pid, status);
			pppd_pid = -1;
			kill(ssh_pid, SIGHUP);
			waitpid(ssh_pid, &status, 0);
			ssh_pid = -1;
			break;
		}

		// pending route instantiation?
		if (rmt_routes != NULL)
		{
			// ifc up? then set up the gateways and resolver information
			if (ppp_dst_match(pvt_ip, log_up) == 0)
			{
				setup_gateways(pvt_ip, rmt_routes);
				free(rmt_routes);
				rmt_routes = NULL;

				resolv_conf();
			}
		}

		// short polling delay or long?
		if (rmt_routes != NULL)
			usleep(250000);
		else
			sleep(POLL_WAIT);
	}
}

// client SIGHUP
static void vpnc_sighup(int sig)
{
	signal(SIGHUP, vpnc_sighup);

	syslog(LOG_DAEMON | LOG_INFO,
		"SIGHUP kill children");
	if (ssh_pid > 0)  kill(ssh_pid,  SIGHUP);
	if (pppd_pid > 0) kill(pppd_pid, SIGHUP);
}

// client SIGTERM
static void vpnc_sigterm(int sig)
{
	signal(SIGTERM, vpnc_sigterm);

	syslog(LOG_DAEMON | LOG_INFO,
		"SIGTERM kill children");
	if (ssh_pid > 0)
		kill(ssh_pid,  SIGHUP);
	if (pppd_pid > 0)
		kill(pppd_pid, SIGHUP);
	exit(0);
}

// client usage
static void vpnc_usage(char *hint)
{

	fprintf(stderr,
		"\nusage: %s [port n] [reconnect] [default] [resolv] [compress] [identity f] [user@]host\nhint: %s\n",
		bn, hint);
	fprintf(stderr, "\n   port n      -    specify port on remote host for ssh connection\n");
	fprintf(stderr, "   reconnect   -    reconnect to server anytime connection fails\n");
	fprintf(stderr, "   default     -    allow override of default route, if server provides it\n");
	fprintf(stderr, "   resolv      -    allow override of resolve.conf, if server provides it\n");
	fprintf(stderr, "   compress    -    enable ssh compression\n");
	fprintf(stderr, "   identity f  -    specify ssh identity key file\n");
	fprintf(stderr, "   [user@]host -    the remote username and hostname for ssh service\n\n");

	exit(1);
}

// parse a hostname or an ip address into the target_ip string
static void parse_host(char *host)
{
	struct in_addr				addr;
	struct hostent*				hent;

	// attempt IP address
	if (inet_aton(host, &addr) == 0)
	{
		strcpy(target_ip, host);
		return;
	}

	// attempt hostname
	hent = gethostbyname(host);
	if (hent != NULL)
	{
		// hackish, I know...
		inet_ltoa(*((unsigned long *) hent->h_addr), target_ip);
		return;
	}

	syslog(LOG_DAEMON | LOG_INFO,
		"unable to parse host %s",
		host);
	vpnc_usage("bad host(name)");
}

// parse the [user@]host string
static void parse_target()
{
	char*						cursor;

	// find the '@', if present
	for (cursor = target; *cursor != '\0'; cursor++)
	{
		if (*cursor == '@')
		{
			*cursor = '\0';
			parse_host(cursor + 1);
			return;
		}
	}

	// no '@', so assume just a host
	parse_host(target);
	// so that it won't get picked up later
	*target = '\0';
}

// vpnc mainline
static int vpnc(int argc, char *argv[])
{
	bn = argv[0];

	argv++; argc--;
	if (argc < 1) vpnc_usage("no [user@]host");

	while (argc > 0)
	{
		if (strcmp(*argv, "port") == 0)
		{
			argv++; argc--;
			if (argc < 1) vpnc_usage("no port number");
			port = atoi(*argv);

		} else if (strcmp(*argv, "identity") == 0)
		{
			argv++; argc--;
			if (argc < 1) vpnc_usage("no identity file");
			identity = *argv;

		} else if (strcmp(*argv, "default") == 0)
		{
			defaultroute = 1;

		} else if (strcmp(*argv, "resolv") == 0)
		{
			resolv = 1;

		} else if (strcmp(*argv, "compress") == 0)
		{
			compress = 1;

		} else if (strcmp(*argv, "reconnect") == 0)
		{
			reconnect = 1;

		} else
		{
			target = *argv;
		}

		argv++; argc--;
	}

	parse_target();
	syslog(LOG_DAEMON | LOG_INFO,
		"target %s@%s:%d",
		target, target_ip, port);

	signal(SIGHUP, vpnc_sighup);
	signal(SIGTERM, vpnc_sigterm);

	do
	{
		vpnc_service();
	} while (reconnect);

	return 0;
}

// vpns mailine
static int vpns(int argc, char *argv[])
{
	char*						pppd_argv[MAX_ARGS];
	int							pppd_argc				=	0;
	int							pppd_pid				=	-1;

	int							got_pid;
	int							status;
	unsigned long				pvt_ip;
	unsigned long				clt_ip;
	char						s_pvt_ip[IP_ADDR_LEN];
	char						s_clt_ip[IP_ADDR_LEN];
	char*						lcl_nets;
	char*						rmt_nets;
	char						clt_ip_addr_colon[2 * IP_ADDR_LEN + 2];
	int							rc;
	struct termios				ti;
	int							fd;
	char						resolv_line[RESOLV_LEN];
	int							resolv_count;

	if (argc != 5)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"usage: %s pvt_ip clt_ip lcl_nets rmt_nets",
			argv[0]);
		exit(1);
	}

	pvt_ip = inet_addr(argv[1]);
	inet_ltoa(pvt_ip, s_pvt_ip);
	clt_ip = inet_addr(argv[2]);
	inet_ltoa(clt_ip, s_clt_ip);
	lcl_nets = argv[3];
	rmt_nets = argv[4];

	if (ioctl(FD_STDOUT, TCGETS, &ti) != 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"TCGETS %s",
			strerror(errno));
		return -1;
	}

	ti.c_iflag = 0;
	ti.c_oflag = 0;
	ti.c_lflag = 0;

	if (ioctl(FD_STDOUT, TCSETS, &ti) != 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"TCSETS %s",
			strerror(errno));
		return -1;
	}

	// log startup parameters
	syslog(LOG_DAEMON | LOG_INFO,
		"pvt_ip %s clt_ip %s lcl_nets %s rmt_nets %s",
		s_pvt_ip, s_clt_ip, lcl_nets, rmt_nets);

	// provide startup parameters to the client
	fprintf(stdout,
		"%s %s %s %s %s",
		CONFIG, s_pvt_ip, s_clt_ip, lcl_nets, rmt_nets);

	fd = open("/etc/resolv.conf", O_RDONLY);
	if (fd >= 0)
	{
		resolv_count = 0;
		while (resolv_count < 2 && loopy_readline(fd, resolv_line, RESOLV_LEN) == 0)
		{
			if (strncmp(resolv_line, NAMESERVER, strlen(NAMESERVER)) == 0)
			{
				fprintf(stdout,
					" %s",
					resolv_line);
				resolv_count++;
			}
		}
		close(fd);
	}

	fprintf(stdout,
		"\n");
	fflush(stdout);

	// if we already have a PPP interface to the client's proposed IP address, down it
	// this has the important property that it kills all routes through the interface
	// this has the bad property that it _may_ leave zombie pppd (not in the classical Unix sense of the word)
	// we should look through /proc and kill the relevant pppd as an additional measure, but that remains unimplemented...
	// in practice, this has not been a problem, as pppd uses an idle-keepalive component in the ppp protocol.
	ppp_dst_match(clt_ip, if_down);

	pppd_pid = fork();
	if (pppd_pid < 0)
	{
		syslog(LOG_DAEMON | LOG_ERR,
			"pppd fork failed");
		exit(1);
	}
	if (pppd_pid == 0)
	{
		syslog(LOG_DAEMON | LOG_INFO,
			"forked pppd pid %d",
			getpid());
		pppd_argv[pppd_argc++] = PPPD_BINARY;
		snprintf(clt_ip_addr_colon, IP_ADDR_LEN,
			"%s:%s",
			s_pvt_ip, s_clt_ip);
		clt_ip_addr_colon[IP_ADDR_LEN - 1] = '\0';
		pppd_argv[pppd_argc++] = clt_ip_addr_colon;
		pppd_argv[pppd_argc++] = "proxyarp";
		pppd_argv[pppd_argc++] = "nodeflate";
		pppd_argv[pppd_argc++] = "nobsdcomp";
		pppd_argv[pppd_argc] = NULL;
	
		execv(pppd_argv[0], pppd_argv);
		syslog(LOG_DAEMON | LOG_ERR,
			"execv %s",
			strerror(errno));
		exit(1);
	}

	// close tty that we passed on to pppd
	close(0);
	close(1);
	
	syslog(LOG_DAEMON | LOG_INFO,
		"wait pppd_dst_match");

	// wait until an interface is up to the target address, then setup the routes, if so configured
	while (1)
	{
		status = 0;
		got_pid = waitpid(pppd_pid, &status, WNOHANG);
		if (got_pid != 0)
		{
			syslog(LOG_DAEMON | LOG_ERR,
				"pppd pid %d died status %d %s",
				pppd_pid, status, strerror(errno));
			exit(1);
		}

		if (ppp_dst_match(clt_ip, log_up) == 0)
		{
			setup_gateways(clt_ip, rmt_nets);
			break;
		}

		sleep(POLL_WAIT);
	}

	syslog(LOG_DAEMON | LOG_INFO,
		"wait pppd pid %d",
		pppd_pid);
	rc = waitpid(pppd_pid, &status, 0);
	syslog(LOG_DAEMON | LOG_INFO,
		"pppd pid %d died status %d %s",
		pppd_pid, status, strerror(errno));
	return 0;
}

static void ast79()
{
	fprintf(stderr, "*******************************************************************************\n");
}

int main(int argc, char *argv[])
{
	// additional initialization
	*target_ip = '\0';

	ast79();
	fprintf(stderr,
		"TaborRampart VolksVPN version %s - Copyright 2001-2007 by Jack Bates\n",
		VERSION);
	fprintf(stderr,
		"Distributed by Jack Bates under the GNU General Public License\n");
	ast79();

	if (*(argv[0]) == '-') argv[0]++;

	openlog(basename(argv[0]), LOG_PID, LOG_DAEMON);

	if (!strcmp(basename(argv[0]), "vpnc"))
	{
		exit(vpnc(argc, argv));
	} else if (!strcmp(basename(argv[0]), "vpns"))
	{
		exit(vpns(argc, argv));
	}

	fprintf(stderr,
		"basename(argv[0]) == %s - UNKNOWN!!!\n",
		basename(argv[0]));
	return 0;
}
