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

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

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

#include "config.h"

int routes(int s, int argc, char *argv[], ax25_address *callsign)
{
	struct ax25_routes_struct ax25_route;
	int i, j;

	if (strcmp(argv[2], "add") == 0) {
		ax25_route.port_addr  = *callsign;
		ax25_route.digi_count = 0;

		if (convert_call_entry(argv[4], (char *)&ax25_route.dest_addr) == -1)
			return 1;

		for (i = 5, j = 0; i < argc && j < 6; i++, j++) {
			if (convert_call_entry(argv[i], (char *)&ax25_route.digi_addr[j]) == -1)
				return 1;
			ax25_route.digi_count++;
		}

		if (ioctl(s, SIOCADDRT, &ax25_route) != 0) {
			perror("add route");
			return 1;
		}
	}
	
	if (strcmp(argv[2], "del") == 0) {
		ax25_route.port_addr  = *callsign;
		ax25_route.digi_count = 0;

		if (convert_call_entry(argv[4], (char *)&ax25_route.dest_addr) == -1)
			return 1;

		if (ioctl(s, SIOCDELRT, &ax25_route) != 0) {
			perror("del route");
			return 1;
		}
	}

	return 0;
}

int setifcall(int s, char *ifn, char *name)
{
	char call[7];
	struct ifreq ifr;
	
	if (convert_call_entry(name, call) == -1)
		return 1;

	strcpy(ifr.ifr_name, ifn);
	memcpy(ifr.ifr_hwaddr.sa_data, call, 7);
	ifr.ifr_hwaddr.sa_family = AF_AX25;
	
	if (ioctl(s, SIOCSIFHWADDR, &ifr) != 0) {
		perror("set hw addr");
		return 1;
	}

	return 0;
}
	
int associate(int s, int argc, char *argv[])
{
	char buffer[80], *u, *c;
	struct sockaddr_ax25 sax25;
	struct passwd *pw;
	int opt;
	FILE *fp;
	
	if (strcmp(argv[2], "show") == 0) {
		if ((fp = fopen("/proc/net/ax25_calls", "r")) == NULL) {
			fprintf(stderr, "axparms: associate: cannot open /proc/net/ax25_calls.\n");
			return 1;
		}

		fgets(buffer, 80, fp);

		printf("Userid     Callsign\n");

		while (fgets(buffer, 80, fp) != NULL) {
			u = strtok(buffer, " \t\n");
			c = strtok(NULL, " \t\n");
			if ((pw = getpwuid(atoi(u))) != NULL)
				printf("%-10s %s\n", pw->pw_name, c);
		}

		fclose(fp);

		return 0;
	}

	if (strcmp(argv[2], "policy") == 0) {
		if (strcmp(argv[3], "default") == 0) {
			opt = AX25_NOUID_DEFAULT;

			if (ioctl(s, SIOCAX25NOUID, &opt) == -1) {
				perror("set policy");
				return 1;
			}

			return 0;
		}

		if (strcmp(argv[3], "deny") == 0) {
			opt = AX25_NOUID_BLOCK;

			if (ioctl(s, SIOCAX25NOUID, &opt) == -1) {
				perror("set policy");
				return 1;
			}

			return 0;
		}

		fprintf(stderr, "axparms: associate: 'default' or 'deny' required\n");

		return 1;
	}
	
	
	if (convert_call_entry(argv[2], (char *)&sax25.sax25_call) == -1) {
		fprintf(stderr, "axparms: associate: invalid callsign %s\n", argv[2]);
		return 1;
	}

	if (strcmp(argv[3], "delete") == 0) {
		if (ioctl(s, SIOCAX25DELUID, &sax25) == -1) {
			perror("del uid");
			return 1;
		}

		return 0;
	}

	if ((pw = getpwnam(argv[3])) == NULL) {
		fprintf(stderr, "axparms: associate: unknown username %s\n", argv[3]);
		return 1;
	}

	sax25.sax25_uid = pw->pw_uid;
		
	if (ioctl(s, SIOCAX25ADDUID, &sax25) == -1) {
		perror("add uid");
		return 1;
	}

	return 0;
}

