/*
 * Copyright (c) 2002-2006 Hajimu UMEMOTO <ume@mahoroba.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Mahoroba: src/dtcpclient/dtcpclient.c,v 1.25 2006/05/25 16:36:21 ume Exp $
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <libgen.h>
#include <netdb.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sysexits.h>
#include <unistd.h>

#ifdef HAVE_MD5
#include <md5.h>
#else
#include <openssl/ssl.h>
#include <openssl/md5.h>
#define MD5Init		MD5_Init
#define MD5Update	MD5_Update
#define MD5Final	MD5_Final
#endif

#define SERVICE_DTCP		"20200"
#define KEEPALIVE_INTERVAL	60
#define TIMEOUT			60
#define RECONNECT_WAIT		10
#define MAXLINELEN		1024
#define DELIM			" \t"
#define UDP_TUNNEL_PORT		4028

#ifndef _PATH_VARRUN
#define _PATH_VARRUN	"/var/run"
#endif

#ifndef PREFIX
#define PREFIX		"/usr/pkg"
#endif

#define PIDFILE		VARBASE "/run/dtcpclient.pid"
#define PASSWDFILE	SYSCONFDIR "/dtcpclient.auth"
#define SCRIPTFLIE	PREFIX "/sbin/dtcpclient-script"

#undef FALSE
#undef TRUE
typedef enum {
	FALSE = 0,
	TRUE = 1
} boolean;

typedef enum {
	TUNNEL_ONLY,
	TUNNEL_HOST,
	TUNNEL_NETWORK
} tuntype;

static char *tuntype_table[] = {
	"tunnelonly",
	"host",
	"network"
};

struct tunnel_info {
	char *nickname;
	char *server;
	char *myaddr;
	tuntype tuntype;
	char *tuninfo[6];
};

static struct tunnel_info *tunnel = NULL;
static int dtcp_sock = -1;
static boolean dtcp_error = FALSE;
static boolean dtcp_connected = FALSE;

static char *pidfile = PIDFILE;
static char *script = SCRIPTFLIE;
static boolean behind_nat = FALSE;
static boolean debug = FALSE;
static boolean daemonize = FALSE;
static int mtu = 0;
static boolean verbose = FALSE;
static boolean syslog_opened = FALSE;
static boolean reconnect_request = FALSE;

static int signalpipe[2];

static void bye(int);

static void
usage(char *progname)
{
	fprintf(stderr,
		"usage: %s [-dDhlnU] [-b udpport] [-B naptport] [-e nickname] [-f pidfile] [-m mtu] [-p port] [-s script] [-t tuntype] [-u username] server\n",
	       progname);
	exit(EX_USAGE);
}

static void
logmsg(int priority, const char *fmt, ...)
{
	static char buf[MAXLINELEN];
	va_list ap;

	buf[0] = '\0';
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf) - 3, fmt, ap);
	va_end(ap);
	if (syslog_opened)
		syslog(priority, "%s", buf);
	else
		fprintf(stderr, "%s\n", buf);
}

static void
debugmsg(const char *fmt, ...)
{
	static char buf[MAXLINELEN];
	va_list ap;

	if (!debug)
		return;
	buf[0] = '\0';
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf) - 3, fmt, ap);
	va_end(ap);
	if (syslog_opened)
		syslog(LOG_DEBUG, "%s", buf);
	else
		fprintf(stderr, "%s\n", buf);
}

static char *
authenticate(char *user, char *challenge, char *pass)
{
	static const char hex[] = "0123456789ABCDEF";
	static char ascii_digest[33];
	MD5_CTX	ctx;
	unsigned char digest[16];
	int i;

	MD5Init(&ctx);
	MD5Update(&ctx, (unsigned char *)user, strlen(user));
	MD5Update(&ctx, (unsigned char *)challenge, strlen(challenge));
	MD5Update(&ctx, (unsigned char *)pass, strlen(pass));
	MD5Final(digest, &ctx);
	for (i = 0;  i < 16;  i++) {
		ascii_digest[i + i] = hex[digest[i] >> 4];
		ascii_digest[i + i + 1] = hex[digest[i] & 0x0f];
	}
	ascii_digest[i + i] = '\0';
	return ascii_digest;
}

static boolean
pidfile_create()
{
	FILE *fp;

	if ((fp = fopen(pidfile, "w")) == NULL)
		return FALSE;
	fprintf(fp, "%d\n", getpid());
	fclose(fp);
	return TRUE;
}

static void
pidfile_delete()
{
	FILE *fp;
	char buf[16];

	if ((fp = fopen(pidfile, "r")) == NULL)
		return;
	if (fgets(buf, sizeof(buf), fp) == NULL)
		return;
	fclose(fp);
	if (atoi(buf) == getpid())
		unlink(pidfile);
}

static char *
getpassword_file(char *dst, char *user)
{
	FILE *fp;
	char buf[MAXLINELEN];
	char *sep = ":";
	char *p, *pw_dst, *pw_user, *pw_passwd;

	if ((fp = fopen(PASSWDFILE, "r")) == NULL) {
		debugmsg("no authinfo file found");
		return NULL;
	}
	while (fgets(buf, sizeof(buf), fp) != NULL) {
		if ((p = strrchr(buf, '\n')) != NULL)
			*p = '\0';
		if ((pw_dst = strtok(buf, sep)) == NULL)
			continue;
		if ((pw_user = strtok(NULL, sep)) == NULL)
			continue;
		if (strcmp(pw_dst, dst) != 0 || strcmp(pw_user, user) != 0)
			continue;
		if ((pw_passwd = strtok(NULL, sep)) == NULL)
			continue;
		fclose(fp);
		return strdup(pw_passwd);
	}
	fclose(fp);
	debugmsg("no relevant authinfo item found");
	return NULL;
}

static char *
getpassword_tty(char *user)
{
	char prompt[MAXLINELEN];
	char *password;

	snprintf(prompt, sizeof(prompt), "password for %s: ", user);
	if ((password = getpass(prompt)) == NULL)
		return NULL;
	password = strdup(password);
	return password;
}

char *
getpassword(char *dst, char *user)
{
	char *password;

	if ((password = getpassword_file(dst, user)) == NULL)
		password = getpassword_tty(user);
	return password;
}

static int
connect_to(char *host, char *port, sa_family_t family)
{
	struct addrinfo hints, *res, *res0;
	int err, sock;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;
	if ((err = getaddrinfo(host, port, &hints, &res0)) != 0) {
		logmsg(LOG_ERR, "getaddrinfo failed (dst=%s port=%s): %s",
		       host, port, gai_strerror(err));
		return -1;
	}
	for (sock = -1, res = res0; res; res = res->ai_next) {
		if ((sock = socket(res->ai_family, res->ai_socktype,
			 res->ai_protocol)) == -1)
			continue;
		if (connect(sock, res->ai_addr, res->ai_addrlen) != -1)
			break;
		close(sock);
		sock = -1;
	}
	freeaddrinfo(res0);
	if (sock == -1) {
		logmsg(LOG_ERR, "could not connect to %s port %s", host, port);
		return -1;
	}
	return sock;
}

static ssize_t
putbuf(char *str, size_t nbytes, int fd)
{
	char *cp;
	ssize_t nleft, nwritten;

	cp = str;
	nleft = nbytes;
	while (nleft > 0) {
		if ((nwritten = write(fd, cp, nleft)) < 0) {
			if (errno != EINTR)
				dtcp_error = TRUE;
			if (verbose)
				logmsg(LOG_ERR, "write failed: %s",
				       strerror(errno));
			return -1;
		}
		nleft -= nwritten;
		cp += nwritten;
	}
	return nbytes - nleft;
}

static int
dtcp_send(int fd, const char *fmt, ...)
{
	char buf[MAXLINELEN];
	va_list ap;

	buf[0] = '\0';
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf) - 3, fmt, ap);
	va_end(ap);
	debugmsg("<<< %s", buf);
	strlcat(buf, "\r\n", sizeof(buf));
	if (putbuf(buf, strlen(buf), fd) < 0)
		return -1;
	return 0;
}

static ssize_t
dtcp_recv(int sock, char *buf, size_t len)
{
	fd_set readfds;
	int maxsock, nsig, err;
	struct timeval tv;
	ssize_t n, nread;

	maxsock = (sock >= signalpipe[0]) ? sock : signalpipe[0];
	nread = 0;
	while (len > nread) {
		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);
		FD_SET(signalpipe[0], &readfds);
		tv.tv_sec = TIMEOUT;
		tv.tv_usec = 0;
		err = select(maxsock + 1, &readfds, NULL, NULL, &tv);
		if (err <= 0) {
			if (err == 0) {
				if (verbose)
					logmsg(LOG_ERR, "select timeout");
			} else if (errno == EINTR)
				continue;
			if (verbose)
				logmsg(LOG_ERR, "select failed: %s",
				       strerror(errno));
			dtcp_error = TRUE;
			return -1;
		}
		if (FD_ISSET(signalpipe[0], &readfds)) {
			if (ioctl(signalpipe[0], FIONREAD, &nsig) != 0) {
				logmsg(LOG_ERR, "ioctl failed: %s",
				       strerror(errno));
				bye(EX_OSERR);
				/* NOTREACHED */
			}
			while (--nsig >= 0) {
				char c;
				if (read(signalpipe[0], &c, 1) != 1) {
					if (verbose)
						logmsg(LOG_ERR,
						       "read failed: %s",
						       strerror(errno));
					bye(EX_OSERR);
					/* NOTREACHED */
				}
				debugmsg("handling signal flag %c", c);
				switch(c) {
				case 'H': /* sighup */
					reconnect_request = TRUE;
					return -1;
				}
			}
		}
		if (FD_ISSET(sock, &readfds)) {
			if ((n = read(sock, buf + nread, len - nread)) < 0) {
				if (errno != EINTR)
					dtcp_error = TRUE;
				if (verbose)
					logmsg(LOG_ERR, "read failed: %s",
					       strerror(errno));
				return -1;
			}
			if (n == 0)
				continue;
			nread += n;
			if (nread < 2 || buf[nread - 1] != '\n' ||
			    buf[nread - 2] != '\r')
				continue;
			nread -= 2;
			buf[nread] = '\0';
			debugmsg(">>> %s", buf);
			return nread;
		}
	}
	if (verbose)
		logmsg(LOG_ERR, "response too long");
	return -1;
}

