/****************************************************************************
 * Copyright (C) 1998 WIDE Project. All rights reserved.
 * Copyright (C) 1999,2000,2001,2002 University of Tromso. All rights reserved.
 * Copyright (C) 2002 Invenia Innovation AS. All rights reserved.
 *
 * Author: Feike W. Dillema, feico@pasta.cs.uit.no.
 *         based on newbie code by Yusuke DOI, Keio Univ. Murai Lab.
 ****************************************************************************/

/*
 * <$Id: ne_io.c,v 3.65 2005/03/31 09:28:29 dillema Exp $>
 */

#include "totd.h"

static G_List *NI_head = NULL;	/* lists are to be allocated */
static G_List *NI_wildcard = NULL;	/* linked element of wildcard socket */
static G_List *NI_wildcard6 = NULL;	/* wildcard 6 */

static int net_ifc_cmp (struct ifconf *, struct ifconf *);
static Nia *nia_alloc (struct sockaddr *, int, int);
static void nia_list_destroy (G_List *);
static int nia_is_in_totd_iflist(char *);
static int net_get_ifaddrs (G_List *, int);
static int nia_set_wildsock (G_List *, int);

/*
 * nia_find_by_sock    : find Nia by socket id
 * nia_fds_set         : set all bits in the fd_set for all socks
 * nia_fds_isset       : check Nia available for fd_set
 *
 * net_init_socketlist
 * net_reinit_socketlist
 * net_bind_socketlist
 * net_delete_socketlist
 *
 * net_stream_socket: open stream socket, bind it and listen
 * net_mesg_socket: open dgram binded socket on a interface
 *
 * net_mesg_send  : send message from specific interface
 *
 */

static int nia_is_in_totd_iflist(char *ifname) {
	char **ifp;

	ifp = T.iflist;
	while (*ifp) {
		if (!strcmp("any", *ifp))
			return 1;
		if (!strcmp("all", *ifp))
			return 1;
		if (!strcmp(ifname, *ifp))
			return 1;
		ifp++;
	}
	return 0;
}

static int net_ifc_cmp (struct ifconf *ifc1, struct ifconf *ifc2) {
	struct ifreq *ifr_p1;
	struct ifreq *ifr_p2;
	u_char *cp1;
	u_char *cp2;

	if (ifc1->ifc_len != ifc2->ifc_len) {
		if (T.debug > 4) 
			syslog (LOG_DEBUG, "net_ifc_cmp(): lengths differ");
		return 1;
	}

	cp1 = ifc1->ifc_buf;
	cp2 = ifc2->ifc_buf;

	while (*cp1 && *cp2) {
		ifr_p1 = (struct ifreq *) cp1;
		ifr_p2 = (struct ifreq *) cp2;

		cp1 = cp1 + IFNAMSIZ + SOCKADDR_SIZEOF (ifr_p1->ifr_addr);
		cp2 = cp2 + IFNAMSIZ + SOCKADDR_SIZEOF (ifr_p2->ifr_addr);

		if (T.debug > 4) {
			char astr[MAX_DNAME], bstr[MAX_DNAME];

			sprint_inet(&ifr_p1->ifr_addr, astr);
			sprint_inet(&ifr_p2->ifr_addr, bstr);
			
			syslog (LOG_DEBUG, "net_ifc_cmp(): if1 %s, if2 %s, \
af1 %d, af2 %d, addr1 %s, addr2 %s", ifr_p1->ifr_name, ifr_p2->ifr_name, \
ifr_p1->ifr_addr.sa_family, ifr_p1->ifr_addr.sa_family, astr, bstr);
		}

#ifdef USE_INET4
		if (ifr_p1->ifr_addr.sa_family != AF_INET &&
		    ifr_p2->ifr_addr.sa_family != AF_INET)
#endif
#ifdef USE_INET6
			if (ifr_p1->ifr_addr.sa_family != AF_INET6 &&
		    	    ifr_p2->ifr_addr.sa_family != AF_INET6)
#endif
				continue; /* skip */

		/* comparison only AF_INET and AF_INET6 */
		if (ifr_p1->ifr_addr.sa_family != ifr_p2->ifr_addr.sa_family)
			return 1;

		if (strcmp (ifr_p1->ifr_name, ifr_p2->ifr_name))
			return 1;

		if (memcmp (&(ifr_p1->ifr_addr), &(ifr_p2->ifr_addr),
			    SOCKADDR_SIZEOF (ifr_p1->ifr_addr)))
			return 1;
	}

	return 0; /* equal! */
}

