/*
* 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