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