int display_parms(int s, ax25_address *callsign)
{
	struct ax25_parms_struct ax25_parms;

	ax25_parms.port_addr = *callsign;

	if (ioctl(s, SIOCAX25GETPARMS, &ax25_parms) != 0) {
		perror("get parms");
		return 1;
	}

	printf("Default IP mode               : %s\n",
		(ax25_parms.values[AX25_VALUES_IPDEFMODE] == 'D') ? "Datagram" : "Virtual Circuit");
	printf("Default AX.25 mode            : %s\n",
		(ax25_parms.values[AX25_VALUES_AXDEFMODE] == 8) ? "Standard" : "Extended");

	printf("Is connected mode allowed ?   : %s\n",
		(ax25_parms.values[AX25_VALUES_CONMODE]) ? "Yes" : "No");
	printf("Is NET/ROM allowed ?          : %s\n",
		(ax25_parms.values[AX25_VALUES_NETROM]) ? "Yes" : "No");
	printf("Is AX.25 text mode allowed ?  : %s\n", 
		(ax25_parms.values[AX25_VALUES_TEXT]) ? "Yes" : "No");

	printf("Standard AX.25 window size    : %d\n", ax25_parms.values[AX25_VALUES_WINDOW]);
	printf("Extended AX.25 window size    : %d\n", ax25_parms.values[AX25_VALUES_EWINDOW]);

	printf("Initial T1 value (seconds)    : %d\n", ax25_parms.values[AX25_VALUES_T1]);
	printf("T2 value (seconds)            : %d\n", ax25_parms.values[AX25_VALUES_T2]);
	printf("T3 value (seconds)            : %d\n", ax25_parms.values[AX25_VALUES_T3]);
	printf("N2 value                      : %d\n", ax25_parms.values[AX25_VALUES_N2]);

	return 0;
}

