#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "kernel_ax25.h"
#include "kernel_rose.h"
#include <netax25/axlib.h>
#include <netax25/axconfig.h>
#include <netax25/nrconfig.h>
#include <netax25/rsconfig.h>
#include <netax25/procutils.h>

#include "node.h"

ax25io *NodeIo	= NULL;

/*
 * Do some validity checking for callsign pointed to by `s'.
 */
static int check_call(const char *s)
{
	int len = 0;
	int nums = 0;
	int ssid = 0;
	char *p[1];

	if (s == NULL)
		return -1;
	while (*s && *s != '-') {
		if (!isalnum(*s))
			return -1;
		if (isdigit(*s))
			nums++;
		len++;
		s++;
	}
	if (*s == '-') {
		if (!isdigit(*++s))
			return -1;
		ssid = strtol(s, p, 10);
		if (**p)
			return -1;
	}
	if (len < 4 || len > 6 || !nums || nums > 2 || ssid < 0 || ssid > 15)
		return -1;
	return 0;
}

static void signal_handler(int sig)
{
	axio_eolmode(NodeIo, EOLMODE_TEXT);
	switch (sig) {
	case SIGALRM:
		nputs("\n");
		node_msg("Timeout! Disconnecting...");
		logout("Timeout");
	case SIGTERM:
		nputs("\n");
		node_msg("System going down! Disconnecting...");
		logout("SIGTERM");
	case SIGINT:
		logout("SIGINT");
	case SIGQUIT:
		logout("SIGQUIT");
	case SIGSEGV:
		nputs("\n");
		node_msg("Caught SIGSEGV");
		logout("SIGSEGV");
	default:
		logout("unknown signal");
	}
}

