/*
 * linc-protocols.c: This file is part of the linc library.
 *
 * Authors:
 *    Elliot Lee     (sopwith@redhat.com)
 *    Michael Meeks  (michael@ximian.com)
 *    Mark McLouglin (mark@skynet.ie) & others
 *
 * Copyright 2001, Red Hat, Inc., Ximian, Inc.,
 *                 Sun Microsystems, Inc.
 */
#include <config.h>
#include "linc-compat.h"
#include <dirent.h>
#include <linc/linc-protocol.h>
#include <linc/linc-connection.h>

#include "linc-private.h"
#include "linc-debug.h"

#undef LOCAL_DEBUG

static char *linc_tmpdir = NULL;

/*
 * make_local_tmpdir:
 * @dirname: directory name.
 *
 * Create a directory with the name in @dirname. Also, clear the
 * access and modification times of @dirname.
 *
 * If the directory already exists and is not owned by the current 
 * user, or is not solely readable by the current user, then linc
 * will error out.
 */
static void
make_local_tmpdir (const char *dirname)
{
	struct stat statbuf;
		
	if (mkdir (dirname, 0700) != 0) {
		int e = errno;
			
		switch (e) {
		case 0:
		case EEXIST:
			if (stat (dirname, &statbuf) != 0)
				g_error ("Can not stat %s\n", dirname);

			if (statbuf.st_uid != getuid ())
				g_error ("Owner of %s is not the current user\n", dirname);

			if ((statbuf.st_mode & (S_IRWXG|S_IRWXO)) ||
			    !S_ISDIR (statbuf.st_mode))
				g_error ("Wrong permissions for %s\n", dirname);

			break;
				
		default:
			g_error("Unknown error on directory creation of %s (%s)\n",
				dirname, g_strerror (e));
		}
	}

	{ /* Hide some information ( apparently ) */
		struct utimbuf utb;
		memset (&utb, 0, sizeof (utb));
		utime (dirname, &utb);
	}
}

/**
 * linc_set_tmpdir:
 * @dir: directory name.
 *
 * Set the temporary directory used by linc to @dir. 
 *
 * This directory is used for the creation of UNIX sockets.
 * @dir must have the correct permissions, 0700, user owned
 * otherwise this method will g_error.
 **/
void
linc_set_tmpdir (const char *dir)
{
	g_free (linc_tmpdir);
	linc_tmpdir = g_strdup (dir);

	make_local_tmpdir (linc_tmpdir);
}

/**
 * linc_get_tmpdir:
 * @void: 
 * 
 * Fetches the directory name used by linc to whack
 * Unix Domain sockets into.
 * 
 * Return value: the g_allocated socket name.
 **/
char *
linc_get_tmpdir (void)
{
	return g_strdup (linc_tmpdir ? linc_tmpdir : "");
}

#ifdef HAVE_SOCKADDR_SA_LEN
#define LINC_SET_SOCKADDR_LEN(saddr, len)                     \
		((struct sockaddr *)(saddr))->sa_len = (len)
#else 
#define LINC_SET_SOCKADDR_LEN(saddr, len)
#endif

#if defined(AF_INET6) && defined(RES_USE_INET6)
#define LINC_RESOLV_SET_IPV6     _res.options |= RES_USE_INET6
#define LINC_RESOLV_UNSET_IPV6   _res.options &= ~RES_USE_INET6
#else
#define LINC_RESOLV_SET_IPV6
#define LINC_RESOLV_UNSET_IPV6
#endif



#if defined(AF_INET) || defined(AF_INET6) || defined (AF_UNIX)
const char *
linc_get_local_hostname (void)
{
	static char local_host[NI_MAXHOST] = { 0 };

	if (local_host [0])
		return local_host;

	if (gethostname (local_host, NI_MAXHOST) == -1)
		return NULL;

	return local_host;
}

/*
 * True if succeeded in mapping, else false.
 */
static gboolean
ipv4_addr_from_addr (struct in_addr *dest_addr,
		     guint8         *src_addr,
		     int             src_length)
{
	if (src_length == 4)
		memcpy (dest_addr, src_addr, 4);

	else if (src_length == 16) {
		int i;

#ifdef LOCAL_DEBUG
		g_warning ("Doing conversion ...");
#endif

		/* An ipv6 address, might be an IPv4 mapped though */
		for (i = 0; i < 10; i++)
			if (src_addr [i] != 0)
				return FALSE;

		if (src_addr [10] != 0xff ||
		    src_addr [11] != 0xff)
			return FALSE;

		memcpy (dest_addr, &src_addr[12], 4);
	} else
		return FALSE;

	return TRUE;
}

