/*
 * AppleTalk MacIP Gateway
 *
 * (c) 2013, 1997 Stefan Bethke. All rights reserved.
 * (c) 2015 Jason King. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>

#include <netatalk/at.h>
#include <atalk/aep.h>
#include <atalk/ddp.h>
#include <atalk/atp.h>
#include <atalk/zip.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include "atp_input.h"
#include "nbp_lkup_async.h"

#include "common.h"
#include "macip.h"
#include "util.h"

#define DDPTYPE_MACIP (22)
#define MACIP_ASSIGN (1)
#define MACIP_SERVER (3)
#define MACIP_PORT (72)

#define MACIP_MAXMTU (586)

#define MACIP_NODETYPE "IPADDRESS"
#define MACIP_GATETYPE "IPGATEWAY"

#define MACIP_ATPRETRIES (5)
#define MACIP_ATPWAIT (5)
#define MACIP_NBPRETRIES (5)
#define MACIP_NBPWAIT (5)

#define ARPTIMEOUT (30)
#define ARPRETRIES (10)
/* ping every 30secs, give up after 5mins */

#define ASSIGN_FREE   (0)
#define ASSIGN_LEASED (1)
#define ASSIGN_FIXED  (2)

struct macip_req_control {
	uint16_t mipr_version;
	uint16_t _mipr_pad1;
	uint32_t mipr_function;
} __attribute__((__packed__));

struct macip_req_data {
	uint32_t mipr_ipaddr;
	uint32_t mipr_nameserver;
	uint32_t mipr_broadcast;
	uint32_t _mipr_pad2;
	uint32_t mipr_subnet;
} __attribute__((__packed__));

struct macip_req {
	struct macip_req_control miprc;
	struct macip_req_data miprd;
} __attribute__((__packed__));

struct ipent {
	int assigned;
	long timo;
	int retr;
	struct sockaddr_at sat;
};

struct macip_data {
	uint32_t net;
	uint32_t mask;
	uint32_t broadcast;
	uint32_t nameserver;
	uint32_t addr;
	int nipent;
	struct ipent *ipent;

	int sock;
	ATP atp;

	char name[32];
	char type[32];
	char zone[32];
};

#define IPADDR(e) (e - gMacip.ipent + gMacip.net + 1)

#define MAXZONES 256

struct zones {
	int n;
	char *z[MAXZONES];
};

static struct macip_data gMacip;
static struct zones gZones;
static outputfunc_t gOutput;


static uint16_t cksum(char *buffer, int len)
{
	uint16_t *b = (uint16_t *) buffer;
	uint32_t sum = 0;

	len /= 2;
	while (len--) {
		sum += *b++;
	}
	sum = (sum & 0xffff) + ((sum >> 16) & 0xffff);
	sum = sum > 65535 ? sum - 65535 : sum;
	return (~sum & 0xffff);
}


static void icmp_echo(uint32_t src, uint32_t dst)
{
	char buffer[500];
	struct ip *ip = (struct ip *) buffer;
	struct icmp *icmp = (struct icmp *) (buffer + 20);

	bzero(buffer, sizeof(buffer));
	ip->ip_v = IPVERSION;
	ip->ip_hl = 5;
	ip->ip_ttl = 255;
	ip->ip_src.s_addr = htonl(src);
	ip->ip_dst.s_addr = htonl(dst);
	ip->ip_id = 1;
	ip->ip_p = IPPROTO_ICMP;
	ip->ip_len = htons(20 + 8);
	ip->ip_sum = 0;
	ip->ip_sum = cksum((char *) ip, 20);
	icmp->icmp_type = ICMP_ECHO;
	icmp->icmp_code = 0;
	icmp->icmp_cksum = 0;
	icmp->icmp_cksum = cksum((char *) icmp, 8);

	macip_output(buffer, ntohs(ip->ip_len));
}



static long now(void)
{
	static struct timeval tv;

	gettimeofday(&tv, 0);
	return tv.tv_sec;
}


struct ipent *get_ipent(uint32_t ip)
{
	return (ip - gMacip.net > 0 && ip - gMacip.net < gMacip.nipent) ?
	    &gMacip.ipent[ip - gMacip.net - 1] : 0;
}

/*
 * aquire a new ip address from the pool
 */

