#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <linux/if.h>
#include <linux/ax25.h>

#include <netinet/in.h>

#include "config.h"

#define AX_CONFIG_PORT_FILE	"/usr/local/etc/axports"
#define NR_CONFIG_PORT_FILE	"/usr/local/etc/nrports"

typedef struct _axport
{
	struct _axport *Next;
	char *Call;
	char *Device;
	int  Baud;
	int  Window;
	int  Paclen;
	char *Description;
} AX_Port;

typedef struct _nrport
{
	struct _nrport *Next;
	char *Call;
	char *Alias;
	char *Device;
	int  Paclen;
	char *Description;
} NR_Port;

static AX_Port *ax25_ports     = NULL;
static AX_Port *ax25_port_tail = NULL;

static NR_Port *nr_ports       = NULL;
static NR_Port *nr_port_tail   = NULL;

static int ax25_hw_cmp(unsigned char *callsign, unsigned char *hw_addr)
{
	unsigned char call[7], *c;
	int i;

	memset(call, ' ' << 1, 6);
	call[6] = 0x00;

	for (c = call, i = 0; i < 6; i++, c++, callsign++) {
		if (isupper(*callsign) || isdigit(*callsign))
			*c = *callsign << 1;
		else
			break;
	}

	if (*callsign == '-') {
		callsign++;
		i = atoi(callsign);
		call[6] = ((i + '0') << 1) & 0x1E;
	}
	
	for (c = call, i = 0; i < 6; i++, c++, hw_addr++)
		if (*c != *hw_addr)
			return 0;

	if ((*c & 0x1E) != (*hw_addr & 0x1E))
		return 0;

	return 1;
}

int ax25_config_num_ports(void)
{
	int ct = 0;
	AX_Port *p = ax25_ports;

	while (p != NULL) {
		ct++;
		p = p->Next;
	}

	return ct;
}

int nr_config_num_ports(void)
{
	int ct = 0;
	NR_Port *p = nr_ports;

	while (p != NULL) {
		ct++;
		p = p->Next;
	}

	return ct;
}

static AX_Port *ax25_port_ptr(int n)
{
	AX_Port *p = ax25_ports;

	while (n-- && p != NULL)
		p = p->Next;

	return p;
}

static NR_Port *nr_port_ptr(int n)
{
	NR_Port *p = nr_ports;

	while (n-- && p != NULL)
		p = p->Next;

	return p;
}

char *ax25_config_get_addr(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return NULL;

	return p->Call;
}

char *ax25_config_get_dev(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Device;
}

int ax25_config_get_window(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Window;
}

int ax25_config_get_paclen(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Paclen;
}

int ax25_config_get_baud(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Baud;
}

char *ax25_config_get_desc(int port)
{
	AX_Port *p = ax25_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Description;
}

char *nr_config_get_addr(int port)
{
	NR_Port *p = nr_port_ptr(port);

	if (p == NULL)
		return NULL;

	return p->Call;
}

char *nr_config_get_dev(int port)
{
	NR_Port *p = nr_port_ptr(port);

	if (p == NULL)
		return NULL;

	return p->Device;
}

char *nr_config_get_alias(int port)
{
	NR_Port *p = nr_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Alias;
}

int nr_config_get_paclen(int port)
{
	NR_Port *p = nr_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Paclen;
}

char *nr_config_get_desc(int port)
{
	NR_Port *p = nr_port_ptr(port);

	if (p == NULL)
		return 0;

	return p->Description;
}

static AX_Port *ax25_config_init_port(int fd, char *line)
{
	AX_Port *p;
	char buffer[1024], *call, *baud, *wind, *desc, *dev = NULL;
	struct ifconf ifc;
	struct ifreq *ifrp;
	struct ifreq ifr;
	struct arpreq req;
	int n, paclen = 120, found = 0;
	struct sockaddr_ax25 *ax25;

	call = strtok(line, " \t\r\n");
	baud = strtok(NULL, " \t\r\n");
	wind = strtok(NULL, " \t\r\n");
	desc = strtok(NULL, "\r\n");

	if (desc == NULL) {
		fprintf(stderr, "Unable to parse input line\n");
		return NULL;
	}

	ifc.ifc_len = sizeof(buffer);
	ifc.ifc_buf = buffer;
	
	if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
		fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno));
		return NULL;
	}

	for (ifrp = ifc.ifc_req, n = ifc.ifc_len / sizeof(struct ifreq); --n >= 0; ifrp++) {
		strcpy(ifr.ifr_name, ifrp->ifr_name);

		if (strcmp(ifr.ifr_name, "lo") == 0) continue;

		if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFFLAGS: %s\n", strerror(errno));
			return NULL;
		}

		if (!(ifr.ifr_flags & IFF_UP)) continue;

		if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFMTU: %s\n", strerror(errno));
			return NULL;
		}

		paclen = ifr.ifr_mtu;

		if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFHWADDR: %s\n", strerror(errno));
			return NULL;
		}

		switch (ifr.ifr_hwaddr.sa_family) {
			case ARPHRD_AX25:
				if (ax25_hw_cmp(call, ifr.ifr_hwaddr.sa_data)) {
					dev = ifr.ifr_name;
					found = 1;
				}
				break;
			case ARPHRD_ETHER:
				if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
					fprintf(stderr, "SIOCGIFADDR: %s\n", strerror(errno));
					return NULL;
				}

				memcpy((char *)&req.arp_pa, &ifr.ifr_addr, sizeof(struct sockaddr));
				req.arp_ha.sa_family = AF_AX25;

				if (ioctl(fd, SIOCGARP, &req) < 0) {
					if (errno != ENXIO) {
						fprintf(stderr, "SIOCGARP: %s\n", strerror(errno));
						return NULL;
					}
					continue;
				}

				ax25 = (struct sockaddr_ax25 *)&req.arp_ha;

				if (ax25_hw_cmp(call, ax25->sax25_call.ax25_call)) {
					dev = ifr.ifr_name;
					found = 1;
				}
				break;
		}

		if (found) break;
	}

	if (!found) {
		fprintf(stderr, "Device with callsign %s not active\n", call);
		return NULL;
	}

	if ((p = (AX_Port *)malloc(sizeof(AX_Port))) == NULL) {
		fprintf(stderr, "Out of memory!\n");
		return NULL;
	}

	p->Call        = strdup(call);
	p->Device      = strdup(dev);
	p->Baud        = atoi(baud);
	p->Window      = atoi(wind);
	p->Paclen      = paclen;
	p->Description = strdup(desc);

	if (ax25_ports == NULL)
		ax25_ports = p;
	else
		ax25_port_tail->Next = p;

	ax25_port_tail = p;

	p->Next = NULL;

	return p;
}