static gboolean
linc_protocol_is_local_ipv46 (const LINCProtocolInfo *proto,
			      const struct sockaddr   *saddr,
			      LincSockLen              saddr_len)
{
	int i;
	static int warned = 0;
	static struct hostent *local_hostent;

	g_assert (saddr->sa_family == proto->family);

	if (!local_hostent) {
		LINC_RESOLV_SET_IPV6;
		local_hostent = gethostbyname (linc_get_local_hostname ());
	}

	if (!local_hostent) {
		if (!warned++)
			g_warning ("can't gethostbyname on '%s'",
				   linc_get_local_hostname ());
		return FALSE;
	}

	if (!local_hostent->h_addr_list)
		g_error ("No address for local host");

	if (proto->family != AF_INET) {
#ifdef AF_INET6
		if (proto->family == AF_INET6 &&
		    local_hostent->h_addrtype != AF_INET6)
			return FALSE; /* can connect via IPv4 */

		if (proto->family != AF_INET6)
			return FALSE;
#else
		return FALSE;
#endif
	}

	for (i = 0; local_hostent->h_addr_list [i]; i++) {

		if (proto->family == AF_INET) {
			struct in_addr ipv4_addr;
			
			if (!ipv4_addr_from_addr (&ipv4_addr,
						  local_hostent->h_addr_list [i],
						  local_hostent->h_length))
				continue;

			if (!memcmp (&ipv4_addr, 
				     &((struct sockaddr_in *)saddr)->sin_addr.s_addr, 4)) {
#ifdef LOCAL_DEBUG
				g_warning ("local ipv4 address");
#endif
				return TRUE;
			}

		}
#ifdef AF_INET6
		else if (!memcmp (local_hostent->h_addr_list [i],
				  &((struct sockaddr_in6 *)saddr)->sin6_addr.s6_addr,
				  local_hostent->h_length)) {
#ifdef LOCAL_DEBUG
			g_warning ("local ipv6 address");
#endif
			return TRUE;
		}
#endif
	}

#ifdef LOCAL_DEBUG
	g_warning ("No match over all");
#endif

	return FALSE;
}

#endif

/*
 * linc_protocol_get_sockaddr_ipv4:
 * @proto: the #LINCProtocolInfo structure for the IPv4 protocol.
 * @hostname: the hostname.
 * @portnum: the port number.
 * @saddr_len: location in which to store the returned structure's length.
 *
 * Allocates and fills a #sockaddr_in with with the IPv4 address 
 * information.
 *
 * Return Value: a pointer to a valid #sockaddr_in structure if the call 
 *               succeeds, NULL otherwise.
 */
#ifdef AF_INET
static struct sockaddr *
linc_protocol_get_sockaddr_ipv4 (const LINCProtocolInfo *proto,
				 const char             *hostname,
				 const char             *portnum,
				 LincSockLen            *saddr_len)
{
	struct sockaddr_in *saddr;
	struct hostent     *host;

	g_assert (proto->family == AF_INET);
	g_assert (hostname);

	if (!portnum)
		portnum = "0";

	saddr = g_new0 (struct sockaddr_in, 1);

	*saddr_len = sizeof (struct sockaddr_in);

	LINC_SET_SOCKADDR_LEN (saddr, sizeof (struct sockaddr_in));

	saddr->sin_family = AF_INET;
	saddr->sin_port   = htons (atoi (portnum));

	if ((saddr->sin_addr.s_addr = inet_addr (hostname)) == INADDR_NONE) {
	        int i;

		LINC_RESOLV_UNSET_IPV6;
		if (!(_res.options & RES_INIT))
			res_init();
		
		host = gethostbyname (hostname);
		if (!host) {
		  g_free (saddr);
		  return NULL;
		}

		for(i = 0; host->h_addr_list[i]; i++)
		    if(ipv4_addr_from_addr (&saddr->sin_addr,
					    (guint8 *)host->h_addr_list [i],
					    host->h_length))
		      break;

		if(!host->h_addr_list[i]) {
		  g_free (saddr);
		  return NULL;
		}
	}

	return (struct sockaddr *) saddr;
}
#endif /* AF_INET */