static void
dtcp_close()
{
	if (dtcp_sock < 0)
		return;
	if (!dtcp_error)
		dtcp_send(dtcp_sock, "quit");
	shutdown(dtcp_sock, 1);
	close(dtcp_sock);
	dtcp_sock = -1;
}

static boolean
tunnel_delete(struct tunnel_info *tunnel)
{
	int i;

	if (tunnel != NULL)
		return FALSE;
	if (tunnel->nickname) {
		free(tunnel->nickname);
		tunnel->nickname = NULL;
	}
	if (tunnel->server) {
		free(tunnel->server);
		tunnel->server = NULL;
	}
	if (tunnel->myaddr) {
		free(tunnel->myaddr);
		tunnel->myaddr = NULL;
	}
	for (i = 0; tunnel->tuninfo[i] != NULL; ++i) {
		free(tunnel->tuninfo[i]);
		tunnel->tuninfo[i] = NULL;
	}
	free(tunnel);
	return TRUE;
}

static struct tunnel_info *
tunnel_new(const char *nickname, const char *server, const char *myaddr,
	   const tuntype tuntype, const char *tuninfo[])
{
	struct tunnel_info *tunnel;
	int i;

	tunnel = (struct tunnel_info *)malloc(sizeof(struct tunnel_info));
	if (tunnel == NULL)
		return NULL;
	memset(tunnel, 0, sizeof(*tunnel));
	if (nickname && (tunnel->nickname = strdup(nickname)) == NULL) {
		tunnel_delete(tunnel);
		return NULL;
	}
	if ((tunnel->server = strdup(server)) == NULL) {
		tunnel_delete(tunnel);
		return NULL;
	}
	if ((tunnel->myaddr = strdup(myaddr)) == NULL) {
		tunnel_delete(tunnel);
		return NULL;
	}
	tunnel->tuntype = tuntype;
	for (i = 0; i < 5 && tuninfo[i] != NULL; ++i)
		if ((tunnel->tuninfo[i] = strdup(tuninfo[i])) == NULL) {
			tunnel_delete(tunnel);
			return NULL;
		}
	tunnel->tuninfo[i] = NULL;
	return tunnel;
}