static Nia *nia_alloc (struct sockaddr *sa_p, int usock, int tsock) {
	Nia *ni;

	ni = (Nia *) malloc (sizeof (Nia));
	if (!ni)
		return NULL;

	if (sa_p) {
		ni->sa_p =
			(struct sockaddr *) malloc (SOCKADDR_SIZEOF (*sa_p));
		if (!ni->sa_p) {
			free (ni);
			return NULL;
		}
		memcpy (ni->sa_p, sa_p, SOCKADDR_SIZEOF (*sa_p));
	} else
		ni->sa_p = NULL;

	ni->udp_sock = usock;
	ni->tcp_sock = tsock;
	return ni;
}

static void nia_list_destroy (G_List *list_head) {

	list_destroy(list_head, nia_free_closev);
	return;
}

void nia_free_closev (void *ni) {
	nia_free((Nia *)ni, 1);
}

void nia_free (Nia *ni, int close_flag) {
	const char *fn = "nia_free()";

	if (ni->sa_p)
		free (ni->sa_p);

	if (ni->udp_sock >= 0 && close_flag) {
		if (T.debug > 4)
			syslog (LOG_DEBUG, "%s: socket close(fd = %d)",
				fn, ni->udp_sock);
		close (ni->udp_sock);
	}

	if (ni->tcp_sock >= 0 && close_flag) {
		if (T.debug > 4)
			syslog (LOG_DEBUG, "%s: socket close(fd = %d)",
				fn, ni->tcp_sock);
		close (ni->tcp_sock);
	}

	free (ni);
}

Nia *nia_copy (Nia *ni) {
	if (!ni)
		return NULL;

	return nia_alloc (ni->sa_p, ni->udp_sock, ni->tcp_sock);
}

Nia *nia_find_by_sock (int sock_id) {
	G_List *gl;
	Nia *ni;

	for (gl = NI_head->next; gl->list_data; gl = gl->next) {
		ni = (Nia *) gl->list_data;

		if (!ni->sa_p)
			continue;

		if (ni->udp_sock == sock_id)
			return ni;
		if (ni->tcp_sock == sock_id)
			return ni;
	}

	return NULL;
}

void nia_fds_set (fd_set *fds, int *max_fd) {
	const char *fn = "nia_fds_set()";
	G_List *gl;
	Nia *ni;

	if (!NI_head)
		return;

	for (gl = NI_head->next; gl->list_data; gl = gl->next) {
		ni = (Nia *) gl->list_data;

		if (!ni->sa_p)
			continue;

		if (ni->udp_sock >= 0) {
			if (T.debug > 4)
				syslog (LOG_DEBUG, "%s: FD_SET(%d)", fn,
					ni->udp_sock);
			FD_SET (ni->udp_sock, fds);
			if (max_fd)
				*max_fd = MAXNUM (*max_fd, ni->udp_sock);
		}

		if (ni->tcp_sock >= 0) {
			if (T.debug > 4)
				syslog (LOG_DEBUG, "%s: FD_SET(%d)", fn,
					ni->tcp_sock);
			FD_SET (ni->tcp_sock, fds);
			if (max_fd)
				*max_fd = MAXNUM (*max_fd, ni->tcp_sock);
		}
	}

	return;
}