/*
 * linc_protocol_get_sockaddr_ipv6:
 * @proto: the #LINCProtocolInfo structure for the IPv6 protocol.
 * @hostname: the hostname.
 * @portnum: the port number
 * @saddr_len: location in which to store the returned structure's length.
 *
 * Allocates and fills a #sockaddr_in6 with with the IPv6 address 
 * information.
 *
 * NOTE: This function is untested.
 *
 * Return Value: a pointer to a valid #sockaddr_in6 structure if the call 
 *               succeeds, NULL otherwise.
 */
#ifdef AF_INET6
static struct sockaddr *
linc_protocol_get_sockaddr_ipv6 (const LINCProtocolInfo *proto,
				 const char             *hostname,
				 const char             *portnum,
				 LincSockLen            *saddr_len)
{
	struct sockaddr_in6 *saddr;
	struct hostent      *host;

	g_assert (proto->family == AF_INET6);
	g_assert (hostname);

	if (!portnum)
		portnum = "0";

	saddr = g_new0 (struct sockaddr_in6, 1);

	*saddr_len = sizeof (struct sockaddr_in6);

	LINC_SET_SOCKADDR_LEN (saddr, sizeof (struct sockaddr_in6));

	saddr->sin6_family = AF_INET6;
	saddr->sin6_port = htons (atoi (portnum));
#ifdef HAVE_INET_PTON
	if (inet_pton (AF_INET6, hostname, &saddr->sin6_addr) > 0)
		return (struct sockaddr *)saddr;
#endif

	if (!(_res.options & RES_INIT))
		res_init();

	LINC_RESOLV_SET_IPV6;
	host = gethostbyname (hostname);
	if (!host || host->h_addrtype != AF_INET6) {
		g_free (saddr);
		return NULL;
	}

	memcpy (&saddr->sin6_addr, host->h_addr_list[0], sizeof (struct in6_addr));

	return (struct sockaddr *)saddr;
}
#endif /* AF_INET6 */

#ifdef AF_UNIX
/*
 * linc_protocol_get_sockaddr_unix:
 * @proto: the #LINCProtocolInfo structure for the UNIX sockets protocol.
 * @dummy: not used.
 * @path: the path name of the UNIX socket.
 * @saddr_len: location in which to store the returned structure's length.
 *
 * Allocates and fills a #sockaddr_un with with the UNIX socket address 
 * information.
 *
 * If @path is NULL, a new, unique path name will be generated.
 *
 * Return Value: a pointer to a valid #sockaddr_un structure if the call 
 *               succeeds, NULL otherwise.
 */
static struct sockaddr *
linc_protocol_get_sockaddr_unix (const LINCProtocolInfo *proto,
				 const char             *dummy,
				 const char             *path,
				 LincSockLen            *saddr_len)
{
	struct sockaddr_un *saddr;
	int                 pathlen;
	char                buf[64], *actual_path;

	g_assert (proto->family == AF_UNIX);

	if (!path) {
		struct timeval t;
		static guint pid = 0, idx = 0;

		if (!pid)
			pid = getpid ();

		gettimeofday (&t, NULL);
		g_snprintf (buf, sizeof (buf),
			    "%s/linc-%x-%x-%x%x",
			    linc_tmpdir ? linc_tmpdir : "",
			    pid, idx,
			    (guint) (rand() ^ t.tv_sec),
			    (guint) (idx ^ t.tv_usec));
		idx++;
#ifdef CONNECTION_DEBUG
		if (g_file_test (buf, G_FILE_TEST_EXISTS))
			g_warning ("'%s' already exists !", buf);
#endif
		actual_path = buf;
	} else 
		actual_path = (char *)path;

	pathlen = strlen (actual_path) + 1;

	if (pathlen > sizeof (saddr->sun_path))
		return NULL;

	saddr = g_new0 (struct sockaddr_un, 1);

	*saddr_len = sizeof (struct sockaddr_un) - sizeof (saddr->sun_path) + pathlen;

	LINC_SET_SOCKADDR_LEN (saddr, *saddr_len);

	saddr->sun_family =  AF_UNIX;
	strncpy (saddr->sun_path, actual_path, sizeof (saddr->sun_path) - 1);
	saddr->sun_path[sizeof (saddr->sun_path) - 1] = '\0';

	return (struct sockaddr *)saddr;
}
#endif /* AF_UNIX */

/*
 * linc_protocol_get_sockaddr_irda:
 * @proto:
 * @hostname:
 * @service:
 * @saddr_len:
 *
 * NOTE: This function is not implemented. We need to hack something
 *       together from irda_getaddrinfo.
 *
 * Return Value:
 */
