#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <linux/socket.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netax25/ax25.h>
#include <netrose/rose.h>

#include "node.h"
#include "io.h"
#include "axutils.h"
#include "config.h"
#include "axconfig.h"
#include "nrconfig.h"
#include "rsconfig.h"
#include "procutils.h"

/*
 * 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 alarm_handler(int sig)
{
	set_eolmode(User.fd, EOLMODE_TEXT);
	tputs("\n");
	node_msg("Timeout! Disconnecting...");
	logout("Timeout");
}

static void term_handler(int sig)
{
	set_eolmode(User.fd, EOLMODE_TEXT);
	tputs("\n");
	node_msg("System going down! Disconnecting...");
	logout("SIGTERM");
}

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, alarm_handler);
	signal(SIGTERM, term_handler);
	signal(SIGPIPE, SIG_IGN);
	if (ax25_config_load_ports() == 0) {
		log(LOGLVL_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(LOGLVL_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, ax2asc(&saddr.sax.fsa_ax25.sax25_call));
		if (getsockname(STDOUT_FILENO, (struct sockaddr *)&saddr.sax, &slen) == -1) {
			log(LOGLVL_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, ax2asc(&saddr.sax.fsa_ax25.sax25_call));
		strcpy(User.ul_name, ax2asc(&saddr.sax.fsa_digipeater[0]));
		if (getsockname(STDOUT_FILENO, (struct sockaddr *)&saddr.sax, &slen) == -1) {
			log(LOGLVL_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, ax2asc(&saddr.srose.srose_call));
		strcpy(User.ul_name, rose2asc(&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(LOGLVL_ERROR, "No uid->callsign association found", -1);
			usflush(User.fd);
			return 1;
		}
		strcpy(User.call, p);
		paclen = 1024;
		p = UNSPEC_EOL;
		break;
	default:
		log(LOGLVL_ERROR, "Unsupported address family %d", User.ul_type);
		return 1;
	}
	if (init_io(User.fd, paclen, p) == -1) {
		write(User.fd, "Error initializing I/O.\r\n", 25);
		log(LOGLVL_ERROR, "Error initializing I/O");
		return 1;
	}
	if (User.ul_type == AF_INET) {
		set_telnetmode(User.fd, 1);
		tn_do_linemode(User.fd);
	}
	init_nodecmds();
	if (read_config() == -1) {
		end_io(User.fd);
		return 1;
	}
	User.state = STATE_LOGIN;
	login_user();
	if (User.call[0] == 0) {
		tprintf("\n%s (%s)\n\nlogin: ", VERSION, HostName);
		usflush(User.fd);
		alarm(300L);			/* 5 min timeout */
		if ((p = readline(User.fd)) == 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;
#ifndef FRA519	
	if (check_call(User.call) == -1) {
		node_msg("Invalid callsign");
		log(LOGLVL_LOGIN, "Invalid callsign %s @ %s", User.call, User.ul_name);
		logout("Invalid callsign");
	}
#endif	
	if ((pw = read_perms(&User, saddr.sin.sin_addr.s_addr)) == NULL) {
		node_msg("Sorry, I'm not allowed to talk to you...");
		log(LOGLVL_LOGIN, "Login denied for %s @ %s", User.call, User.ul_name);
		logout("Login denied");
	} else if (strcmp(pw, "*") != 0) {
		tprintf("Password: ");
		if (User.ul_type == AF_INET) {
			tn_will_echo(User.fd);
			set_eolmode(User.fd, EOLMODE_BINARY);
		}
		usflush(User.fd);
		p = readline(User.fd);
		if (User.ul_type == AF_INET) {
			tn_wont_echo(User.fd);
			set_eolmode(User.fd, EOLMODE_TEXT);
			tputs("\n");
		}
		if (p == NULL || strcmp(p, pw) != 0) {
			tputs("\n");
			node_msg("Login failed");
			log(LOGLVL_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\n", HostName);
	if ((fp = fopen(CONF_NODE_MOTD_FILE, "r")) != NULL) {
		while (fgets(buf, 256, fp) != NULL)
			tputs(buf);
		tputs("--\n\n");
	}
	log(LOGLVL_LOGIN, "%s @ %s logged in", User.call, User.ul_name);
	while (1) {
		usflush(User.fd);
		User.state = STATE_IDLE;
		time(&User.cmdtime);
		update_user();
		alarm(IdleTimeout);
		if ((p = readline(User.fd)) == 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;
		tputs("\n");
	}
	logout("Out of main loop !?!?!?");
}
