/*
 * Copyright (c) 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: parsesockstr.c,v 1.3 2006/09/29 05:35:07 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/ctype.h"
#include "sm/heap.h"
#include "sm/net.h"
#include "sm/sockcnf.h"
#include "sm/socket.h"

#define NETUNIX	1
#define NETINET	1
#define NETINET6 0

static sm_ret_T
setdfltfamily(sockspec_P sockspec)
{
	SM_REQUIRE(sockspec != NULL);

#if NETUNIX
	sockspec->sckspc_type = SOCK_TYPE_UNIX;
	return SM_SUCCESS;
#endif
#if NETINET
	sockspec->sckspc_type = SOCK_TYPE_INET;
	return SM_SUCCESS;
#endif
#if NETINET6
	sockspec->sckspc_type = SOCK_TYPE_INET6;
	return SM_SUCCESS;
#endif
	return sm_err_perm(EINVAL);
}

/*
**  PARSESOCKSTR -- convert textual description of socket into sockspec
**
**	Parameters:
**		sockstr -- textual description of socket
**		sockspec -- internal description of socket
**
**	Returns:
**		usual error code
**
	sockstr ::= [protocol ":"] filename
		| ("inet"|"inet6") ":" port ["@" host]
*/

sm_ret_T
parsesockstr(const char *sockstr, sockspec_P sockspec)
{
	sm_ret_T ret;
	size_t len;
	char *colon, *at;
	char p[MAXPATHLEN + 16];

	if (sockstr == NULL || *sockstr == '\0')
		return sm_err_perm(EINVAL);
	SM_REQUIRE(sockspec != NULL);

	sm_memzero(sockspec, sizeof(*sockspec));
	len = strlcpy(p, sockstr, sizeof(p));
	if (len >= sizeof(p))
		return sm_err_perm(SM_E_2BIG);
	colon = strchr(p, ':');
	if (colon != NULL)
	{
		*colon = '\0';

		if (*p == '\0')
		{
			ret = setdfltfamily(sockspec);
			if (sm_is_err(ret))
				goto error;
		}
#if NETUNIX
		else if (strcasecmp(p, "unix") == 0 ||
			 strcasecmp(p, "local") == 0)
			sockspec->sckspc_type = SOCK_TYPE_UNIX;
#endif
#if NETINET
		else if (strcasecmp(p, "inet") == 0)
			sockspec->sckspc_type = SOCK_TYPE_INET;
#endif
#if NETINET6
		else if (strcasecmp(p, "inet6") == 0)
			sockspec->sckspc_type = SOCK_TYPE_INET6;
#endif
		else
		{
			ret = sm_err_perm(EINVAL);
			goto error;
		}
		*colon++ = ':';
	}
	else
	{
		colon = p;
		ret = setdfltfamily(sockspec);
		if (sm_is_err(ret))
			goto error;
	}

#if NETUNIX
	if (sockspec->sckspc_type == SOCK_TYPE_UNIX)
	{
		struct sockaddr_un sunix;

		at = colon;
		len = strlen(colon) + 1;
		if (len <= 1)
		{
			ret = sm_err_perm(EINVAL);
			goto error;
		}
		if (len >= sizeof(sunix.sun_path))
		{
			ret = sm_err_perm(SM_E_2BIG);
			goto error;
		}
		sockspec->sock_unix.unixsckspc_path = strdup(colon);
		if (sockspec->sock_unix.unixsckspc_path == NULL)
		{
			ret = sm_err_perm(ENOMEM);
			goto error;
		}
	}
#endif /* NETUNIX */

#if NETINET || NETINET6
	if (sockspec->sckspc_type == SOCK_TYPE_INET ||
	    sockspec->sckspc_type == SOCK_TYPE_INET6)
	{
		unsigned short port;

		/* Parse port@host */
		at = strchr(colon, '@');
		if (at == NULL)
		{
			switch (sockspec->sckspc_type)
			{
			  case SOCK_TYPE_INET:
# if NETINET
				sockspec->sock_inet.inetsckspc_addr =
					INADDR_ANY;
# endif
				break;

			  case SOCK_TYPE_INET6:
# if NETINET6
				sockspec->sock_inet6.inet6sckspc_addr =
					in6addr_any;
# endif
				break;
			  case SOCK_TYPE_UNIX:
				SM_ASSERT(sockspec->sckspc_type !=
					SOCK_TYPE_UNIX);
				ret = sm_err_perm(EINVAL);
				goto error;
			}
		}
		else
			*at = '\0';

		if (ISDIGIT(*colon))
		{
			int i;

			i = atoi(colon);
			if (i <= 0 || i >= (int) USHRT_MAX)
			{
				ret = sm_err_perm(EINVAL);
				goto error;
			}
			port = htons((unsigned short) i);
		}
		else
		{
			struct servent *sp;

			sp = getservbyname(colon, "tcp");
			if (sp == NULL)
			{
				ret = sm_err_perm(EINVAL);
				goto error;
			}
			port = sp->s_port;
		}
		if (at != NULL)
		{
			*at++ = '@';
			if (*at == '[')
			{
				char *end;
				bool found;
# if NETINET
				ipv4_T hid;
# endif
# if NETINET6
				struct sockaddr_in6 hid6;
# endif

				end = strchr(at, ']');
				if (NULL == end)
				{
					ret = sm_err_perm(EINVAL);
					goto error;
				}

				found = false;
				hid = INADDR_NONE;
				SM_ASSERT(end != NULL);
				*end = '\0';
# if NETINET
				if (sockspec->sckspc_type == SOCK_TYPE_INET &&
				    (hid = inet_addr(at + 1)) != INADDR_NONE)
				{
					sockspec->sock_inet.inetsckspc_addr =
						hid;
					sockspec->sock_inet.inetsckspc_port =
						port;
					found = true;
				}
# endif /* NETINET */
# if NETINET6
				sm_memzero(&hid6, sizeof(hid6));
				if (sockspec->sckspc_type == SOCK_TYPE_INET6 &&
				    inet_pton(SOCK_TYPE_INET6, at + 1,
						&hid6.sin6_addr) == 1)
				{
					sockspec->sin6.sin6_addr =
						hid6.sin6_addr;
					sockspec.sin6.sin6_port = port;
					found = true;
				}
# endif /* NETINET6 */
				*end = ']';
				if (!found)
				{
					ret = sm_err_perm(EINVAL);
					goto error;
				}
			}
			else
			{
# if HAVE_GETADDRINFO
				struct addrinfo hints, *res;
				int error;

				/*
				**  note: sockaddr's sa_data has the following
				**  layout:
				**  sockaddr_in:
				**	16 bit port#
				**	32 bit IPv4 address
				**  sockaddr_in6:
				**	16 bit port#
				**	32 bit flow label
				**	128 bit IPv6 address
				*/

				sm_memzero(&hints, sizeof(hints));
#  if NETINET && NETINET6
				hints.ai_family = AF_UNSPEC;
#  else
#   if NETINET6
				hints.ai_family = AF_INET6;
#   else
#    if NETINET
				hints.ai_family = AF_INET;
#    else
				hints.ai_family = AF_UNSPEC;
#    endif
#   endif
#  endif
				hints.ai_socktype = SOCK_STREAM;
				error = getaddrinfo(at, NULL, &hints, &res);
				if (error != 0)
				{
					ret = sm_err_perm(EINVAL);
					goto error;
				}

				switch (res->ai_family)
				{
#  if NETINET
				  case AF_INET:
					sockspec->sckspc_type = SOCK_TYPE_INET;
					sm_memmove(&sockspec->sock_inet.inetsckspc_addr,
						res->ai_addr->sa_data + 2,
						sizeof(sockspec->sock_inet.inetsckspc_addr));
					sockspec->sock_inet.inetsckspc_port = port;
					break;
#  endif /* NETINET */

#  if NETINET6
				  case AF_INET6:
					sockspec.sckspc_type = SOCK_TYPE_INET6;
					(void) sm_memmove(&sockspec.sin6.sin6_addr,
							res->ai_addr->sa_data
							+ 2 + 4,
							IN6ADDRSZ);
					sockspec.sin6.sin6_port = port;
					break;
#  endif /* NETINET6 */

				  default:
					ret = sm_err_perm(EINVAL);
					freeaddrinfo(res);
					goto error;
				}

				freeaddrinfo(res);
# else /* HAVE_GETADDRINFO */
				struct hostent *hp;

				hp = gethostbyname(at);
				if (hp == NULL)
				{
					ret = sm_err_perm(EINVAL);
					goto error;
				}
				switch (hp->h_addrtype)
				{
#  if NETINET
				  case AF_INET:
					sockspec->sckspc_type = SOCK_TYPE_INET;
					sm_memmove(&sockspec->sock_inet.inetsckspc_addr,
						hp->h_addr,
						sizeof(sockspec->sock_inet.inetsckspc_addr));
					sockspec->sock_inet.inetsckspc_port = port;
					break;
#  endif /* NETINET */

#  if NETINET6
				  case AF_INET6:
					sockspec.sckspc_type = SOCK_TYPE_INET6;
					(void) memmove(&sockspec.sin6.sin6_addr,
						       hp->h_addr,
						       IN6ADDRSZ);
					sockspec.sin6.sin6_port = port;
					break;
#  endif /* NETINET6 */

				  default:
					ret = sm_err_perm(EINVAL);
#  if NETINET6
					freehostent(hp);
#  endif
					goto error;
				}
#  if NETINET6
				freehostent(hp);
#  endif
# endif /* HAVE_GETADDRINFO */
			}
		}
		else
		{
			switch (sockspec->sckspc_type)
			{
			  case SOCK_TYPE_INET:
# if NETINET
				sockspec->sock_inet.inetsckspc_port = port;
# endif
				break;
			  case SOCK_TYPE_INET6:
# if NETINET6
				sockspec.sin6.sin6_port = port;
# endif
				break;
			  case SOCK_TYPE_UNIX:
				SM_ASSERT(sockspec->sckspc_type !=
					SOCK_TYPE_UNIX);
				ret = sm_err_perm(EINVAL);
				goto error;
			}
		}
	}
#endif /* NETINET || NETINET6 */

	return SM_SUCCESS;

  error:
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1