static uint32_t lease_ip()
{
	int i;

	for (i = 0; i < gMacip.nipent; i++) {
		if (gMacip.ipent[i].assigned == ASSIGN_FREE) {
			gMacip.ipent[i].assigned = ASSIGN_LEASED;
			bzero(&gMacip.ipent[i].sat,
			      sizeof(struct sockaddr_at));
			gMacip.ipent[i].retr = ARPRETRIES;
			gMacip.ipent[i].timo = ARPTIMEOUT;
			return gMacip.net + i + 1;
		}
	}
	return 0;
}


/*
 * find AT address for an IP address
 */

static int arp_lookup(struct sockaddr_at *sat, uint32_t ip)
{
	int i;
	char s[32];
	struct ipent *e = get_ipent(ip);

	if (e && e->assigned) {
		if (gDebug & DEBUG_MACIP)
			printf("found arp entry: %s -> %d.%d\n",
			       iptoa(ip), ntohs(e->sat.sat_addr.s_net),
			       e->sat.sat_addr.s_node);
		bcopy(&e->sat, sat, sizeof(*sat));
		return 1;
	}

	strcpy(s, iptoa(ip));
	for (i = 0; i < gZones.n; i++) {
		nbp_lookup_req(gMacip.sock, s, MACIP_NODETYPE,
			       gZones.z[i]);
	}

	return 0;
}


/*
 * Set AT address from received packet
 */

static void arp_set(uint32_t ip, struct sockaddr_at *sat)
{
	struct ipent *e;

	e = get_ipent(ip);
	if (e) {
		bcopy(sat, &e->sat, sizeof(struct sockaddr_at));
		e->timo = now() + ARPTIMEOUT;
		e->retr = ARPRETRIES;
		if (e->assigned == ASSIGN_FREE)
			e->assigned = ASSIGN_LEASED;
		if (gDebug & DEBUG_MACIP)
			printf("arp_set: %s -> %d.%d\n", iptoa(ip),
			       ntohs(sat->sat_addr.s_net),
			       sat->sat_addr.s_node);
	}
}

/*
 *	handle name lookup replies, add to arp table
 */

static void arp_input(struct sockaddr_at *sat, char *buffer, int len)
{
	struct nbpnve *nve;
	char s[32];
	uint32_t ip;

	while ((nve = nbp_parse_lkup_repl(buffer, len)) != NULL) {
		if (gDebug & DEBUG_MACIP)
			printf("received nbp entry '%.*s:%.*s@%.*s'\n",
			       nve->nn_objlen, nve->nn_obj,
			       nve->nn_typelen, nve->nn_type,
			       nve->nn_zonelen, nve->nn_zone);

		if (nve->nn_typelen != strlen(MACIP_NODETYPE) ||
		    strncasecmp(nve->nn_type, MACIP_NODETYPE,
				strlen(MACIP_NODETYPE)) != 0) {
			continue;
		}
		bcopy(nve->nn_obj, s, nve->nn_objlen);
		s[nve->nn_objlen] = 0;
		ip = atoip(s);
		if (ip == 0)
			continue;
		arp_set(ip, &nve->nn_sat);
	}
	/* send packets waiting */
}

/*
 *	handle incoming IP packet
 */

static void ip_input(struct sockaddr_at *sat, char *buffer, int len)
{
	struct ip *p = (struct ip *) buffer;

	if (gDebug & DEBUG_MACIP) {
		printf("got IP packet from %d.%d\n",
		       ntohs(sat->sat_addr.s_net), sat->sat_addr.s_node);
		printf("\tsource=%s, ", iptoa(ntohl(p->ip_src.s_addr)));
		printf("dest=%s\n", iptoa(ntohl(p->ip_dst.s_addr)));
	}

	arp_set(ntohl(p->ip_src.s_addr), sat);

	gOutput(buffer, len);
}

/*
 *	send IP packet through AT
 */