int nia_fds_isset (fd_set *fds, int *sock) {
	const char *fn = "nia_fds_isset()";
	G_List *gl;
	Nia *ni;

	*sock = -1;

	if (!NI_head)
		return -1;

	for (gl = NI_head->next; gl->list_data; gl = gl->next) {
		ni = (Nia *) gl->list_data;

		if (!ni->sa_p)
			continue;

		if (ni->udp_sock >= 0 && FD_ISSET (ni->udp_sock, fds)) {
			if (T.debug > 4)
				syslog (LOG_DEBUG, "%s: %d FD_ISSET",
					fn, ni->udp_sock);
			FD_CLR (ni->udp_sock, fds);
			*sock = ni->udp_sock;
			return 0; /* UDP */
		}

		if (ni->tcp_sock >= 0 && FD_ISSET (ni->tcp_sock, fds)) {
			if (T.debug > 4)
				syslog (LOG_DEBUG, "%s: %d FD_ISSET",
					fn, ni->tcp_sock);
			FD_CLR (ni->tcp_sock, fds);
			*sock = ni->tcp_sock;
			return 1; /* TCP */
		}
	}

	return -1;
}

static int nia_set_wildsock (G_List *list_head, int port) {
	Nia *ni;

#ifdef USE_INET4
	if (!T.use_mapped) {
		struct sockaddr_in sin;

		memset (&sin, 0, sizeof (sin));
#ifdef HAVE_SA_LEN_FIELD
		sin.sin_len = sizeof (struct sockaddr_in);
#endif
		sin.sin_family = AF_INET;
		sin.sin_port = htons(port);
		sin.sin_addr.s_addr = INADDR_ANY;

		ni = nia_alloc((struct sockaddr *)&sin, -1, -1);
		if (!ni)
			return -1;

		if (list_add (list_head, ni) < 0) {
			nia_free (ni, 0);
			return -1;
		}
	
		NI_wildcard = list_head->next;
	}
#endif

#ifdef USE_INET6
	if (1) {
		struct sockaddr_in6 sin6;

		memset (&sin6, 0, sizeof (sin6));
#ifdef HAVE_SA_LEN_FIELD
		sin6.sin6_len = sizeof (struct sockaddr_in6);
#endif
		sin6.sin6_family = AF_INET6;
		sin6.sin6_port = htons(port);
		sin6.sin6_addr = in6addr_any;

		ni = nia_alloc((struct sockaddr *)&sin6, -1, -1);
		if (!ni)
			return -1;

		if (list_add (list_head, ni) < 0) {
			nia_free (ni, 0);
			return -1;
		}

		NI_wildcard6 = list_head->next;
	}
#endif

	/* SUCCESS */
	return 0;
}

int net_init_socketlist (int port) {

	NI_head = list_init();
	if (!NI_head)
		return -1;

	if (T.wildcard)
		nia_set_wildsock (NI_head, T.port);
	else {
		/* wildcard for sending requests only */
		nia_set_wildsock (NI_head, 0);
		if (net_get_ifaddrs (NI_head, port) != 1)
			return -1;
	}

	return 0;
}

int net_reinit_socketlist (int port, int force_update) {
	G_List *newlist;

	newlist = list_init();
	if (!newlist)
		return -1;

	if (T.wildcard) {
		if (force_update) {
			nia_list_destroy (NI_head);
			nia_set_wildsock (newlist, T.port);
			NI_head = newlist;
			net_bind_socketlist();
			return 1;
		} else {
			nia_list_destroy (newlist);
			return 0;
		}
	}

	switch (net_get_ifaddrs (newlist, port)) {
	case 0: /* no change */
		if (!force_update) {
			nia_list_destroy (newlist);
			return 0;
		}
		/* else fall through to next case */
	case 1: /* changed list */
		nia_list_destroy (NI_head);
		/* wildcard for sending requests only */
		nia_set_wildsock (newlist, 0);
		NI_head = newlist;
		net_bind_socketlist();
		return 1;
	default: /* error */
		nia_list_destroy (newlist);
		return -1;
	}
}

/* returns number of sockets opened successfully */
int net_bind_socketlist (void) {
	G_List *gl;
	Nia *ni;
	int s;

	if (!NI_head)
		return -1;

	s = 0; /* no socket opened yet */
	for (gl = NI_head->next; gl->list_data; gl = gl->next) {
		ni = (Nia *) gl->list_data;

		if (!ni->sa_p) 
			continue;

		if (net_mesg_socket (ni) >= 0)
			s++;
		if (net_stream_socket (ni) >= 0)
			s++;
	}

	return s;
}