static void
invoke_script(const char *state, const char *server, const char*myaddr,
	      const tuntype tuntype, const char *tuninfo[])
{
	pid_t pid;
	char buf[MAXLINELEN];
	char *argv[11];
	int argc = 0, i;
	int status;

	argv[argc++] = script;
	argv[argc++] = (char *)state;
	argv[argc++] = (char *)server;
	argv[argc++] = (char *)myaddr;
	argv[argc++] = tuntype_table[tuntype];
	for (i = 0; i < 5; ++i)
		argv[argc++] = (char *)tuninfo[i];
	argv[argc] = NULL;

	if (verbose) {
		strncpy(buf, "calling:", sizeof(buf));
		for (argc = 0; argv[argc] != NULL; ++argc) {
			strncat(buf, " ", sizeof(buf) - 1);
			strncat(buf, argv[argc], sizeof(buf) - 1);
		}
		logmsg(LOG_NOTICE, "%s", buf);
	}

	switch (pid = vfork()) {
	case -1:
		logmsg(LOG_ERR, "cannot fork: %s", strerror(errno));
		break;
	case 0:
		(void)execv(script, argv);
		_exit(EX_UNAVAILABLE);
	default:
		(void)waitpid(pid, &status, 0);
	}
}

static boolean
node_up(const char *nickname, const char *server, const char *myaddr,
	const tuntype tuntype, const char *tuninfo[])
{
	const char *name;

	tunnel = tunnel_new(nickname, server, myaddr, tuntype, tuninfo);
	if (tunnel == NULL)
		return FALSE;
	name = nickname ? nickname : server;
	invoke_script("up", name, myaddr, tuntype, tuninfo);
	return TRUE;
}