void macip_output(char *buffer, int len)
{
	struct sockaddr_at sat;
	struct ip *ip = (struct ip *) buffer;
	char ob[MACIP_MAXMTU + 1];

	if (len > MACIP_MAXMTU) {
		if (gDebug & DEBUG_MACIP)
			printf
			    ("macip_output: packet to large, dropped.\n");
		return;
		/*      actually, we could fragment here, but this should never happen,
		   as the MTU for the tunnel should be small enough.
		 */
	}
	if (arp_lookup(&sat, ntohl(ip->ip_dst.s_addr))) {
		bcopy(ip, ob + 1, len);
		*ob = DDPTYPE_MACIP;
		len++;
		if (sendto(gMacip.sock, ob, len, 0,
			   (struct sockaddr *) &sat, sizeof(sat)) <= 0) {
			if (gDebug & DEBUG_MACIP)
				perror("macip_output: sendto");
		}
	} else {
		/* XXX we could queue the packet here for delivery when the
		   address has been looked up */
	}
}

/*
 *	handle configuration ATP request
 */

static void config_input(ATP atp, struct sockaddr_at *faddr, char *packet,
			 int len)
{
	struct atp_block atpb;
	struct sockaddr_at sat;
	struct iovec iov;
	char buffer[600];
	struct macip_req *rq;
	int f;
	uint32_t ip;

	if (atp_input(atp, faddr, packet, len) < 0) {
		if (gDebug & DEBUG_MACIP)
			printf("atp_input: packet rejected.\n");
		return;
	}

	bzero(&sat, sizeof(sat));
	sat.sat_family = AF_APPLETALK;
	sat.sat_addr.s_net = ATADDR_ANYNET;
	sat.sat_addr.s_node = ATADDR_ANYNODE;
	sat.sat_port = ATADDR_ANYPORT;

	if (atp_rsel(atp, &sat, ATP_TREQ) != ATP_TREQ) {
		return;		/* something other than a new request */
	}

	atpb.atp_saddr = &sat;
	atpb.atp_rreqdata = buffer;
	atpb.atp_rreqdlen = sizeof(buffer);

	if (atp_rreq(atp, &atpb) < 0) {
		if (gDebug & DEBUG_MACIP)
			perror("macip: config_input: atp_rreq");
		return;
	}

	rq = (struct macip_req *) (atpb.atp_rreqdata);
	f = ntohl(rq->miprc.mipr_function);
	len = atpb.atp_rreqdlen;
	if (gDebug & DEBUG_MACIP)
		printf("\nMacIP req: %d from %d.%d\n", f,
		       ntohs(atpb.atp_saddr->sat_addr.s_net),
		       atpb.atp_saddr->sat_addr.s_node);

	rq = (struct macip_req *) buffer;
	bzero(rq, sizeof(struct macip_req));
	switch (f) {
	case MACIP_ASSIGN:
		rq->miprc.mipr_version = htons(1U);
		if ((ip = lease_ip())) {
			rq->miprc.mipr_function = htonl(1U);

			rq->miprd.mipr_ipaddr = htonl(ip);
			rq->miprd.mipr_nameserver =
			    htonl(gMacip.nameserver);
			rq->miprd.mipr_broadcast = htonl(gMacip.broadcast);
			rq->miprd.mipr_subnet = htonl(gMacip.mask);
			len = sizeof(struct macip_req);
			if (gDebug & DEBUG_MACIP)
				printf("assigned %s.\n", iptoa(ip));
		} else {
			rq->miprc.mipr_function = htonl(0);
			len = sizeof(struct macip_req);
		}
		break;

	case MACIP_SERVER:
		rq->miprc.mipr_version = htons(1U);
		rq->miprc.mipr_function = htonl(3U);
		len = sizeof(struct macip_req_control);
		break;

	default:
		if (gDebug & DEBUG_MACIP)
			printf
			    ("macip_input: unknown request #%d received; ignored.\n",
			     f);
		rq->miprc.mipr_version = htons(1U);
		rq->miprc.mipr_function = htonl(0U);
		len = sizeof(struct macip_req_control);
	}
	iov.iov_base = buffer;
	iov.iov_len = len;

	atpb.atp_sresiov = &iov;
	atpb.atp_sresiovcnt = 1;
	if (atp_sresp(atp, &atpb) < 0 && gDebug & DEBUG_MACIP) {
		perror("macip_input: atp_sresp");
	}
}

/*
 *	Handle incoming AT packet
 */