int net_delete_socketlist (void) {

	/*  throw out old list if any */
	nia_list_destroy (NI_head);
	NI_head = NI_wildcard = NI_wildcard6 = NULL;

	return 0;
}

static int net_get_ifaddrs (G_List *list_head, int port) {
	const char *fn = "net_get_ifaddrs()";
	static struct ifconf ifc_old;
	struct ifconf ifc;
	int dummy_sock = -1, status;
	u_char buf[8192];
	u_char *cp;

	memset (buf, 0, sizeof (buf));
	ifc.ifc_len = sizeof (buf);
	ifc.ifc_buf = buf;

#ifdef USE_INET6
	if (dummy_sock < 0)
		dummy_sock = socket (AF_INET6, SOCK_STREAM, 0);
#endif
#ifdef USE_INET4
	if (dummy_sock < 0)
		dummy_sock = socket (AF_INET, SOCK_STREAM, 0);
#endif
	if (dummy_sock < 0)
		return -1;

	/*
	 * check whether list of interface addresses has changed
	 */
	if (ioctl (dummy_sock, SIOCGIFCONF, (char *) &ifc) < 0) {
		close(dummy_sock);
		syslog (LOG_ERR, "%s: get iflist error: %m", fn);
		return -1;
	}
	close(dummy_sock);

	status = 1;
	if (ifc_old.ifc_buf) {
		status = net_ifc_cmp (&ifc_old, &ifc);
		syslog (LOG_DEBUG, "%s: checked interface data %d", fn, status);
	}

	/*
	 * Interface address list changed or we got called for
	 * for the first time
	 */

	cp = buf;
	while (*cp) {
		int valid_address = 0;
		char astr[MAX_DNAME];
		struct ifreq *ifr_p;

		ifr_p = (struct ifreq *) cp;
		sprint_inet(&ifr_p->ifr_addr, astr);

#ifdef USE_INET6
		if (T.ip6 && ifr_p->ifr_addr.sa_family == AF_INET6)
			if (nia_is_in_totd_iflist(ifr_p->ifr_name))
				valid_address = 1;
#endif
#ifdef USE_INET4
		if (T.ip4 && ifr_p->ifr_addr.sa_family == AF_INET)
			if (T.ip4 && nia_is_in_totd_iflist(ifr_p->ifr_name))
				valid_address = 1;
#endif

		if (valid_address) {
			Nia *ni;

			ni = nia_alloc (&ifr_p->ifr_addr, -1, -1);
			if (!ni)
				return -1;

#ifdef USE_INET6
			if (T.ip6 && ni->sa_p->sa_family == AF_INET6) {
				struct sockaddr_in6 *sa_p;

				sa_p = (struct sockaddr_in6 *) ni->sa_p;
				sa_p->sin6_port = htons (port);
			}
#endif

#ifdef USE_INET4
			if (T.ip4 && ni->sa_p->sa_family == AF_INET) {
				struct sockaddr_in *sa_p;

				sa_p = (struct sockaddr_in *) ni->sa_p;
				sa_p->sin_port = htons (port);
			}
#endif

			if (*astr && T.debug > 3)
				syslog (LOG_DEBUG, "Found address %s on if %s",
					astr, ifr_p->ifr_name);

			/* ok! add to the list */
			if (list_add (list_head, ni) < 0)
                        	return -1;

		} else if (*astr && T.debug > 3)
			syslog (LOG_DEBUG, "Ignoring address %s on if %s",
				astr, ifr_p->ifr_name);

		/* point to next address entry in list */
		cp = cp + IFNAMSIZ + SOCKADDR_SIZEOF (ifr_p->ifr_addr);
	}

	/*
	 * preserve old buffer
	 */

	if (ifc_old.ifc_buf)
		free (ifc_old.ifc_buf);

	ifc_old.ifc_buf = malloc (ifc.ifc_len);
	if (ifc_old.ifc_buf) {
		memcpy (ifc_old.ifc_buf, buf, ifc.ifc_len);
		ifc_old.ifc_len = ifc.ifc_len;
	}

	return status;
}