static boolean
node_down()
{
	const char *name;

	if (tunnel == NULL)
		return FALSE;
	name = tunnel->nickname ? tunnel->nickname : tunnel->server;
	invoke_script("down", name, tunnel->myaddr, tunnel->tuntype,
		      (const char **)tunnel->tuninfo);
	tunnel_delete(tunnel);
	tunnel = NULL;
	return TRUE;
}

static void
bye(int status)
{
	dtcp_close();
	node_down();
	if (daemonize)
		pidfile_delete();
	exit(status);
}

static boolean
getmyaddr(int sock, char *hbuf, int len)
{
	struct sockaddr_storage ss;
	socklen_t salen;
	int niflags, err;

	salen = sizeof(ss);
	if (getsockname(sock, (struct sockaddr *)&ss, &salen) != 0) {
		logmsg(LOG_ERR, "getsockname failed: %s", strerror(errno));
		return FALSE;
	}
	niflags = NI_NUMERICHOST;
#ifdef NI_WITHSCOPEID
	if (((struct sockaddr *)&ss)->sa_family == AF_INET6)
		niflags |= NI_WITHSCOPEID;
#endif
	err = getnameinfo((struct sockaddr *)&ss, salen, hbuf, len, NULL, 0,
			  niflags);
	if (err) {
		logmsg(LOG_ERR, "getnameinfo failed: %s", gai_strerror(err));
		return FALSE;
	}
	return TRUE;
}

static void
keep_alive(int sock)
{
	char buf[MAXLINELEN];
	char *p;

	while (1) {
		debugmsg("sleep(60)");
		sleep(KEEPALIVE_INTERVAL);
		if (dtcp_send(sock, "ping") < 0)
			break;
		if (dtcp_recv(sock, buf, sizeof(buf)) < 0)
			break;
		if ((p = strtok(buf, DELIM)) == NULL ||
		    strcmp(p, "+OK") != 0) {
			if (verbose)
				logmsg(LOG_ERR, "invalid response");
			break;
		}
	}
}

static char *
getchallenge(char *buf)
{
	char *challenge, *p;

	for (challenge = buf; *challenge == ' '; challenge++)
		;
	if (*challenge == '\0')
		return NULL;
	for (p = challenge; *p != '\0' && *p != ' '; p++)
		;
	*p = '\0';
	return challenge;
}

static char *
getresponse(char *buf)
{
	char *response, *p;

	for (response = buf; *response == ' '; response++)
		;
	if (*response == '\0')
		return NULL;
	for (p = response; *p != '\0' && *p != '\r' && *p != '\n'; p++)
		;
	*p = '\0';
	return response;
}

static boolean
check_tuntype(tuntype tuntype, int nargs)
{
	boolean valid = FALSE;

	switch (nargs) {
	case 2:
		if (tuntype == TUNNEL_ONLY)
			valid = TRUE;
		break;
	case 4:
		if (tuntype == TUNNEL_HOST)
			valid = TRUE;
		break;
	case 3:
	case 5:
		if (tuntype == TUNNEL_NETWORK)
			valid = TRUE;
		break;
	}
	return valid;
}

