#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 "ax25.h"
#include "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"

int aliascmd = 0;

/*
 * 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...");
	node_logout("Timeout");
}

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

int main(int argc, char *argv[])
{
	union {
		struct full_sockaddr_ax25 sax;
#ifdef HAVE_ROSE		
		struct sockaddr_rose      srose;
#endif
		struct sockaddr_in        sin;
	} saddr;
	int i, slen = sizeof(saddr);
	char *p, buf[256], *pw;
	int paclen;
	FILE *fp;
	int invalid_cmds = 0;
 	int no_password = 1;
 	int first_time = 1;
 	
	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();
#ifdef HAVE_ROSE			
	rs_config_load_ports();
#endif	
	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_FLEXNET:
	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;
#ifdef HAVE_ROSE				
	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;
#endif		
	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;
	}
	for(i=1;i<argc;i++) {
          if (strcmp(argv[i],"--delay")==0) {
	    usflush(User.fd);
	    p = readline(User.fd);
          }
        }
	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)
			node_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(LOGLVL_LOGIN, "Invalid callsign %s @ %s", User.call, User.ul_name);
		node_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(LOGLVL_LOGIN, "Login denied for %s @ %s", User.call, User.ul_name);
		node_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);
			node_logout("Login failed");
		}
	no_password = 0;
	};
	free(pw);
	examine_user();
	ipc_open();
	log(LOGLVL_LOGIN, "%s @ %s logged in", User.call, User.ul_name);
	tprintf("\n%s\n", VERSION);
	tputs("\n");
	if ((fp = fopen(CONF_NODE_MOTD_FILE, "r")) != NULL) {
		while (fgets(buf, 256, fp) != NULL) tputs(buf);
		tputs ("\n");
	tputs("\n");}
	lastlog();
	newmail(); 
	while (1) {
	        mailcheck();
		if (no_password == 2) 	tprintf("%s\n",Prompt);
		if (no_password == 1) 	{
					tprintf("%s\n",Prompt);
					no_password = 2;
					}
		if (no_password == 0) 
					{
    					if (first_time == 1) 
						{
						first_time = 0;
						if (User.ul_type != AF_INET) tprintf("%s\n",Prompt);
						}
					else 	tprintf("%s\n",Prompt);
					}
		usflush(User.fd);
		User.state = STATE_IDLE;
		time(&User.cmdtime);
		update_user();
		alarm(IdleTimeout);
		if ((p = readline(User.fd)) == NULL) {
			if (errno == EINTR)
				continue;
			node_logout("User disconnected");
		};
		alarm(IdleTimeout);
		time(&User.cmdtime);
		update_user();
		aliascmd = 0;
		switch (cmdparse(Nodecmds, p))
		{
		case -1: 
			if (++invalid_cmds < 3) {
			tputs("\n");
			node_msg("Unknown command. Type ? for a list"); 
			tputs("\n");
						}
			else 	{
			tputs("\n");
			node_msg("Too many invalid commands. Disconnecting...");
			node_logout("Too many invalid commands");
				}
			break;
		case 0: 
		invalid_cmds = 0; 
			tputs ("\n");
			break;
		case 2: 
		invalid_cmds = 0; 
			break;
		}  	
		mailcheck();
		}
	node_logout("Out of main loop !?!?!?");
}
