/* $USAGI: nid.c,v 1.3 2001/07/14 23:49:15 yoshfuji Exp $ */

/*
 * Copyright (C)2000-2001 Hideaki YOSHIFUJI and USAGI Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* ========================================================================= */
#include "config.h"
#define _GNU_SOURCE
#include <features.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <linux/uts.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
#if HAVE_SSL_MD5_H
#include <ssl/md5.h>
#elif HAVE_OPENSSL_MD5_H
#include <openssl/md5.h>
#else
#include "md5.h"
#endif

/* ========================================================================= */
#ifndef IPV6_JOIN_GROUP
#define IPV6_JOIN_GROUP		IPV6_ADD_MEMBERSHIP
#endif
#ifndef IPV6_LEAVE_GROUP
#define	IPV6_LEAVE_GROUP	IPV6_DROP_MEMBERSHIP
#endif

/* ========================================================================= */
/* Configs */
char *ConfigFile;
int SetBuflen;
int SetControlBuflen;

/* Static Buffers */
#define MIN_PACKET_BUFSIZE	4096
char __packetbuf[MIN_PACKET_BUFSIZE];
char __controlbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
char __temp1[MIN_PACKET_BUFSIZE + 16], __temp2[MIN_PACKET_BUFSIZE + 16];
char __outbuf[MIN_PACKET_BUFSIZE];
struct sockaddr_storage __from, __to;
u_int32_t to_ifindex;

/* ========================================================================= */
/* Prototypes */
int setsockopt_nifilter(int);
int setsockopt_ipv6pktinfo(int, int);
int setsockopt_ipv6checksum(int, int);
int setsockopt_joinnigroup(int, const char *);
struct in6_addr in6_nigroup(const char *, size_t len);

/* ========================================================================= */
/* -- Set socket options -- */
/* icmpv6 filter */
int setsockopt_nifilter(int sock)
{
	struct icmp6_filter filter;
	ICMP6_FILTER_SETBLOCKALL(&filter);
	ICMP6_FILTER_SETPASS(ICMP6_NI_QUERY, &filter);
	return (setsockopt(sock,
			   IPPROTO_ICMPV6, ICMP6_FILTER,
			   &filter, sizeof(struct icmp6_filter)));
}

/* ipv6 pktinfo */
int setsockopt_ipv6pktinfo(int sock, int i)
{
	return (setsockopt
		(sock, IPPROTO_IPV6, IPV6_PKTINFO, &i, sizeof(i)));
}

/* ipv6 checksum */
int setsockopt_ipv6checksum(int sock, int i)
{
	return (setsockopt
		(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &i, sizeof(i)));
}

/* join NI gorup address */
int setsockopt_joinnigroup(int sock, const char *name)
{
	/* XXX: Which interface do you want to be bound; set ipv6mr_interface */
	struct ipv6_mreq ipv6_mreq;

	if (!name || !strlen(name))
		return 1;

	memset(&ipv6_mreq, 0, sizeof(ipv6_mreq));
	ipv6_mreq.ipv6mr_multiaddr = in6_nigroup(name, strlen(name));
	if (IN6_IS_ADDR_UNSPECIFIED(&ipv6_mreq.ipv6mr_multiaddr))
		return 1;

	return (setsockopt(sock,
			   IPPROTO_IPV6, IPV6_JOIN_GROUP,
			   &ipv6_mreq, sizeof(ipv6_mreq)));
}

/* -- Send NI packet -- */
int
send_ni_packet(int sock, struct in6_addr *in6,
	       u_int8_t type, u_int8_t code,
	       u_int16_t qtype, u_int16_t flag,
	       u_int8_t nonce[8], void *data, size_t datalen)
{
	struct msghdr msghdr;
	struct iovec iov[3];
	struct sockaddr_in6 sin6;
	struct icmp6_nodeinfo ni6;

	if (datalen < 0)
		return -1;

	memset(&ni6, 0, sizeof(ni6));
	memset(&iov, 0, sizeof(iov));
	memset(&msghdr, 0, sizeof(msghdr));
	memset(&sin6, 0, sizeof(sin6));

	ni6.ni_type = type;	/* ICMP_NI_{QUERY,REPLY} */
	ni6.ni_code = code;	/* ICMP_NI_{SUCCESS,REFUSED,UNKNOWN} etc. */
	ni6.ni_cksum = 0;	/* check sum */
	ni6.ni_qtype = qtype;	/* htons(NI_QTYPE_{NOOP,SUPTYPES,FQDN,NODEADDR,V4ADDR}) */
	ni6.ni_flags = flag;	/* NI_*_FLAG_* */
	memcpy(ni6.icmp6_ni_nonce, nonce, sizeof(ni6.icmp6_ni_nonce));

	iov[0].iov_base = (caddr_t) & ni6;
	iov[0].iov_len = sizeof(ni6);
	iov[1].iov_base = (caddr_t) data;
	iov[1].iov_len = data ? datalen : 0;
#if 0
	iov[2].iov_base = (caddr_t) NULL;
	iov[2].iov_len = 0;
#endif

	msghdr.msg_name = (caddr_t) & sin6;
	msghdr.msg_namelen = sizeof(sin6);
	msghdr.msg_iov = iov;
	msghdr.msg_iovlen = data ? 2 : 1;

	sin6.sin6_family = AF_INET6;
#ifdef SIN6_LEN
	sin6.sin6_len = sizeof(sin6);
#endif
	memcpy(&sin6.sin6_addr, in6, sizeof(sin6.sin6_addr));

	return sendmsg(sock, &msghdr, 0);
}