static boolean
dtcpc(char *nickname, char *dst, char *port, char *username, char *password,
    tuntype tuntype, int udp_tunnel, int udp_tunnel_port, int udp_napt_port)
{
	char myaddr[NI_MAXHOST], buf[MAXLINELEN], buf2[MAXLINELEN];
	char *challenge, *response, *p;
	char *tuninfo[6];
	int i, r;

	dtcp_error = FALSE;
	dtcp_connected = FALSE;
	if ((dtcp_sock = connect_to(dst, port, AF_INET)) < 0)
		return FALSE;
	if (!getmyaddr(dtcp_sock, myaddr, sizeof(myaddr))) {
		dtcp_close();
		return FALSE;
	}
	dtcp_connected = TRUE;

	logmsg(LOG_NOTICE, "logging in to %s port %s", dst, port);

	/* get greeting */
	if (dtcp_recv(dtcp_sock, buf, sizeof(buf)) < 0) {
		dtcp_close();
		return FALSE;
	}
	if (strncmp(buf, "+OK ", 4) != 0 ||
	    (challenge = getchallenge(buf + 4)) == NULL) {
		if (verbose)
			logmsg(LOG_ERR, "invalid response");
		dtcp_close();
		return FALSE;
	}
	response = authenticate(username, challenge, password);
	snprintf(buf, sizeof(buf), "tunnel %s %s %s",
	    username, response, tuntype_table[tuntype]);
	if (mtu > 0) {
		snprintf(buf2, sizeof(buf2), " %d", mtu);
		strlcat(buf, buf2, sizeof(buf));
	}
	if (udp_tunnel) {
		strlcat(buf, " proto=udp", sizeof(buf));
		if (!behind_nat || udp_napt_port > 0) {
			snprintf(buf2, sizeof(buf2), " port=%d",
			    (udp_napt_port > 0) ?
			    udp_napt_port : udp_tunnel_port);
			strlcat(buf, buf2, sizeof(buf));
		}
	}
	r = dtcp_send(dtcp_sock, buf);
	if (r < 0) {
		dtcp_close();
		return FALSE;
	}

	if (dtcp_recv(dtcp_sock, buf, sizeof(buf)) < 0) {
		dtcp_close();
		return FALSE;
	}
	if (strncmp(buf, "+OK ", 4) != 0) {
		if (strncmp(buf, "-ERR ", 5) != 0) {
			if (verbose)
				logmsg(LOG_ERR, "invalid response");
			dtcp_close();
			return FALSE;
		}
		if ((response = getresponse(buf + 4)) != NULL)
			logmsg(LOG_ERR, "failed, reason: %s", response);
		dtcp_close();
#if 0
		if (response != NULL &&
		    strncmp(response, "authentication", 14) == 0) {
			bye(EX_DATAERR);
			/* NOTREACHED */
		}
#endif
		return FALSE;
	}

	if ((p = strtok(buf, DELIM)) == NULL || strcmp(p, "+OK") != 0) {
		if (verbose)
			logmsg(LOG_ERR, "invalid response");
		dtcp_close();
		return FALSE;
	}
	/* tunnelonly 1: me, 2: her */
	/* host       1: me, 2: her 3: me6 4: her6 */
	/* network    1: me, 2: her 3: prefix (4: me6, 5: her6) */
	i = 0;
	while ((p = strtok(NULL, DELIM)) != NULL) {
		if (i > 4)
			break;
		tuninfo[i++] = p;
	}
	tuninfo[i] = NULL;

	if (!check_tuntype(tuntype, i)) {
		if (verbose)
			logmsg(LOG_ERR, "invalid response");
		dtcp_close();
		return FALSE;
	}

	if (strcmp(myaddr, tuninfo[0]) != 0 && !behind_nat) {
		logmsg(LOG_ERR, "failed, you are behind a NAT box (%s != %s)",
		       myaddr, tuninfo[0]);
		dtcp_close();
		bye(EX_DATAERR);
		/* NOTREACHED */
	}

	if (udp_tunnel) {
		snprintf(buf2, sizeof(buf2), ";%d", udp_tunnel_port);
		strlcat(myaddr, buf2, sizeof(myaddr));
	}

	node_up(nickname, dst, myaddr, tuntype, (const char **)tuninfo);
	keep_alive(dtcp_sock);
	dtcp_close();
	node_down();
	return TRUE;
}