int net_mesg_send (Nia *ni, u_char *mesg, int mesg_len,
		   struct sockaddr *sa_p) {
	const char *fn = "net_mesg_send()";

	if (!ni) {
#ifdef USE_INET4
		if (sa_p->sa_family == AF_INET)
			ni = (Nia *) NI_wildcard->list_data;
#endif
#ifdef USE_INET6
		if (sa_p->sa_family == AF_INET6)
			ni = (Nia *) NI_wildcard6->list_data;
#endif
	}

	if (!ni || ni->udp_sock < 0) {
		syslog (LOG_WARNING, "%s: no socket to send message over", fn);
		return -1;
	}

#ifdef USE_INET4
	if (ni->sa_p->sa_family == AF_INET && sa_p->sa_family == AF_INET)
		return sendto (ni->udp_sock, mesg, mesg_len, 0, sa_p,
			       SOCKADDR_SIZEOF (*sa_p));
#endif

#ifdef USE_INET6
	if (ni->sa_p->sa_family == AF_INET6 && sa_p->sa_family == AF_INET6)
		return sendto (ni->udp_sock, mesg, mesg_len, 0, sa_p,
			       SOCKADDR_SIZEOF (*sa_p));
#endif

	return -1;
}

int net_mesg_socket (Nia *ni) {
	const char *fn = "net_mesg_socket()";
	char astr[MAX_DNAME];
	const int on = 1;
	int sock;

	ni->udp_sock = -1;

	sock = socket (ni->sa_p->sa_family, SOCK_DGRAM, 0);
	if (sock < 0) {
		syslog (LOG_ERR, "%s: socket open failed: %m", fn);
		return -1;
	}
	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: socket open(fd = %d)", fn, sock);

	if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,
			(char *) &on, sizeof (on)))
		syslog (LOG_WARNING, "setsockopt: %m");


	if (bind (sock, ni->sa_p, SOCKADDR_SIZEOF (*ni->sa_p)) < 0) {
		syslog (LOG_ERR, "Can not bind datagram socket: %m");
		close (sock);
		return -1;
	}

	/* if no port set, then it is for outgoing messages only */
	if (((struct sockaddr_in *)ni->sa_p)->sin_port) {
		sprint_inet(ni->sa_p, astr);
		syslog (LOG_NOTICE, "Listening on %s for UDP", astr);
	}

#ifdef USE_INET6
#ifdef IPV6_USE_MIN_MTU
	if (ni->sa_p->sa_family == AF_INET6) {
		const int on = 1;

		/* ignore error */
		(void)setsockopt(sock, IPPROTO_IPV6,
			IPV6_USE_MIN_MTU, &on, sizeof(on));
	}
#endif
#endif

	ni->udp_sock = sock;
	return sock;
}

int net_stream_socket (Nia *ni) {
	const char *fn = "net_stream_socket()";
	char astr[MAX_DNAME];
	const int on = 1;
	int sock;

	ni->tcp_sock = -1;

	/* if no port set, do nothing for TCP */
	if (!((struct sockaddr_in *)ni->sa_p)->sin_port)
		return -1;

	sock = socket (ni->sa_p->sa_family, SOCK_STREAM, 0);
	if (sock < 0) {
		syslog (LOG_ERR, "%s: socket open failed: %m", fn);
		return -1;
	}
	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: socket open(fd = %d)", fn, sock);

	if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,
			(char *) &on, sizeof (on)))
		syslog (LOG_WARNING, "setsockopt: %m");

	if (bind (sock, ni->sa_p, SOCKADDR_SIZEOF (*ni->sa_p)) != 0) {
		syslog (LOG_ERR, "Can't bind TCP socket: %m");
		close (sock);
		return -1;
	}

	if (ioctl (sock, FIONBIO, (char *) &on) < 0) {
		syslog (LOG_ERR, "Can't ioctl on service socket: %m");
		return -1;
	}

	if (listen (sock, 5) != 0) {
		syslog (LOG_ERR, "Listen failed: %m");
		close (sock);
		return -1;
	}

	sprint_inet(ni->sa_p, astr);
	syslog (LOG_NOTICE, "Listening on %s for TCP", astr);

	ni->tcp_sock = sock;
	return sock;
}


syntax highlighted by Code2HTML, v. 0.9.1