/* -- Create NI Group Address -- */
struct in6_addr in6_nigroup(const char *name, size_t len)
{
	struct in6_addr in6;
	MD5_CTX ctxt;
	unsigned char digest[16];
	char l;

	if (len > 63)
		return in6addr_any;
	l = (char) len;

	memset(&ctxt, 0, sizeof(ctxt));
	MD5_Init(&ctxt);
	MD5_Update(&ctxt, &l, sizeof(l));
	MD5_Update(&ctxt, name, len);
	MD5_Final(digest, &ctxt);

	/* FF02:0:0:0:0:2::/96 */
	memset(&in6, 0, sizeof(in6));
	in6.s6_addr[ 0] = 0xff;
	in6.s6_addr[ 1] = 0x02;
	in6.s6_addr[11] = 0x02;
	memcpy(&in6.s6_addr[12], digest, 4);

	return in6;
}

/* -- Encode / Decode DNS wire format (RFC1035 P.28)-- */
int encode_dnsname(const char *name, char *buf, int fqdn)
{
	/* XXX: sizeof(buf[]) must >= sizeof(name[]) + 2 */
	/* "some.where.domain" */
	char *p, *d;
	int len;
	size_t clen;

	len = strlen(name) + 1;
	buf[0] = '\0';
	strcpy(&buf[1], name);
	p = buf;
	while (p < &buf[1 + len - 1]) {
		d = strchr(p + 1, '.');
		if (d)
			*d = '\0';
		clen = strlen(p + 1);
		if (clen > 255) {
			len = -1;
			break;
		}
		*p = (char) clen;
		p += 1 + clen;
	}
	if (len > 0) {
		*p = '\0';
		if (!fqdn) {
			*p++ = '\0';
			len++;
		}
	}
	return len + 1;
}

int decode_dnsname(const char *dnsname, size_t len, char *buf)
{
	/* XXX: strange */
	const char *p;
	int nofqdn = -1, cc = 0;
	size_t clen;
	char *last = NULL;

	p = dnsname;
	while (p) {
		clen = 0xff & (int) (p[0]);
		if (1 + clen > len) {
			break;
		}
		if (clen == 0) {
			if (len == 2 && p[1] == 0) {
				*(last ? last : buf) = '\0';
				nofqdn = cc;
				break;
			} else if (len == 1) {
				*(last ? last : buf) = '\0';
				nofqdn = 0;
				break;
			}
		}
		memmove(buf, &p[1], clen);
		len -= clen + 1;
		last = buf += clen;
		*buf++ = '.';
		cc++;
		p += clen + 1;
	}
	return nofqdn;
}