static void
flag_signal(int c)
{
	char ch = c;

	if (write(signalpipe[1], &ch, 1) != 1) {
		logmsg(LOG_ERR, "write failed: %s", strerror(errno));
		bye(EX_OSERR);
		/* NOTREACHED */
	}
}

static void
reconnect(int signo __attribute__((__unused__)))
{
	logmsg(LOG_NOTICE, "reconnect by SIGHUP");
	if (dtcp_sock >= 0)
		flag_signal('H');
}

static void
terminate(int signo)
{
	if (daemonize) {
		switch (signo) {
		case SIGTERM:
			logmsg(LOG_NOTICE, "exit with SIGTERM");
			break;
		case SIGINT:
			logmsg(LOG_NOTICE, "exit with SIGINT");
			break;
		default:
			logmsg(LOG_NOTICE, "exit with signal %d", signo);
			break;
		}
	}
	bye(EX_OK);
	/* NOTREACHED */
}

int
main(int argc, char *argv[])
{
	int c;
	boolean loop = FALSE;
	char *progname;
	char *nickname = NULL;
	char *dst;
	char *port = SERVICE_DTCP;
	tuntype tuntype = TUNNEL_ONLY;
	char *username = NULL;
	char *password = NULL;
	struct passwd *pwd;
	char dtcp_mtu[10];
	int udp_tunnel = FALSE;
	int udp_tunnel_port = UDP_TUNNEL_PORT, udp_napt_port = 0;

	progname = basename(argv[0]);

	if ((pwd = getpwuid(getuid())) != NULL)
		username = pwd->pw_name;

	while ((c = getopt(argc, argv, "b:B:dDe:f:hlm:np:s:t:u:U")) != -1)
		switch (c) {
		case 'b':
			udp_tunnel_port = atoi(optarg);
			break;
		case 'B':
			udp_napt_port = atoi(optarg);
			break;
		case 'd':
			debug = TRUE;
			break;
		case 'D':
			daemonize = TRUE;
			break;
		case 'e':
			nickname = optarg;
			break;
		case 'f':
			pidfile = optarg;
			break;
		case 'h':
			usage(progname);
			/* NOTREACHED */
		case 'l':
			loop = TRUE;
			break;
		case 'm':
			mtu = atoi(optarg);
			break;
		case 'n':
			behind_nat = TRUE;
			break;
		case 'p':
			port = optarg;
			break;
		case 's':
			script = optarg;
			break;
		case 't':
			if (strcmp(optarg, "tunnelonly") == 0)
				tuntype = TUNNEL_ONLY;
			else if (strcmp(optarg, "host") == 0)
				tuntype = TUNNEL_HOST;
			else if (strcmp(optarg, "network") == 0)
				tuntype = TUNNEL_NETWORK;
			else {
				usage(progname);
				/* NOTREACHED */
			}
			break;
		case 'u':
			username = optarg;
			break;
		case 'U':
			udp_tunnel = TRUE;
			break;
		default:
			usage(progname);
			/* NOTREACHED */
		}

	argv += optind;
	argc -= optind;

	if (argc != 1) {
		usage(progname);
		/* NOTREACHED */
	}

	dst = argv[0];

	if (daemonize) {
		daemon(0, 0);
		openlog((const char *)progname, LOG_PID, LOG_DAEMON);
		syslog_opened = TRUE;
		if (verbose)
			logmsg(LOG_NOTICE, "start");
	}

	password = getpassword(dst, username);

	if (pipe(signalpipe) != 0) {
		logmsg(LOG_ERR, "pipe failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	if (daemonize) {
		if (!pidfile_create())
			exit(EX_DATAERR);
	}

	signal(SIGPIPE, SIG_IGN);
	signal(SIGTERM, terminate);
	signal(SIGINT, terminate);
	signal(SIGHUP, reconnect);

	if (mtu > 0) {
		snprintf(dtcp_mtu, sizeof(dtcp_mtu), "%d", mtu);
		setenv("DTCP_MTU", dtcp_mtu, 1);
	}

	while (1) {
		dtcpc(nickname, dst, port, username, password, tuntype,
		    udp_tunnel, udp_tunnel_port, udp_napt_port);
		if (reconnect_request) {
			reconnect_request = FALSE;
			continue;
		}
		if (dtcp_connected)
			logmsg(LOG_NOTICE, "connection was lost.");
		if (!loop)
			break;
		sleep(RECONNECT_WAIT);
	}

	free(password);

	if (daemonize)
		pidfile_delete();
	exit(EX_OK);
}