#ifdef AF_IRDA
static struct sockaddr *
linc_protocol_get_sockaddr_irda (const LINCProtocolInfo *proto,
				 const char             *hostname,
				 const char             *service,
				 LincSockLen            *saddr_len)
{
	g_assert (proto->family == AF_IRDA);

	return NULL;
}
#endif /* AF_IRDA */

/*
 * linc_protocol_get_sockaddr:
 * @proto: a #LINCProtocolInfo structure.
 * @hostname: protocol dependant host information.
 * @service: protocol dependant service information.
 * @saddr_len: location in which to store the returned structure's length.
 *
 * Allocates, fills in and returns the #sockaddr structure appropriate
 * for the supplied protocol, @proto.
 *
 * Return Value: a pointer to a valid #sockaddr structure if the call 
 *               succeeds, NULL otherwise.
 */
struct sockaddr *
linc_protocol_get_sockaddr (const LINCProtocolInfo *proto,
			    const char             *hostname,
			    const char             *service,
			    LincSockLen            *saddr_len)		   
{
	if (proto && proto->get_sockaddr)
		return proto->get_sockaddr (proto, hostname, service, saddr_len);

	return NULL;
}

/*
 * linc_protocol_get_sockinfo_ipv46:
 * @host: a #hostent structure describing the host.
 * @port: the portnumber.
 * @hostname: pointer by which the hostname string is returned.
 * @portnum: pointer by which the port number string is returned.
 *
 * Generates two strings, returned through @hostname and @portnum, corresponding
 * to @host and @port. On return @hostname should contain the canonical hostname 
 * of the host and @portnum should contain the port number string.
 *
 * If @host is NULL, the local host name is used.
 *
 * Note: both @hostname and @service are allocated on the heap and should be
 *       freed using g_free().
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
static gboolean
linc_protocol_get_sockinfo_ipv46 (struct hostent  *host,
				  guint            port,
				  gchar          **hostname,
				  char           **portnum)
{
	if (!host) {
		const char *local_host;

		if (!(local_host = linc_get_local_hostname ()))
			return FALSE;

		LINC_RESOLV_SET_IPV6;
		host = gethostbyname (local_host);
	}

	if (!host)
		return FALSE;

	if (hostname)
		*hostname = g_strdup (host->h_name);

	if (portnum) {
		gchar tmpport[NI_MAXSERV];

		g_snprintf (tmpport, sizeof (tmpport), "%d", ntohs (port));

		*portnum = g_strdup (tmpport);
	}

	return TRUE;
}

/*
 * linc_protocol_get_sockinfo_ipv4:
 * @proto: the #LINCProtocolInfo structure for the IPv4 protocol.
 * @sockaddr: a #sockaddr_in structure desribing the socket.
 * @hostname: pointer by which the hostname string is returned.
 * @portnum: pointer by which the port number string is returned.
 *
 * Generates two strings, returned through @hostname and @portnum, describing
 * the socket address, @sockaddr. On return @hostname should contain the 
 * canonical hostname of the host described in @sockaddr and @portnum should
 * contain the port number of the socket described in @sockaddr.
 *
 * Note: both @hostname and @service are allocated on the heap and should be
 *       freed using g_free().
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
#ifdef AF_INET
static gboolean
linc_protocol_get_sockinfo_ipv4 (const LINCProtocolInfo  *proto,
				 const struct sockaddr   *saddr,
				 gchar                  **hostname,
				 gchar                  **portnum)
{
	struct sockaddr_in *sa_in = (struct sockaddr_in  *)saddr;
	struct hostent     *host = NULL;

	g_assert (proto && saddr && saddr->sa_family == AF_INET);

	if (sa_in->sin_addr.s_addr != INADDR_ANY) {
		host = gethostbyaddr ((char *)&sa_in->sin_addr, 
                                      sizeof (struct in_addr), AF_INET);
		if (!host)
			return FALSE;
	}

	return linc_protocol_get_sockinfo_ipv46 (host, sa_in->sin_port, 
						 hostname, portnum);
}
#endif /* AF_INET */

