/* $USAGI: ninfod_name.c,v 1.15 2003-01-11 14:33:28 yoshfuji Exp $ */
/*
 * Copyright (C) 2002 USAGI/WIDE 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.
 */
/*
 * Author:
 * 	YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if STDC_HEADERS
# include <stdio.h>
# include <stdlib.h>
# include <stddef.h>
# include <ctype.h>
#else
# if HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif
#if HAVE_STRING_H
# if !STDC_HEADERS && HAVE_MEMORY_H
#  include <memory.h>
# endif
# include <string.h>
#endif
#if HAVE_STRINGS_H
# include <strings.h>
#endif
#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
#  include <stdint.h>
# endif
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#if TIME_WITH_SYS_TIME   
# include <sys/time.h>  
# include <time.h>
#else
# if HAVE_SYS_TIME_H     
#  include <sys/time.h>
# else                
#  include <time.h>
# endif                  
#endif                   

#if HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif

#include <sys/socket.h>

#if HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#if HAVE_NETINET_ICMP6_H
# include <netinet/icmp6.h>
#endif
#ifndef HAVE_STRUCT_ICMP6_NODEINFO
# include "icmp6_nodeinfo.h"
#endif

#include <arpa/inet.h>

#if defined(HAVE_GNUTLS_OPENSSL_H)
# include <gnutls/openssl.h>
#elif defined(HAVE_OPENSSL_MD5_H)
# include <openssl/md5.h>
#endif

#if HAVE_SYS_UTSNAME_H
# include <sys/utsname.h>
#endif
#if HAVE_NETDB_H
# include <netdb.h>
#endif
#include <errno.h>

#if HAVE_SYSLOG_H
# include <syslog.h>
#endif

#include "ninfod.h"

#ifndef offsetof
# define offsetof(aggregate,member)	((size_t)&((aggregate *)0)->member)
#endif

/* Hmm,,, */
#ifndef IPV6_JOIN_GROUP
# define IPV6_JOIN_GROUP	IPV6_ADD_MEMBERSHIP
# define IPV6_LEAVE_GROUP	IPV6_DROP_MEMBERSHIP
#endif

/* ---------- */
/* ID */
static char *RCSID __attribute__ ((unused)) = "$USAGI: ninfod_name.c,v 1.15 2003-01-11 14:33:28 yoshfuji Exp $";

/* Variables */
static struct utsname utsname;
static char *uts_nodename = utsname.nodename;

char nodename[MAX_DNSNAME_SIZE];
static size_t nodenamelen;

static struct ipv6_mreq nigroup;

/* ---------- */
/* Functions */
int check_nigroup(const struct in6_addr *addr)
{
	return IN6_IS_ADDR_MULTICAST(&nigroup.ipv6mr_multiaddr) &&
	       IN6_ARE_ADDR_EQUAL(&nigroup.ipv6mr_multiaddr, addr);
}

static int encode_dnsname(const char *name, 
			  char *buf, size_t buflen, 
			  int fqdn)
{
	size_t namelen;
	int i;

	if (buflen < 0)
		return -1;

	namelen = strlen(name);
	if (namelen == 0)
		return 0;
	if (namelen > 255 || buflen < namelen+1)
		return -1;

	i = 0;
	while(i <= namelen) {
		const char *e;
		int llen, ii;

		e = strchr(&name[i], '.');
		if (e == NULL)
			e = name + namelen;
		llen = e - &name[i];
		if (llen == 0) {
			if (*e)
				return -1;
			if (fqdn < 0)
				return -1;
			fqdn = 1;
			break;
		}
		if (llen >= 0x40)
			return -1;
		buf[i] = llen;
		for (ii = 0; ii < llen; ii++) {
			if (!isascii(name[i+ii]))
				return -1;
			if (ii == 0 || ii == llen-1) {
				if (!isalpha(name[i+ii]) && !isdigit(name[i+ii]))
					return -1;
			} else if (!isalnum(name[i+ii]) && name[i+ii] != '-')
				return -1;
			buf[i+ii+1] = isupper(name[i+ii]) ? tolower(name[i+ii]) : name[i+ii];
		}
		i += llen + 1;
	}
	if (buflen < i + 1 + !(fqdn > 0))
		return -1;
	buf[i++] = 0;
	if (!(fqdn > 0))
		buf[i++] = 0;
	return i;
}

static int compare_dnsname(const char *s, size_t slen,
			   const char *n, size_t nlen)
{
	const char *s0 = s, *n0 = n;
	int done = 0, retcode = 0;
	if (slen < 1 || nlen < 1)
		return -1;	/* invalid length */
	/* simple case */
	if (slen == nlen && memcmp(s, n, slen) == 0)
		return 0;
	if (*(s0 + slen - 1) || *(n0 + nlen - 1))
		return -1;	/* invalid termination */
	while (s < s0 + slen && n < n0 + nlen) {
		if (*s >= 0x40 || *n >= 0x40)
			return -1;	/* DNS compression is not allowed here */
		if (s + *s + 1 > s0 + slen || n + *n + 1 > n0 + nlen)
			return -1;	/* overrun */
		if (*s == '\0') {
			if (s == s0 + slen - 1)
				break;	/* FQDN */
			else if (s + 1 == s0 + slen - 1)
				return retcode;	/* truncated */
			else
				return -1;	/* more than one subject */
		}
		if (!done) {
			if (*n == '\0') {
				if (n == n0 + nlen - 1) {
					done = 1;	/* FQDN */
				} else if (n + 1 == n0 + nlen - 1) {
					retcode = 1;	// trunc
					done = 1;
				} else
					return -1;
			} else {
				if (*s != *n) {
					done = 1;
					retcode = 1;
				} else {
					if (memcmp(s+1, n+1, *s)) {
						done = 1;
						retcode = 1;
					}
				}
			}
		}
		s += *s + 1;
		n += done ? 0 : (*n + 1);
	}
	return retcode;
}