void macip_input(void)
{
	struct sockaddr_at sat;
	char buffer[800];
	ssize_t len;
	socklen_t flen;

	bzero(&sat, sizeof(sat));
#ifdef BSD4_4
	sat.sat_len = sizeof(struct sockaddr_at);
#endif
	sat.sat_family = AF_APPLETALK;
	sat.sat_addr.s_net = ATADDR_ANYNET;
	sat.sat_addr.s_node = ATADDR_ANYNODE;
	sat.sat_port = ATADDR_ANYPORT;

	flen = sizeof(struct sockaddr_at);
	if ((len = recvfrom(gMacip.sock, buffer, ATP_BUFSIZ,
			    0, (struct sockaddr *) &sat, &flen)) > 0) {
		if (gDebug & DEBUG_MACIP)
			printf("macip_input: packet: DDP=%d, len=%zu\n",
			       *buffer, len);
		switch (*buffer) {
		/*DDPTYPE*/ case DDPTYPE_NBP:
			arp_input(&sat, buffer, len);
			break;
		case DDPTYPE_ATP:
			config_input(gMacip.atp, &sat, buffer, len);
			break;
		case DDPTYPE_MACIP:
			ip_input(&sat, buffer + 1, len - 1);
			break;
		}
	} else {
		perror("recvfrom");
	}
}


static int init_ip(uint32_t net, uint32_t mask, uint32_t nameserver)
{
	bzero(&gMacip, sizeof(gMacip));
	gMacip.net = net;
	gMacip.mask = mask;
	gMacip.nameserver = nameserver;
	gMacip.broadcast = net | ~mask;

	gMacip.nipent = (~mask) - 1;
	if ((gMacip.ipent =
	     calloc(gMacip.nipent, sizeof(struct ipent))) == 0) {
		if (gDebug & DEBUG_MACIP)
			perror("init_ip: calloc");
		return -1;
	}

	gMacip.addr = net + 1;	/* my address */
	gMacip.ipent[0].assigned = ASSIGN_FIXED;

	if (gDebug & DEBUG_MACIP) {
		printf("init_ip: network %s/", iptoa(gMacip.net));
		printf("%s, ", iptoa(gMacip.mask));
		printf("broadcast %s\n\t%d addresses available\n",
		       iptoa(gMacip.broadcast), gMacip.nipent - 1);
		printf("\tmy ip=%s\n", iptoa(gMacip.net + 1));
	}

	return 0;
}


static void add_zones(short n, char *buf)
{
	char s[32];
	for (; n--; buf += (*buf) + 1) {
		if (gDebug & DEBUG_MACIP)
			printf("add_zones: %.*s\n", *buf, buf + 1);
		bcopy(buf + 1, s, *buf);
		s[(int) *buf] = 0;
		if (gZones.n < MAXZONES)
			gZones.z[gZones.n++] = strdup(s);
	}
}

static int get_zones(void)
{
	struct atp_handle *ah;
	struct atp_block atpb;
	struct sockaddr_at saddr;
	struct servent *se;
	char reqdata[4], buf[ATP_MAXDATA];
	struct iovec iov;
	short temp, index = 0;
	int i;

	gZones.n = 0;

	reqdata[0] = ZIPOP_GETZONELIST;
	if ((ah = atp_open(0, NULL)) == NULL) {
		perror("atp_open");
		return -1;
	}
	bzero((char *) &saddr, sizeof(struct sockaddr_at));
#ifdef BSD4_4
	saddr.sat_len = sizeof(struct sockaddr_at);
#endif
	saddr.sat_family = AF_APPLETALK;
	if ((se = getservbyname("zip", "ddp")) == NULL) {
		fprintf(stderr, "Unknown service \"zip/ddp\".\n");
		return -1;
	}
	saddr.sat_port = ntohs(se->s_port);
	saddr.sat_addr.s_net = ATADDR_ANYNET;
	saddr.sat_addr.s_node = ATADDR_ANYNODE;

	index = 1;
	reqdata[1] = 0;

	do {
		atpb.atp_saddr = &saddr;
		temp = htons(index);
		bcopy(&temp, &reqdata[2], 2);
		atpb.atp_sreqdata = reqdata;
		atpb.atp_sreqdlen = 4;
		atpb.atp_sreqto = 2;
		atpb.atp_sreqtries = 5;

		/* send getzone request zones (or get my zone)
		 */
		if (atp_sreq(ah, &atpb, 1, 0) < 0) {
			perror("atp_sreq");
			return -1;
		}

		iov.iov_base = buf;
		iov.iov_len = ATP_MAXDATA;
		atpb.atp_rresiov = &iov;
		atpb.atp_rresiovcnt = 1;


		i = atp_rresp(ah, &atpb);
		if (i < 0) {
			if (errno == ETIMEDOUT) {
				gZones.n = 1;
				gZones.z[0] = "*";
				if (gDebug & DEBUG_MACIP)
					printf("no zones on network\n");
				return 0;
			}
			perror("get_zones: atp_rresp");
			return -1;
		}

		bcopy(&((char *) iov.iov_base)[2], &temp, 2);
		temp = ntohs(temp);

		add_zones(temp, iov.iov_base + 4);

		index += temp;
	} while (!((char *) iov.iov_base)[0]);

	if (atp_close(ah) != 0) {
		perror("atp_close");
		return -1;
	}
	return 0;
}