static NR_Port *nr_config_init_port(int fd, char *line)
{
	NR_Port *p;
	char buffer[1024], *call, *alias, *desc, *dev = NULL;
	struct ifconf ifc;
	struct ifreq *ifrp;
	struct ifreq ifr;
	int n, paclen = 120, found = 0;
	
	call  = strtok(line, " \t\r\n");
	alias = strtok(NULL, " \t\r\n");
	desc  = strtok(NULL, " \t\r\n");

	if (desc == NULL) {
		fprintf(stderr, "Unable to parse input line\n");
		return NULL;
	}

	ifc.ifc_len = sizeof(buffer);
	ifc.ifc_buf = buffer;
	
	if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
		fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno));
		return NULL;
	}

	for (ifrp = ifc.ifc_req, n = ifc.ifc_len / sizeof(struct ifreq); --n >= 0; ifrp++) {
		strcpy(ifr.ifr_name, ifrp->ifr_name);

		if (strcmp(ifr.ifr_name, "lo") == 0) continue;

		if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFFLAGS: %s\n", strerror(errno));
			return NULL;
		}

		if (!(ifr.ifr_flags & IFF_UP)) continue;

		if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFMTU: %s\n", strerror(errno));
			return NULL;
		}

		paclen = ifr.ifr_mtu;

		if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
			fprintf(stderr, "SIOCGIFHWADDR: %s\n", strerror(errno));
			return NULL;
		}

		if (ifr.ifr_hwaddr.sa_family != ARPHRD_NETROM) continue;

		if (ax25_hw_cmp(call, ifr.ifr_hwaddr.sa_data)) {
			dev = ifr.ifr_name;
			found = 1;
			break;
		}
	}

	if (!found) {
		fprintf(stderr, "Device with callsign %s not active\n", call);
		return NULL;
	}

	if ((p = (NR_Port *)malloc(sizeof(NR_Port))) == NULL) {
		fprintf(stderr, "Out of memory!\n");
		exit(1);
	}

	p->Call        = strdup(call);
	p->Alias       = strdup(alias);
	p->Device      = strdup(dev);
	p->Paclen      = paclen;
	p->Description = strdup(desc);

	if (nr_ports == NULL)
		nr_ports = p;
	else
		nr_port_tail->Next = p;

	nr_port_tail = p;

	p->Next = NULL;

	return p;
}

int ax25_config_load_ports(void)
{
	FILE *fp;
	char buf[256];
	int fd;

	if ((fp = fopen(AX_CONFIG_PORT_FILE, "r")) == NULL) {
		perror(AX_CONFIG_PORT_FILE);
		return -1;
	}

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		fprintf(stderr, "Unable to open socket\n");
		fclose(fp);
		return -1;
	}

	while (fgets(buf, 255, fp) != NULL)
		ax25_config_init_port(fd, buf);

	fclose(fp);
	close(fd);

	if (ax25_config_num_ports() == 0)
		return -1;

	return 0;
}

int nr_config_load_ports(void)
{
	FILE *fp;
	char buf[256];
	int fd;

	if ((fp = fopen(NR_CONFIG_PORT_FILE, "r")) == NULL) {
		perror(NR_CONFIG_PORT_FILE);
		return -1;
	}

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		fprintf(stderr, "Unable to open socket\n");
		fclose(fp);
		return -1;
	}

	while (fgets(buf, 255, fp) != NULL)
		nr_config_init_port(fd, buf);

	fclose(fp);
	close(fd);

	if (nr_config_num_ports() == 0)
		return -1;

	return 0;
}