/* ========================================================================= */
/* -- Main -- */
int main(int argc, char *argv[])
{
	/* ------------------------- */
	/* Socket */
	int sock;
	/* Message header and IO vectors */
	struct msghdr msghdr;
	struct iovec iov[2];
	/* Source / destination address */
	struct sockaddr_in6 *from, *to;
	socklen_t *fromlen;
	/* Buffers */
	char *packetbuf, *controlbuf;
	int packetbuflen, controlbuflen;
	struct utsname utsname;
	/* Pointers etc. */
	struct icmp6_nodeinfo *ni6;
	struct cmsghdr *cmsg;
	size_t datalen;
	/* Misc. */
	char addrbuf[NI_MAXHOST];
	int cc;
	char c;

	/* ------------------------- */
	while ((c = getopt(argc, argv, "f:b:")) != EOF) {
		switch (c) {
		case 'f':
			{
				ConfigFile = optarg;
				break;
			}
		case 'b':
			{
				SetBuflen = 1;
				packetbuflen = atoi(optarg);
				if (packetbuflen < 0)
					packetbuflen = 0;
				break;
			}
		}
	}

	/* ------------------------- */
	/* Make a RAW socket */
	sock = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (sock < 0) {
		perror("socket");
		exit(1);
	}

	/* Set ICMPv6 filter */
	if (setsockopt_nifilter(sock) < 0) {
		perror("setsockopt_nifilter()");
		close(sock);
		exit(1);
	}

	/* Set IPV6_PKTINFO option to the raw socket */
	if (setsockopt_ipv6pktinfo(sock, 1) < 0) {
		perror("setsockopt_ipv6pktinfo()");
		close(sock);
		exit(1);
	}

	/* Ensure to have kernel set check sum */
	if (setsockopt_ipv6checksum
	    (sock,
	     ((size_t) (&((struct icmp6_nodeinfo *) NULL)->ni_cksum))) < 0) {
		perror("setsockopt_ipv6checksum()");
		exit(1);
	}

	/* Get system name and bind to the NI Group Address */
	if (uname(&utsname) < 0) {
		perror("uname");
		close(sock);
		exit(1);
	}
	if (setsockopt_joinnigroup(sock, utsname.nodename) < 0) {
		perror("setsockopt_joinnigroup()");
#if 0
		close(sock);
		exit(1);
#endif
	}

	/* -------------------------- */
	/* Initialize variables */
	/* we may want more buffer; alloca? */
	if (SetBuflen) {
		packetbuf = malloc(packetbuflen);
		if (!packetbuf) {
			perror("malloc");
			exit(1);
		}
	} else {
		packetbuf = __packetbuf;
		packetbuflen = sizeof(__packetbuf);
	}

	/* misc. */
	ni6 = (struct icmp6_nodeinfo *) packetbuf;
	from = (struct sockaddr_in6 *) &__from;
	to = (struct sockaddr_in6 *) &__to;

	/* ------------------------- */
#if ENABLE_DAEMON
	daemon(0, 0);
#endif

	/* ------------------------- */
	/* Setup MSGHDR and IOVEC structres */
	memset(&msghdr, 0, sizeof(msghdr));
	memset(&iov, 0, sizeof(iov));
	msghdr.msg_name = (void *) &__from;
	msghdr.msg_namelen = sizeof(__from);	/* useless */
	msghdr.msg_iov = iov;
	msghdr.msg_iovlen = 1;
	msghdr.msg_control = controlbuf;
	msghdr.msg_controllen = controlbuflen;	/* useless */
	iov[0].iov_base = packetbuf;
	iov[0].iov_len = packetbuflen;

	fromlen = &msghdr.msg_namelen;

	while (1) {
		/* re-initialize buffer */
		msghdr.msg_namelen = sizeof(__from);
		iov[0].iov_len = packetbuflen;
		to->sin6_addr = in6addr_any;

		/* receive message */
		if ((cc = recvmsg(sock, &msghdr, 0)) < 0) {
			if (errno == EAGAIN || errno == EINTR)
				continue;
			/* XXX: syslog() */
			continue;
		}

		/* get ipmpv6 destination address */
		for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg;
		     cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
			if (cmsg->cmsg_level == IPPROTO_IPV6 &&
			    cmsg->cmsg_type == IPV6_PKTINFO) {
				struct in6_pktinfo *in6_pktinfo =
				    (struct in6_pktinfo *) CMSG_DATA(cmsg);
				memcpy(&to->sin6_addr,
				       &in6_pktinfo->ipi6_addr,
				       sizeof(in6_pktinfo->ipi6_addr));
				to_ifindex = in6_pktinfo->ipi6_ifindex;
#if HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
				if (IN6_IS_ADDR_LINKLOCAL(to)
				    || IN6_IS_ADDR_MC_LINKLOCAL(to)) {
					to->sin6_scope_id =
					    (IN6_IS_ADDR_LINKLOCAL(to)
					     ||
					     IN6_IS_ADDR_MC_LINKLOCAL(to))
					    ? to_ifindex : 0;
				}
#endif
				break;
			}
		}

#warning Sorry, destination address checking is not implemented...
		/* XXX: check the destination address */
#if DEBUG
		if (!getnameinfo
		    ((struct sockaddr *) from, *fromlen, addrbuf,
		     sizeof(addrbuf), NULL, 0, NI_NUMERICHOST)) {
			printf("%s\n", addrbuf);
		} else {
			printf("?\n");
		}
		printf("\tcc=%d, nonce=%08x %08x qtype=%04X, flag=%04X\n",
		       cc, *(long *) ni6->icmp6_ni_nonce,
		       *(1 + (long *) ni6->icmp6_ni_nonce),
		       htons(ni6->ni_qtype), htons(ni6->ni_flags));
#endif

		/* --------------------------------- */
		/* check size */
		if (cc < sizeof(*ni6)) {
			/* XXX */
#if DEBUG
			perror("NI too small!?");
#endif
			continue;
		}
		datalen = cc - sizeof(*ni6);	/* length of Data field */

		/* --------------------------------- */
		/* check the ni_type */
		if (ni6->ni_type != ICMP6_NI_QUERY) {
			/* XXX */
#if DEBUG
			perror("ICMP NI QUERY?!");
#endif
			continue;
		}

		/* --------------------------------- */
		/* check the ni_code */
		switch (ni6->ni_code) {
		case ICMP6_NI_SUBJ_IPV6:
#if 0
			if (datalen < sizeof(struct in6_addr))
				/* XXX: Packet is too small */
				continue;
#endif
			break;
		case ICMP6_NI_SUBJ_FQDN:
			break;
		case ICMP6_NI_SUBJ_IPV4:
#if 0
			if (datalen < sizeof(struct in_addr))
				/* XXX: Packet is too small */
				continue;
#endif
			break;
		default:
			perror("NI_CODE?");	/* XXX */
			continue;
		}

		/* --------------------------------- */
		/* check the qtype */
		switch (htons(ni6->ni_qtype)) {
		case NI_QTYPE_NOOP:
			/* NOOP: draft-05 section 5.1 */
			{
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_SUCCESS,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce, NULL,
					       0);
				break;
			}
		case NI_QTYPE_SUPTYPES:
			/* Supported QTypes : draft-05 section 5.2 */
			{
#if defined(NI_QTYPE_V4ADDR) && defined(ENABLE_IPV4MAPPED)
				u_int32_t suptypes_data =
				    htonl(0x0000001f);
#else
				u_int32_t suptypes_data =
				    htonl(0x0000000f);
#endif
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_SUCCESS,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce,
					       &suptypes_data, 4);
				break;
			}
		case NI_QTYPE_FQDN:
			/* Name: draft-05 section 5.3 */
			{
#warning Sorry, NI DNS Name Query is not fully supported...
				size_t namelen;
				size_t domainlen;
				size_t buflen;
				size_t len;
				int fqdn = 0;
				char *buf = __temp1;
				char *obuf = __temp2;
				namelen =
				    strcmp(utsname.nodename,
					   UTS_NODENAME) ? strlen(utsname.
								  nodename)
				    : 0;
				if (!namelen) {
					/* hostname is not known */
					send_ni_packet(sock,
						       &from->sin6_addr,
						       ICMP6_NI_REPLY,
						       ICMP6_NI_SUCCESS,
						       ni6->ni_qtype, 0,
						       ni6->icmp6_ni_nonce,
						       NULL, 0);
					break;
				}
				domainlen =
				    strcmp(utsname.domainname,
					   UTS_DOMAINNAME) ?
				    strlen(utsname.domainname) : 0;
				buflen =
				    domainlen ? (namelen + 1 + domainlen +
						 1 + 1) : (namelen + 1);
				if (buflen + 2 > sizeof(__temp2)) {
					/* XXX: we should try to trunc the result */
					send_ni_packet(sock,
						       &from->sin6_addr,
						       ICMP6_NI_REPLY,
						       ICMP6_NI_REFUSED,
						       ni6->ni_qtype, 0,
						       ni6->icmp6_ni_nonce,
						       NULL, 0);
					break;
				}
				if (domainlen) {
					stpcpy(stpcpy
					       (stpcpy
						(buf, utsname.nodename),
						"."), utsname.domainname);
					fqdn = 1;
				} else {
					strcpy(buf, utsname.nodename);
				}
#ifdef DEBUG
				printf("%s: \n", buf);
#endif
				memset(obuf, 0, 4);	/* clear TTL */
				len = encode_dnsname(buf, &obuf[4], fqdn);
#ifdef DEBUG
				{
					int i;
					for (i = 0; i < 4 + len; i++) {
						printf(" %02X",
						       0xff & (int)
						       obuf[i]);
					}
				}
#endif
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_SUCCESS,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce, obuf,
					       4 + len);
				break;
			}
		case NI_QTYPE_NODEADDR:
			/* IPv6 Addresses: draft-05 section 5.4 */
			{
#warning Sorry, NI Node Address Query is not supported at this moment...
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_REFUSED,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce, NULL,
					       0);
				break;
			}
#if defined(NI_QTYPE_V4ADDR) && defined(ENABLE_IPV4MAPPED)
		case NI_QTYPE_V4ADDR:
			/* IPv4 Addresses: draft-05 section 5.5 */
			{
#warning Sorry, NI IPv4 Address Query is not supported at this moment...
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_REFUSED,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce, NULL,
					       0);
				break;
			}
#endif
		default:
			/* Qtype is not supported: draft-05 section 3 */
			{
				send_ni_packet(sock, &from->sin6_addr,
					       ICMP6_NI_REPLY,
					       ICMP6_NI_UNKNOWN,
					       ni6->ni_qtype, 0,
					       ni6->icmp6_ni_nonce, NULL,
					       0);
				break;
			}
		}
	}
}