/*
 * linc_protocol_get_sockinfo_ipv6:
 * @proto: the #LINCProtocolInfo structure for the IPv6 protocol.
 * @sockaddr: a #sockaddr_in structure desribing the socket.
 * @hostname: pointer by which the hostname string is returned.
 * @portnum: pointer by which the port number string is returned.
 *
 * Generates two strings, returned through @hostname and @portnum, describing
 * the socket address, @sockaddr. On return @hostname should contain the 
 * canonical hostname of the host described in @sockaddr and @portnum should
 * contain the port number of the socket described in @sockaddr.
 *
 * Note: both @hostname and @service are allocated on the heap and should be
 *       freed using g_free().
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
#ifdef AF_INET6

/* FIXME: is IN6ADDR_ANY_INIT exported on Mac OS X ? */
/* on Mac OS X 10.1 inaddr6_any isn't exported by libc */
#ifndef in6addr_any
	static const struct in6_addr in6addr_any = { { { 0 } } };
#endif

static gboolean
linc_protocol_get_sockinfo_ipv6 (const LINCProtocolInfo  *proto,
				 const struct sockaddr   *saddr,
				 gchar                  **hostname,
				 gchar                  **portnum)
{
	struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)saddr;
	struct hostent      *host = NULL;

	g_assert (proto && saddr && saddr->sa_family == AF_INET6);

	if (!memcmp (&sa_in6->sin6_addr, &in6addr_any, sizeof (struct in6_addr))) {

		host = gethostbyaddr ((char *)&sa_in6->sin6_addr, 
				      sizeof (struct in6_addr), AF_INET6);
		if (!host)
			return FALSE;
	}

	return linc_protocol_get_sockinfo_ipv46 (host, sa_in6->sin6_port, 
						 hostname, portnum);
}

#endif /* AF_INET6 */

/*
 * linc_protocol_get_sockinfo_unix:
 * @proto: a #LINCProtocolInfo structure.
 * @sockaddr: a #sockaddr_un structure desribing the socket.
 * @hostname: pointer by which the hostname string is returned.
 * @service: pointer by which the sockets pathname string is returned.
 *
 * Generates two strings, returned through @hostname and @sock_path, describing
 * the socket address, @sockaddr. On return @hostname should contain the 
 * canonical hostname of the local host and @sock_path should contain the 
 * path name of the unix socket described in @sockaddr.
 *
 * Note: both @hostname and @sock_path are allocated on the heap and should 
 *       be freed using g_free().
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
#ifdef AF_UNIX
static gboolean
linc_protocol_get_sockinfo_unix (const LINCProtocolInfo  *proto,
				 const struct sockaddr   *saddr,
				 gchar                  **hostname,
				 gchar                  **sock_path)
{
	struct sockaddr_un *sa_un = (struct sockaddr_un *)saddr;

	g_assert (proto && saddr && saddr->sa_family == AF_UNIX);

	if (hostname) {
		const char *local_host;

		if (!(local_host = linc_get_local_hostname ()))
			return FALSE;

		*hostname = g_strdup (local_host);
	}

	if (sock_path)
		*sock_path = g_strdup (sa_un->sun_path);

	return TRUE;
}
#endif /* AF_UNIX */

/*
 * linc_protocol_get_sockinfo_irda:
 * @proto: a #LINCProtocolInfo structure.
 * @sockaddr: a #sockaddr_irda structure desribing the socket.
 * @hostname: 
 * @service: 
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
#ifdef AF_IRDA
static gboolean
linc_protocol_get_sockinfo_irda (const LINCProtocolInfo  *proto,
				 const struct sockaddr   *saddr,
				 gchar                  **hostname,
				 gchar                  **portnum)
{
	g_assert (proto && saddr && saddr->sa_family == AF_IRDA);

	return FALSE;
}
#endif /* AF_IRDA */

/*
 * linc_protocol_get_sockinfo:
 * @proto: a #LINCProtocolInfo structure.
 * @sockaddr: a #sockadrr structure desribing the socket.
 * @hostname: pointer by which the hostname string is returned.
 * @service: pointer by which the service string is returned.
 *
 * Generates two strings, returned through @hostname and @service, describing
 * the socket address, @sockaddr. On return @hostname should contain the 
 * canonical hostname of the host described in @sockaddr and @service should
 * contain the service descriptor(e.g. port number) of the socket described in 
 * @sockaddr
 *
 * Note: both @hostname and @service are allocated on the heap and should be
 *       freed using g_free().
 *
 * Return Value: #TRUE if the function succeeds, #FALSE otherwise.
 */
gboolean
linc_protocol_get_sockinfo (const LINCProtocolInfo  *proto,
			    const struct sockaddr   *saddr,
			    gchar                  **hostname,
			    gchar                  **service)
{
	if (proto && proto->get_sockinfo)
		return proto->get_sockinfo (proto, saddr, hostname, service);

	return FALSE;
}