/*
 * time out old arp entries
 */

void macip_idle(void)
{
	struct ipent *e;
	int i;
	long n = now();

	for (i = gMacip.nipent, e = gMacip.ipent; i--; e++) {
		if ((e->assigned == ASSIGN_LEASED) && (e->timo < n)) {
			if (e->retr--) {
				icmp_echo(gMacip.addr, IPADDR(e));
				e->timo = n + ARPTIMEOUT;
				if (gDebug & DEBUG_MACIP)
					printf
					    ("macip_idle: sending probe to %s.\n",
					     iptoa(IPADDR(e)));
			} else {
				e->assigned = ASSIGN_FREE;
				if (gDebug & DEBUG_MACIP)
					printf
					    ("macip_idle: arp entry %s/%d.%d free'd.\n",
					     iptoa(IPADDR(e)),
					     ntohs(e->sat.sat_addr.s_net),
					     e->sat.sat_addr.s_node);
			}
		}
	}
}


int
macip_open(char *zone, uint32_t net, uint32_t mask, uint32_t ns,
	   outputfunc_t o)
{
	int i;

	if (init_ip(net, mask, ns)) {
		if (gDebug & DEBUG_MACIP)
			printf("macip_open: init_ip failed.\n");
		return -1;
	}

	for (i=0; i < MACIP_ATPRETRIES; i++) {
		if (i == MACIP_ATPRETRIES-1) {
			printf ("macip_open: too many retries\n");
			return -1;
		}
		if ((gMacip.atp=atp_open(MACIP_PORT, NULL)) == NULL) {
			if (gDebug & DEBUG_MACIP)
				perror ("macip_open: atp_open");
			printf ("macip_open: retrying in %d seconds\n", MACIP_ATPRETRIES);
			sleep (MACIP_ATPWAIT);
		} else
			break;
	}

	gMacip.sock = gMacip.atp->atph_socket;

	strcpy(gMacip.name, iptoa(gMacip.addr));
	strcpy(gMacip.type, MACIP_GATETYPE);
	strcpy(gMacip.zone, zone);
	for (i=0; i < MACIP_NBPRETRIES; i++) {
		if (i == MACIP_NBPRETRIES-1) {
			printf ("macip_open: too many retries\n");
			return -1;
		}
		if (gDebug & DEBUG_MACIP) {
			printf ("macip_open: registering %s:%s@%s...",
				gMacip.name, gMacip.type, gMacip.zone);
			fflush (stdout);
		}
		if (nbp_rgstr (atp_sockaddr (gMacip.atp),
				gMacip.name,
				gMacip.type,
				gMacip.zone) < 0) {
			if (gDebug & DEBUG_MACIP)
				perror ("failed");
			printf ("macip_open: retrying in %d seconds\n", MACIP_NBPRETRIES);
			sleep (MACIP_NBPWAIT);
		} else
			break;
	}
	if (gDebug & DEBUG_MACIP)
		printf("done.\n");
	if (get_zones())
		return -1;

	nbp_lookup_req(gMacip.sock, "=", "IPADDRESS", "*");

	gOutput = o;

	return gMacip.sock;
}


void macip_close(void)
{
	nbp_unrgstr(gMacip.name, gMacip.type, gMacip.zone, NULL);
	close(gMacip.sock);
}