int main(int argc, char *argv[])
{
	union {
		struct full_sockaddr_ax25 sax;
		struct sockaddr_rose      srose;
		struct sockaddr_in        sin;
	} saddr;
	int slen = sizeof(saddr);
	char *p, buf[256], *pw;
	int paclen;
	FILE *fp;
	int invalid_cmds = 0;

	signal(SIGALRM, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGINT,  signal_handler);
	signal(SIGQUIT, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGPIPE, SIG_IGN);
	if (ax25_config_load_ports() == 0) {
		log(L_ERROR, "No AX.25 port data configured");
		return 1;
	}
	nr_config_load_ports();
	rs_config_load_ports();
	if (getpeername(STDOUT_FILENO, (struct sockaddr *)&saddr, &slen) == -1) {
		if (errno != ENOTSOCK) {
			log(L_ERROR, "getpeername: %s", strerror(errno));
			return 1;
		}
		User.ul_type = AF_UNSPEC;
	} else
		User.ul_type = saddr.sax.fsa_ax25.sax25_family;
	switch (User.ul_type) {
	case AF_AX25:
		strcpy(User.call, ax25_ntoa(&saddr.sax.fsa_ax25.sax25_call));
		if (getsockname(STDOUT_FILENO, (struct sockaddr *)&saddr.sax, &slen) == -1) {
			log(L_ERROR, "getsockname: %s", strerror(errno));
			return 1;
		}
		strcpy(User.ul_name, ax25_config_get_port(&saddr.sax.fsa_digipeater[0]));
		paclen = ax25_config_get_paclen(User.ul_name);
		p = AX25_EOL;
		break;
	case AF_NETROM:
		strcpy(User.call, ax25_ntoa(&saddr.sax.fsa_ax25.sax25_call));
		strcpy(User.ul_name, ax25_ntoa(&saddr.sax.fsa_digipeater[0]));
		if (getsockname(STDOUT_FILENO, (struct sockaddr *)&saddr.sax, &slen) == -1) {
			log(L_ERROR, "getsockname: %s", strerror(errno));
			return 1;
		}
		strcpy(User.ul_port, nr_config_get_port(&saddr.sax.fsa_ax25.sax25_call));
		paclen = nr_config_get_paclen(User.ul_port);
		p = NETROM_EOL;
		break;
	case AF_ROSE:
		strcpy(User.call, ax25_ntoa(&saddr.srose.srose_call));
		strcpy(User.ul_name, rose_ntoa(&saddr.srose.srose_addr));
		paclen = rs_config_get_paclen(NULL);
		p = ROSE_EOL;
		break;
	case AF_INET:
		strcpy(User.ul_name, inet_ntoa(saddr.sin.sin_addr));
		paclen = 1024;
		p = INET_EOL;
		break;
	case AF_UNSPEC:
		strcpy(User.ul_name, "local");
		if ((p = get_call(getuid())) == NULL) {
			log(L_ERROR, "No uid->callsign association found", -1);
			return 1;
		}
		strcpy(User.call, p);
		paclen = 1024;
		p = UNSPEC_EOL;
		break;
	default:
		log(L_ERROR, "Unsupported address family %d", User.ul_type);
		return 1;
	}
	NodeIo = axio_init(STDIN_FILENO, STDOUT_FILENO, paclen, p);
	if (NodeIo == NULL) {
		log(L_ERROR, "Error initializing I/O");
		return 1;
	}
#ifdef HAVE_ZLIB_H
	if (argc > 1 && strcmp(argv[1], "-c") == 0) {
		axio_compr(NodeIo, 1);
	}
#endif
	if (User.ul_type == AF_INET) {
		axio_tnmode(NodeIo, 1);
		axio_tn_do_linemode(NodeIo);
	}
	init_nodecmds();
	if (read_config() == -1) {
		axio_end(NodeIo);
		return 1;
	}
	if (ResolveAddrs && User.ul_type == AF_INET) {
		struct hostent *hp;

		hp = gethostbyaddr((char *)&saddr.sin.sin_addr,
				   sizeof(struct in_addr), AF_INET);
		if (hp != NULL) {
			strncpy(User.ul_name, hp->h_name, 31);
			User.ul_name[31] = 0;
		} else
			log(L_ERROR, "gethostbyaddr: %s", strherror(h_errno));
	}
	User.state = STATE_LOGIN;
	login_user();
	if (User.call[0] == 0) {
		nprintf("\n%s (%s)\n\nlogin: ", VERSION, HostName);
		axio_flush(NodeIo);
		alarm(300L);			/* 5 min timeout */
		if ((p = axio_getline(NodeIo)) == NULL)
			logout("User disconnected");
		alarm(0L);
		strncpy(User.call, p, 9);
		User.call[9] = 0;
		strlwr(User.call);
	}
	if ((p = strstr(User.call, "-0")) != NULL)
		*p = 0;
	if (check_call(User.call) == -1) {
		node_msg("Invalid callsign");
		log(L_LOGIN, "Invalid callsign %s @ %s", User.call, User.ul_name);
		logout("Invalid callsign");
	}
	if ((pw = read_perms(&User, saddr.sin.sin_addr.s_addr)) == NULL) {
		node_msg("Sorry, I'm not allowed to talk to you...");
		log(L_LOGIN, "Login denied for %s @ %s", User.call, User.ul_name);
		logout("Login denied");
	} else if (strcmp(pw, "*") != 0) {
		nputs("Password: ");
		if (User.ul_type == AF_INET) {
			axio_tn_will_echo(NodeIo);
			axio_eolmode(NodeIo, EOLMODE_BINARY);
		}
		axio_flush(NodeIo);
		p = axio_getline(NodeIo);
		if (User.ul_type == AF_INET) {
			axio_tn_wont_echo(NodeIo);
			axio_eolmode(NodeIo, EOLMODE_TEXT);
			nputs("\n");
		}
		if (p == NULL || strcmp(p, pw) != 0) {
			nputs("\n");
			node_msg("Login failed");
			log(L_LOGIN, "Login failed for %s @ %s", User.call, User.ul_name);
			logout("Login failed");
		}
	}
	free(pw);
	ipc_open();
	node_msg("Welcome to %s network node", HostName);
	if ((fp = fopen(CONF_NODE_MOTD_FILE, "r")) != NULL) {
		while (fgets(buf, 256, fp) != NULL)
			nputs(buf);
		nputs("--\n");
	}
	put_prompt();
	log(L_LOGIN, "%s @ %s logged in", User.call, User.ul_name);
	while (1) {
		axio_flush(NodeIo);
		User.state = STATE_IDLE;
		time(&User.cmdtime);
		update_user();
		alarm(IdleTimeout);
		if ((p = axio_getline(NodeIo)) == NULL) {
			if (errno == EINTR)
				continue;
			logout("User disconnected");
		}
		alarm(IdleTimeout);
		time(&User.cmdtime);
		update_user();
		if (cmdparse(Nodecmds, p) == -1) {
			if (++invalid_cmds < 3) {
				node_msg("Unknown command. Type ? for a list");
			} else {
				node_msg("Too many invalid commands. Disconnecting...");
				logout("Too many invalid commands");
			}
		} else
			invalid_cmds = 0;
		put_prompt();
	}
	logout("Out of main loop !?!?!?");
}