/**
 * linc_protocol_is_local:
 * @proto: the protocol
 * @saddr: the socket address of a connecting client.
 * 
 *   This method determines if the client is from the same
 * machine or not - per protocol.
 * 
 * Return value: TRUE if the connection is local, else FALSE
 **/
gboolean
linc_protocol_is_local (const LINCProtocolInfo  *proto,
			const struct sockaddr   *saddr,
			LincSockLen              saddr_len)
{
	if (proto && proto->is_local)
		return proto->is_local (proto, saddr, saddr_len);

	return FALSE;
}

/*
 * af_unix_destroy:
 * @fd: file descriptor of the socket.
 * @dummy: not used.
 * @pathname: path name of the UNIX socket
 *
 * Removes the UNIX socket file.
 */
#ifdef AF_UNIX
static void
linc_protocol_unix_destroy (int         fd,
			    const char *dummy,
			    const char *pathname)
{
	unlink (pathname);
}

static gboolean
linc_protocol_unix_is_local (const LINCProtocolInfo *proto,
			     const struct sockaddr   *saddr,
			     LincSockLen              saddr_len)
{
	return TRUE;
}
#endif /* AF_UNIX */

/*
 * linc_protocol_tcp_setup:
 * @fd: file descriptor of the socket.
 * @cnx_flags: a #LINCConnectionOptions value.
 *
 * Sets the TCP_NODELAY option on the TCP socket.
 *
 * Note: this is not applied to SSL TCP sockets.
 */
#if defined(AF_INET) || defined(AF_INET6)
static void
linc_protocol_tcp_setup (int                   fd,
			 LINCConnectionOptions cnx_flags)
{
#ifdef TCP_NODELAY
	if (!(cnx_flags & LINC_CONNECTION_SSL)) {
		struct protoent *proto;
		int              on = 1;

		proto = getprotobyname ("tcp");
		if (!proto)
			return;

		setsockopt (fd, proto->p_proto, TCP_NODELAY, 
		            &on, sizeof (on));
	}
#endif
}
#endif /* defined(AF_INET) || defined(AF_INET6) */

static LINCProtocolInfo static_linc_protocols[] = {
#if defined(AF_INET)
	{
	"IPv4", 			/* name */
	AF_INET, 			/* family */
	sizeof (struct sockaddr_in), 	/* addr_len */
	IPPROTO_TCP, 			/* stream_proto_num */
	0, 				/* flags */
	linc_protocol_tcp_setup, 	/* setup */
	NULL, 				/* destroy */
	linc_protocol_get_sockaddr_ipv4,/* get_sockaddr */
	linc_protocol_get_sockinfo_ipv4,/* get_sockinfo */
	linc_protocol_is_local_ipv46    /* is_local */
	},
#endif
#if defined(AF_INET6)
	{ 
	"IPv6", 			/* name */
	AF_INET6, 			/* family */
	sizeof (struct sockaddr_in6), 	/* addr_len */
	IPPROTO_TCP, 			/* stream_proto_num */
	0, 				/* flags */
	linc_protocol_tcp_setup, 	/* setup */
	NULL, 				/* destroy */
	linc_protocol_get_sockaddr_ipv6,/* get_sockaddr */
	linc_protocol_get_sockinfo_ipv6,/* get_sockinfo */
	linc_protocol_is_local_ipv46    /* is_local */
	},
#endif
#ifdef AF_UNIX
	{
	"UNIX", 					/* name */
	AF_UNIX, 					/* family */
	sizeof (struct sockaddr_un), 			/* addr_len */
	0, 						/* stream_proto_num */
	LINC_PROTOCOL_SECURE|LINC_PROTOCOL_NEEDS_BIND, 	/* flags */
	NULL,  						/* setup */
	linc_protocol_unix_destroy,  			/* destroy */
	linc_protocol_get_sockaddr_unix, 		/* get_sockaddr */
	linc_protocol_get_sockinfo_unix, 		/* get_sockinfo */
	linc_protocol_unix_is_local                     /* is_local */
	},
#endif
#ifdef AF_IRDA
	{
	"IrDA", 				/* name */
	AF_IRDA, 				/* family */
	sizeof (struct sockaddr_irda), 		/* addr_len */
	0, 					/* stream_proto_num */
	LINC_PROTOCOL_NEEDS_BIND, 		/* flags */
	NULL, 					/* setup */
	NULL, 					/* destroy */
	linc_protocol_get_sockaddr_irda, 	/* get_sockaddr */
	linc_protocol_get_sockinfo_irda, 	/* get_sockinfo */
	NULL                                    /* is_local */
	},
#endif
	{ NULL /* name */ }
};