int assign_parms(int s, int argc, char *argv[], ax25_address *callsign)
{
	struct ax25_parms_struct ax25_parms;
	int i, n;

	ax25_parms.port_addr = *callsign;

	if (ioctl(s, SIOCAX25GETPARMS, &ax25_parms) != 0) {
		perror("get parms");
		return 1;
	}

	i = 3;
	
	while (i < argc) {
		if (strncmp(argv[i], "-ip", 3) == 0) {
			n = toupper(argv[i + 1][0]);
			if (n != 'D' && n != 'V') {
				fprintf(stderr, "axparms: parms: invalid default ip mode flag %s\n", argv[i + 1]);
			} else {
				ax25_parms.values[AX25_VALUES_IPDEFMODE] = n;
			}
			
			i++;
		}

		if (strncmp(argv[i], "-ax", 3) == 0) {
			n = toupper(argv[i + 1][0]);
			if (n != 'E' && n != 'S') {
				fprintf(stderr, "axparms: parms: invalid default AX.25 mode flag %s\n", argv[i + 1]);
			} else {
				if (n == 'S') {
					ax25_parms.values[AX25_VALUES_AXDEFMODE] = 8;
				} else {
					ax25_parms.values[AX25_VALUES_AXDEFMODE] = 128;
				}
			}
			
			i++;
		}

		if (strncmp(argv[i], "-con", 4) == 0) {
			n = toupper(argv[i + 1][0]);
			if (n != 'Y' && n != 'N') {
				fprintf(stderr, "axparms: parms: invalid connected mode allowed flag %s\n", argv[i + 1]);
			} else {
				ax25_parms.values[AX25_VALUES_CONMODE] = n == 'Y';
			}
			
			i++;
		}

		if (strncmp(argv[i], "-netr", 5) == 0) {
			n = toupper(argv[i + 1][0]);
			if (n != 'Y' && n != 'N') {
				fprintf(stderr, "axparms: parms: invalid NET/ROM allowed flag %s\n", argv[i + 1]);
			} else {
				ax25_parms.values[AX25_VALUES_NETROM] = n == 'Y';
			}
			
			i++;
		}

		if (strncmp(argv[i], "-text", 5) == 0) {
			n = toupper(argv[i + 1][0]);
			if (n != 'Y' && n != 'N') {
				fprintf(stderr, "axparms: parms: invalid AX.25 allowed flag %s\n", argv[i + 1]);
			} else {
				ax25_parms.values[AX25_VALUES_TEXT] = n == 'Y';
			}
			
			i++;
		}

		if (strncmp(argv[i], "-wind", 5) == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1 || n > 7) {
				fprintf(stderr, "axparms: parms: invalid standard window value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_WINDOW] = n;
			}
			
			i++;
		}

		if (strncmp(argv[i], "-ewind", 6) == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1 || n > 63) {
				fprintf(stderr, "axparms: parms: invalid extended window value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_EWINDOW] = n;
			}
			
			i++;
		}

		if (strcmp(argv[i], "-t1") == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1) {
				fprintf(stderr, "axparms: parms: invalid t1 value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_T1] = n;
			}
			
			i++;
		}

		if (strcmp(argv[i], "-t2") == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1) {
				fprintf(stderr, "axparms: parms: invalid t2 value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_T2] = n;
			}
			
			i++;
		}

		if (strcmp(argv[i], "-t3") == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1) {
				fprintf(stderr, "axparms: parms: invalid t3 value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_T3] = n;

			}
			
			i++;
		}

		if (strcmp(argv[i], "-n2") == 0) {
			n = atoi(argv[i + 1]);
			if (n < 1 || n > 31) {
				fprintf(stderr, "axparms: parms: invalid n2 value %d\n", n);
			} else {
				ax25_parms.values[AX25_VALUES_N2] = n;
			}
			
			i++;
		}

		i++;
	}

	if (ioctl(s, SIOCAX25SETPARMS, &ax25_parms) != 0) {
		perror("set parms");
		return 1;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	ax25_address callsign;
	int s, n;
	
	if (strncmp(argv[1], "-a", 2) == 0) {
		if (argc == 2) {
			fprintf(stderr, "usage: axparms -assoc [callsign] [username]\n");
			fprintf(stderr, "usage: axparms -assoc [callsign] delete\n");
			fprintf(stderr, "usage: axparms -assoc policy default|deny\n");
			fprintf(stderr, "usage: axparms -assoc show\n");
			return 1;
		}

		if ((s = socket(AF_AX25, SOCK_SEQPACKET, 0)) < 0) {
			perror("socket");
			return 1;
		}

		n = associate(s, argc, argv);

		close(s);

		return n;
	}

	if (strncmp(argv[1], "-p", 2) == 0) {
		if (argc == 2) {
			fprintf(stderr, "usage: axparms -parms port ...\n");
			return 1;
		}
	
		n = atoi(argv[2]) - 1;
	
		if (ax25_config_load_ports() == -1) {
			fprintf(stderr, "axparms: no AX.25 port data configured\n");
			return 1;
		}

		if (n < 0 || n > ax25_config_num_ports()) {
			fprintf(stderr, "axparms: invalid port number %s\n", argv[2]);
			return 1;
		}

		if (convert_call_entry(ax25_config_get_addr(n), callsign.ax25_call) == -1)
			return 1;

		if ((s = socket(AF_AX25, SOCK_SEQPACKET, 0)) < 0) {
			perror("socket");
			return 1;
		}

		if (argc == 3) {
			printf("Parameters for device %s with callsign %s\n", ax25_config_get_dev(n), ax25_config_get_addr(n));
			n = display_parms(s, &callsign);
		} else {
			n = assign_parms(s, argc, argv, &callsign);
		}

		close(s);

		return n;
	}

	if (strncmp(argv[1], "-r", 2) == 0) {
		if (argc < 5) {
			fprintf(stderr, "usage: axparms -route add port callsign [digi ...]\n");
			fprintf(stderr, "usage: axparms -route del port callsign\n");
			return 1;
		}

		if (strcmp(argv[2], "add") != 0 && strcmp(argv[2], "del") != 0) {
			fprintf(stderr, "usage: axparms -route add port callsign [digi ...]\n");
			fprintf(stderr, "usage: axparms -route del port callsign\n");
			return 1;
		}
		
		n = atoi(argv[3]) - 1;
	
		if (ax25_config_load_ports() == -1) {
			fprintf(stderr, "axparms: no AX.25 port data configured\n");
			return 1;
		}

		if (n < 0 || n > ax25_config_num_ports()) {
			fprintf(stderr, "axparms: invalid port number %s\n", argv[2]);
			return 1;
		}

		if (convert_call_entry(ax25_config_get_addr(n), callsign.ax25_call) == -1)
			return 1;

		if ((s = socket(AF_AX25, SOCK_SEQPACKET, 0)) < 0) {
			perror("socket");
			return 1;
		}

		n = routes(s, argc, argv, &callsign);

		close(s);
		
		return n;
	}

	if (strncmp(argv[1], "-s", 2) == 0) {
		if (argc != 4) {
			fprintf(stderr, "usage: axparms -setcall interface callsign\n");
			return 1;
		}
	
		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
			perror("socket");
			return 1;
		}

		n = setifcall(s, argv[2], argv[3]);

		close(s);

		return n;
	}

	fprintf(stderr, "usage: axparms -assoc|-parms|-route|-setcall ...\n");

	return 1;
}