static int nodeinfo_group(const char *dnsname, int namelen, 
			  struct in6_addr *nigroup)
{
	MD5_CTX ctxt;
	unsigned char digest[16];

	if (!dnsname || !nigroup)
		return -1;

	MD5_Init(&ctxt);
	MD5_Update(&ctxt, dnsname, *dnsname);
	MD5_Final(digest, &ctxt);

#ifdef s6_addr32
	nigroup->s6_addr32[0] = htonl(0xff020000);
	nigroup->s6_addr32[1] = 0;
	nigroup->s6_addr32[2] = htonl(0x00000002);
#else
	memset(nigroup, 0, sizeof(*nigroup));
	nigroup->s6_addr[ 0] = 0xff;
	nigroup->s6_addr[ 1] = 0x02;
	nigroup->s6_addr[11] = 0x02;
#endif
	memcpy(&nigroup->s6_addr[12], digest, 4);

	return 0;
}

/* ---------- */
void init_nodeinfo_nodename(int forced)
{
	struct utsname newname;
	int len;
	int changed = 0;

	DEBUG(LOG_DEBUG, "%s()\n", __func__);

	uname(&newname);
	changed = strcmp(newname.nodename, utsname.nodename);

	if (!changed && !forced)
		return;

	memcpy(&utsname, &newname, sizeof(newname));

	/* leave old group */
	if ((changed || forced) && !IN6_IS_ADDR_UNSPECIFIED(&nigroup.ipv6mr_multiaddr)) {
		if (setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &nigroup, sizeof(nigroup)) < 0) {
#if ENABLE_DEBUG
			char niaddrbuf[INET6_ADDRSTRLEN];
			if (inet_ntop(AF_INET6, &nigroup, niaddrbuf, sizeof(niaddrbuf)) == NULL)
				strcpy(niaddrbuf, "???");
#endif
			DEBUG(LOG_WARNING,
			      "%s(): failed to leave group %s.\n",
			      __func__, niaddrbuf);
			memset(&nigroup, 0, sizeof(nigroup));
		}
	}

	len = encode_dnsname(uts_nodename,
			     nodename, 
			     sizeof(nodename),
			     0);

	/* setup ni reply */
	nodenamelen = len > 0 ? len : 0;

	/* setup ni group */
	if (changed || forced) {
		if (nodenamelen) {
			memset(&nigroup, 0, sizeof(nigroup));
			nodeinfo_group(nodename, len, &nigroup.ipv6mr_multiaddr);
			nigroup.ipv6mr_interface = 0;
			if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &nigroup, sizeof(nigroup)) < 0) {
#if ENABLE_DEBUG
				char niaddrbuf[INET6_ADDRSTRLEN];
				if (inet_ntop(AF_INET6, &nigroup, niaddrbuf, sizeof(niaddrbuf)) == NULL)
					strcpy(niaddrbuf, "???");
#endif
				DEBUG(LOG_WARNING,
				      "%s(): failed to join group %s.\n",
				      __func__, niaddrbuf);
				memset(&nigroup, 0, sizeof(nigroup));
			}
		} else {
			memset(&nigroup, 0, sizeof(nigroup));
		}
	}

	return;
}

/* ---------- */
/* nodename */
int pr_nodeinfo_nodename(CHECKANDFILL_ARGS)
{
	DEBUG(LOG_DEBUG, "%s()\n", __func__);

	if (subject) {
		if (!nodenamelen ||
		    compare_dnsname(subject, subjlen, 
				    nodename, 
				    nodenamelen))
			return 1;
		if (subj_if)
			*subj_if = p->pktinfo.ipi6_ifindex;
	}

	if (reply) {
		uint32_t ttl = 0;

		p->reply.ni_type = ICMP6_NI_REPLY;
		p->reply.ni_code = ICMP6_NI_SUCCESS;
		p->reply.ni_cksum = 0;
		p->reply.ni_qtype = htons(NI_QTYPE_DNSNAME);
		p->reply.ni_flags = 0;

		p->replydatalen = nodenamelen ? sizeof(ttl)+nodenamelen : 0;
		p->replydata = nodenamelen ? ni_malloc(p->replydatalen) : NULL;
		if (p->replydata) {
			memcpy(p->replydata, &ttl, sizeof(ttl));
			memcpy(p->replydata + sizeof(ttl), &nodename, nodenamelen);
		}
	}

	return 0;
}