/* 
 * Routines for AF_IRDA 
 * FIXME: These are left here only as a reference for implementing
 *        linc_protocol_get_sockinfo_irda and 
 *        linc_protocol_get_sockaddr_irda
 *       
 */
#if 0
#ifdef AF_IRDA

#define MAX_IRDA_DEVICES 10
#define IRDA_NICKNAME_MAX (sizeof (((struct irda_device_info *)NULL)->info) + 1)

static int
irda_find_device (guint32  *addr,
		  char     *name,
		  gboolean  name_to_addr)
{
	struct irda_device_list *list;
	unsigned char           *buf;
	int                      len, i, retval, fd;

	retval = -1;

	fd = socket (AF_IRDA, SOCK_STREAM, 0);
	if (fd < 0)
		return -1;

	len = sizeof (struct irda_device_list) +
	      sizeof (struct irda_device_info) * MAX_IRDA_DEVICES;

	buf = g_alloca (len);
	list = (struct irda_device_list *)buf;
        
	if (getsockopt (fd, SOL_IRLMP, IRLMP_ENUMDEVICES, buf, &len))
		goto out;

	if (len < 1)
		goto out;

	for (i = 0; i < list->len && retval; i++) {
		if(name_to_addr) {
			if (!strcmp (list->dev[i].info, name)) {
				*addr = list->dev[i].daddr;
				retval = 0;
			}
		}
		else {
			if (list->dev[i].daddr == *addr) {
				strncpy (name, list->dev[i].info,
					 sizeof(list->dev[i].info));
				name[sizeof (list->dev[i].info)] = '\0';
				retval = 0;
			}
		}
	}

 out:
	LINC_CLOSE (fd);

	return retval;
}

#define IRDA_PREFIX      "IrDA-"
#define IRDA_PREFIX_LEN  5

static int
irda_getaddrinfo (const char             *nodename,
		  const char             *servname,
		  const struct addrinfo  *hints,
		  struct addrinfo       **res)
{
	struct sockaddr_irda  sai;
	struct addrinfo      *retval;
	char                  hnbuf[IRDA_NICKNAME_MAX + IRDA_PREFIX_LEN];
	int                   n;
	char                 *tptr;

	/* 
	 * For now, it *has* to start with IRDA_PREFIX to be in the IRDA
	 * hostname/address format we use 
	 */
	if (nodename && strcmp (nodename, IRDA_PREFIX))
		return EAI_NONAME;

	sai.sir_family   = AF_IRDA;
	sai.sir_lsap_sel = LSAP_ANY;

	if (servname)
		g_snprintf (sai.sir_name, sizeof (sai.sir_name), "%s", servname);
	else {
		struct timeval t;

		gettimeofday (&t, NULL);
		g_snprintf (sai.sir_name, sizeof (sai.sir_name), "IIOP%x%x",
		            rand(), (guint)(t.tv_sec^t.tv_usec));
	}

	if (nodename) {
		if (!strncmp (nodename + IRDA_PREFIX_LEN, "0x", 2)) {
			if (sscanf (nodename + strlen(IRDA_PREFIX "0x"),
				    "%u", &sai.sir_addr) != 1)
				return EAI_NONAME;

			/* It's a numeric address - we need to find the hostname */
			g_strncpy (hnbuf, IRDA_PREFIX, IRDA_NICKNAME_MAX + IRDA_PREFIX_LEN);
			if (irda_find_device (&sai.sir_addr, 
					      hnbuf + IRDA_PREFIX_LEN,
					      FALSE))
				return EAI_NONAME;

			nodename = hnbuf;
		}
		else if (!(hints->ai_flags & AI_NUMERICHOST)) {
			/* It's a name - we need to find the address */
			if (irda_find_device (&sai.sir_addr, 
					      (char *)nodename + IRDA_PREFIX_LEN,
					      TRUE))
				return EAI_NONAME;
		}
		else
			return EAI_NONAME;
	}
	else
		/* AI_PASSIVE flag gets ignored, sort of */
		hnbuf[0] = 0;

	n = sizeof (struct addrinfo) + sizeof (struct sockaddr_irda);

	if (hints->ai_flags & AI_CANONNAME)
		n += strlen(hnbuf) + 1;

	retval = g_malloc0(n);

	tptr = (char *)retval;
	tptr += sizeof (struct addrinfo);
	retval->ai_addr = (struct sockaddr *)tptr;
	memcpy (retval->ai_addr, &sai, sizeof (struct sockaddr_irda));
	tptr += sizeof (struct sockaddr_irda);
	g_strncpy (tptr, hnbuf, IRDA_NICKNAME_MAX + IRDA_PREFIX_LEN);
	retval->ai_family = AF_IRDA;
	retval->ai_socktype = SOCK_STREAM;
	retval->ai_protocol = 0;
	retval->ai_next = NULL;
	retval->ai_addrlen = sizeof(struct sockaddr_irda);

	*res = retval;

	return 0;
}

static int
irda_getnameinfo (const struct sockaddr *sa,
		  LincSockLen            sa_len,
		  char                  *host,
		  size_t                 hostlen,
		  char                  *serv,
		  size_t                 servlen,
		  int                    flags)
{
	struct sockaddr_irda *sai = (struct sockaddr_irda *) sa;
	gboolean got_host = FALSE;
	/* Here, we talk to the host specified, and ask it for its name */

	if (sa_len != sizeof (struct sockaddr_irda))
		return -1;

	/* It doesn't seem like the sir_lsap_sel is supposed to be taken into consideration when connecting... */
	if (!(flags & NI_NUMERICHOST)) {
		guint32 daddr;
		char hostbuf [IRDA_NICKNAME_MAX];

		daddr = sai->sir_addr;
		if (!irda_find_device (&daddr, hostbuf, FALSE)) {
			g_snprintf (host, hostlen, "%s", hostbuf);
			got_host = TRUE;
		}
	}
	if (!got_host) {
		if (flags & NI_NAMEREQD)
			return -1;

		g_snprintf (host, hostlen, IRDA_PREFIX "%#08x", sai->sir_addr);
	}

	g_snprintf(serv, servlen, "%s", sai->sir_name);

	return 0;
}
#endif /* AF_IRDA */
#endif /* 0 */

void
linc_protocol_destroy_cnx (const LINCProtocolInfo *proto,
			   int                     fd,
			   const char             *host,
			   const char             *service)
{
	g_return_if_fail (proto != NULL);

	if (fd >= 0) {
		if (proto->destroy)
			proto->destroy (fd, host, service);
		
		LINC_CLOSE (fd);
	}
}


void
linc_protocol_destroy_addr (const LINCProtocolInfo *proto,
			    int                     fd,
			    struct sockaddr        *saddr)
{
	g_return_if_fail (proto != NULL);

	if (fd >= 0) {
#ifdef AF_UNIX
		if (proto->family == AF_UNIX && proto->destroy) {
			/* We are AF_UNIX - we need the path to unlink */
			struct sockaddr_un *addr_un =
				(struct sockaddr_un *) saddr;
			proto->destroy (fd, NULL, addr_un->sun_path);
		}
#endif
		LINC_CLOSE (fd);
		g_free (saddr);
	}

}

/*
 * linc_protocol_all:
 *
 * Returns a list of protocols supported by linc.
 *
 * Note: the list is terminated by a #LINCProtocolInfo with a
 *       NULL name pointer.
 *
 * Return Value: an array of #LINCProtocolInfo structures.
 */
LINCProtocolInfo * const
linc_protocol_all (void)
{
	return static_linc_protocols;
}

/*
 * linc_protocol_find:
 * @name: name of the protocol.
 *
 * Find a protocol identified by @name.
 *
 * Return Value: a pointer to a valid #LINCProtocolInfo structure if 
 *               the protocol is supported by linc, NULL otherwise.
 */
LINCProtocolInfo * const
linc_protocol_find (const char *name)
{
	int i;

	for (i = 0; static_linc_protocols [i].name; i++) {
		if (!strcmp (name, static_linc_protocols [i].name))
			return &static_linc_protocols [i];
	}

	return NULL;
}

/*
 * linc_protocol_find_num:
 * @family: the family identifier of the protocol - i.e. AF_*
 *
 * Find a protocol identified by @family.
 *
 * Return Value: a pointer to a valid #LINCProtocolInfo structure if
 *               the protocol is supported by linc, NULL otherwise.
 */
LINCProtocolInfo * const
linc_protocol_find_num (const int family)
{
	int i;

	for (i = 0; static_linc_protocols [i].name; i++) {
		if (family == static_linc_protocols [i].family)
			return &static_linc_protocols [i];
	}

	return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1