diff options
author | Joseph Mingrone <jrm@FreeBSD.org> | 2023-03-27 18:45:17 +0000 |
---|---|---|
committer | Joseph Mingrone <jrm@FreeBSD.org> | 2023-03-27 18:45:17 +0000 |
commit | 35af88c96350eb786f1198dfb6b29a171016e6bf (patch) | |
tree | e883c1f8391d5ca1afd57abd8ed9d2cd7c274b0b /sockutils.c | |
parent | 20616273d52132557e786a8aea1637be4c218a08 (diff) | |
download | src-35af88c96350eb786f1198dfb6b29a171016e6bf.tar.gz src-35af88c96350eb786f1198dfb6b29a171016e6bf.zip |
Diffstat (limited to 'sockutils.c')
-rw-r--r-- | sockutils.c | 817 |
1 files changed, 646 insertions, 171 deletions
diff --git a/sockutils.c b/sockutils.c index d3e94649d9d4..933f32670761 100644 --- a/sockutils.c +++ b/sockutils.c @@ -56,11 +56,7 @@ #include <errno.h> /* for the errno variable */ #include <stdio.h> /* for the stderr file */ #include <stdlib.h> /* for malloc() and free() */ -#ifdef HAVE_LIMITS_H -#include <limits.h> -#else -#define INT_MAX 2147483647 -#endif +#include <limits.h> /* for INT_MAX */ #include "pcap-int.h" @@ -71,7 +67,7 @@ /* * Winsock initialization. * - * Ask for WinSock 2.2. + * Ask for Winsock 2.2. */ #define WINSOCK_MAJOR_VERSION 2 #define WINSOCK_MINOR_VERSION 2 @@ -97,7 +93,7 @@ * * On Windows, send() and recv() return an int. * - * Wth MSVC, there *is* no ssize_t. + * With MSVC, there *is* no ssize_t. * * With MinGW, there is an ssize_t type; it is either an int (32 bit) * or a long long (64 bit). @@ -124,51 +120,187 @@ static int sock_ismcastaddr(const struct sockaddr *saddr); * * ****************************************************/ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +const uint8_t *fuzzBuffer; +size_t fuzzSize; +size_t fuzzPos; + +void sock_initfuzz(const uint8_t *Data, size_t Size) { + fuzzPos = 0; + fuzzSize = Size; + fuzzBuffer = Data; +} + +static int fuzz_recv(char *bufp, int remaining) { + if (remaining > fuzzSize - fuzzPos) { + remaining = fuzzSize - fuzzPos; + } + if (fuzzPos < fuzzSize) { + memcpy(bufp, fuzzBuffer + fuzzPos, remaining); + } + fuzzPos += remaining; + return remaining; +} +#endif + +int sock_geterrcode(void) +{ +#ifdef _WIN32 + return GetLastError(); +#else + return errno; +#endif +} + /* - * Format an error message given an errno value (UN*X) or a WinSock error + * Format an error message given an errno value (UN*X) or a Winsock error * (Windows). */ -void sock_fmterror(const char *caller, int errcode, char *errbuf, int errbuflen) +void sock_vfmterrmsg(char *errbuf, size_t errbuflen, int errcode, + const char *fmt, va_list ap) { if (errbuf == NULL) return; #ifdef _WIN32 - pcap_fmt_errmsg_for_win32_err(errbuf, errbuflen, errcode, - "%s", caller); + pcap_vfmt_errmsg_for_win32_err(errbuf, errbuflen, errcode, + fmt, ap); #else - pcap_fmt_errmsg_for_errno(errbuf, errbuflen, errcode, - "%s", caller); + pcap_vfmt_errmsg_for_errno(errbuf, errbuflen, errcode, + fmt, ap); #endif } +void sock_fmterrmsg(char *errbuf, size_t errbuflen, int errcode, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sock_vfmterrmsg(errbuf, errbuflen, errcode, fmt, ap); + va_end(ap); +} + /* - * \brief It retrieves the error message after an error occurred in the socket interface. - * - * This function is defined because of the different way errors are returned in UNIX - * and Win32. This function provides a consistent way to retrieve the error message - * (after a socket error occurred) on all the platforms. - * - * \param caller: a pointer to a user-allocated string which contains a message that has - * to be printed *before* the true error message. It could be, for example, 'this error - * comes from the recv() call at line 31'. - * - * \param errbuf: a pointer to an user-allocated buffer that will contain the complete - * error message. This buffer has to be at least 'errbuflen' in length. - * It can be NULL; in this case the error cannot be printed. - * - * \param errbuflen: length of the buffer that will contains the error. The error message cannot be - * larger than 'errbuflen - 1' because the last char is reserved for the string terminator. + * Format an error message for the last socket error. + */ +void sock_geterrmsg(char *errbuf, size_t errbuflen, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sock_vfmterrmsg(errbuf, errbuflen, sock_geterrcode(), fmt, ap); + va_end(ap); +} + +/* + * Types of error. * - * \return No return values. The error message is returned in the 'string' parameter. + * These are sorted by how likely they are to be the "underlying" problem, + * so that lower-rated errors for a given address in a given family + * should not overwrite higher-rated errors for another address in that + * family, and higher-rated errors should overwrit elower-rated errors. */ -void sock_geterror(const char *caller, char *errbuf, int errbuflen) +typedef enum { + SOCK_CONNERR, /* connection error */ + SOCK_HOSTERR, /* host error */ + SOCK_NETERR, /* network error */ + SOCK_AFNOTSUPERR, /* address family not supported */ + SOCK_UNKNOWNERR, /* unknown error */ + SOCK_NOERR /* no error */ +} sock_errtype; + +static sock_errtype sock_geterrtype(int errcode) { + switch (errcode) { + +#ifdef _WIN32 + case WSAECONNRESET: + case WSAECONNABORTED: + case WSAECONNREFUSED: +#else + case ECONNRESET: + case ECONNABORTED: + case ECONNREFUSED: +#endif + /* + * Connection error; this means the problem is probably + * that there's no server set up on the remote machine, + * or that it is set up, but it's IPv4-only or IPv6-only + * and we're trying the wrong address family. + * + * These overwrite all other errors, as they indicate + * that, even if somethng else went wrong in another + * attempt, this probably wouldn't work even if the + * other problems were fixed. + */ + return (SOCK_CONNERR); + +#ifdef _WIN32 + case WSAENETUNREACH: + case WSAETIMEDOUT: + case WSAEHOSTDOWN: + case WSAEHOSTUNREACH: +#else + case ENETUNREACH: + case ETIMEDOUT: + case EHOSTDOWN: + case EHOSTUNREACH: +#endif + /* + * Network errors that could be IPv4-specific, IPv6- + * specific, or present with both. + * + * Don't overwrite connection errors, but overwrite + * everything else. + */ + return (SOCK_HOSTERR); + #ifdef _WIN32 - sock_fmterror(caller, GetLastError(), errbuf, errbuflen); + case WSAENETDOWN: + case WSAENETRESET: #else - sock_fmterror(caller, errno, errbuf, errbuflen); + case ENETDOWN: + case ENETRESET: #endif + /* + * Network error; this means we don't know whether + * there's a server set up on the remote machine, + * and we don't have a reason to believe that IPv6 + * any worse or better than IPv4. + * + * These probably indicate a local failure, e.g. + * an interface is down. + * + * Don't overwrite connection errors or host errors, + * but overwrite everything else. + */ + return (SOCK_NETERR); + +#ifdef _WIN32 + case WSAEAFNOSUPPORT: +#else + case EAFNOSUPPORT: +#endif + /* + * "Address family not supported" probably means + * "No soup^WIPv6 for you!". + * + * Don't overwrite connection errors, host errors, or + * network errors (none of which we should get for this + * address family if it's not supported), but overwrite + * everything else. + */ + return (SOCK_AFNOTSUPERR); + + default: + /* + * Anything else. + * + * Don't overwrite any errors. + */ + return (SOCK_UNKNOWNERR); + } } /* @@ -201,7 +333,7 @@ int sock_init(char *errbuf, int errbuflen) WINSOCK_MINOR_VERSION), &wsaData) != 0) { if (errbuf) - pcap_snprintf(errbuf, errbuflen, "Failed to initialize Winsock\n"); + snprintf(errbuf, errbuflen, "Failed to initialize Winsock\n"); WSACleanup(); @@ -262,6 +394,79 @@ static int sock_ismcastaddr(const struct sockaddr *saddr) } } +struct addr_status { + struct addrinfo *info; + int errcode; + sock_errtype errtype; +}; + +/* + * Sort by IPv4 address vs. IPv6 address. + */ +static int compare_addrs_to_try_by_address_family(const void *a, const void *b) +{ + const struct addr_status *addr_a = (const struct addr_status *)a; + const struct addr_status *addr_b = (const struct addr_status *)b; + + return addr_a->info->ai_family - addr_b->info->ai_family; +} + +/* + * Sort by error type and, within a given error type, by error code and, + * within a given error code, by IPv4 address vs. IPv6 address. + */ +static int compare_addrs_to_try_by_status(const void *a, const void *b) +{ + const struct addr_status *addr_a = (const struct addr_status *)a; + const struct addr_status *addr_b = (const struct addr_status *)b; + + if (addr_a->errtype == addr_b->errtype) + { + if (addr_a->errcode == addr_b->errcode) + { + return addr_a->info->ai_family - addr_b->info->ai_family; + } + return addr_a->errcode - addr_b->errcode; + } + + return addr_a->errtype - addr_b->errtype; +} + +static SOCKET sock_create_socket(struct addrinfo *addrinfo, char *errbuf, + int errbuflen) +{ + SOCKET sock; +#ifdef SO_NOSIGPIPE + int on = 1; +#endif + + sock = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (sock == INVALID_SOCKET) + { + sock_geterrmsg(errbuf, errbuflen, "socket() failed"); + return INVALID_SOCKET; + } + + /* + * Disable SIGPIPE, if we have SO_NOSIGPIPE. We don't want to + * have to deal with signals if the peer closes the connection, + * especially in client programs, which may not even be aware that + * they're sending to sockets. + */ +#ifdef SO_NOSIGPIPE + if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (char *)&on, + sizeof (int)) == -1) + { + sock_geterrmsg(errbuf, errbuflen, + "setsockopt(SO_NOSIGPIPE) failed"); + closesocket(sock); + return INVALID_SOCKET; + } +#endif + return sock; +} + /* * \brief It initializes a network connection both from the client and the server side. * @@ -271,7 +476,10 @@ static int sock_ismcastaddr(const struct sockaddr *saddr) * * In case of a server socket, the function calls socket(), bind() and listen(). * - * This function is usually preceeded by the sock_initaddress(). + * This function is usually preceded by the sock_initaddress(). + * + * \param host: for client sockets, the host name to which we're trying + * to connect. * * \param addrinfo: pointer to an addrinfo variable which will be used to * open the socket and such. This variable is the one returned by the previous call to @@ -293,48 +501,33 @@ static int sock_ismcastaddr(const struct sockaddr *saddr) * if everything is fine, INVALID_SOCKET if some errors occurred. The error message is returned * in the 'errbuf' variable. */ -SOCKET sock_open(struct addrinfo *addrinfo, int server, int nconn, char *errbuf, int errbuflen) +SOCKET sock_open(const char *host, struct addrinfo *addrinfo, int server, int nconn, char *errbuf, int errbuflen) { SOCKET sock; -#if defined(SO_NOSIGPIPE) || defined(IPV6_V6ONLY) || defined(IPV6_BINDV6ONLY) - int on = 1; -#endif - - sock = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); - if (sock == INVALID_SOCKET) - { - sock_geterror("socket()", errbuf, errbuflen); - return INVALID_SOCKET; - } - - /* - * Disable SIGPIPE, if we have SO_NOSIGPIPE. We don't want to - * have to deal with signals if the peer closes the connection, - * especially in client programs, which may not even be aware that - * they're sending to sockets. - */ -#ifdef SO_NOSIGPIPE - if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (char *)&on, - sizeof (int)) == -1) - { - sock_geterror("setsockopt(SO_NOSIGPIPE)", errbuf, errbuflen); - closesocket(sock); - return INVALID_SOCKET; - } -#endif /* This is a server socket */ if (server) { + int on; + + /* + * Attempt to create the socket. + */ + sock = sock_create_socket(addrinfo, errbuf, errbuflen); + if (sock == INVALID_SOCKET) + { + return INVALID_SOCKET; + } + /* * Allow a new server to bind the socket after the old one * exited, even if lingering sockets are still present. * * Don't treat an error as a failure. */ - int optval = 1; + on = 1; (void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *)&optval, sizeof (optval)); + (char *)&on, sizeof (on)); #if defined(IPV6_V6ONLY) || defined(IPV6_BINDV6ONLY) /* @@ -371,11 +564,12 @@ SOCKET sock_open(struct addrinfo *addrinfo, int server, int nconn, char *errbuf, #endif /* IPV6_V6ONLY */ if (addrinfo->ai_family == PF_INET6) { + on = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof (int)) == -1) { if (errbuf) - pcap_snprintf(errbuf, errbuflen, "setsockopt(IPV6_V6ONLY)"); + snprintf(errbuf, errbuflen, "setsockopt(IPV6_V6ONLY)"); closesocket(sock); return INVALID_SOCKET; } @@ -385,7 +579,7 @@ SOCKET sock_open(struct addrinfo *addrinfo, int server, int nconn, char *errbuf, /* WARNING: if the address is a mcast one, I should place the proper Win32 code here */ if (bind(sock, addrinfo->ai_addr, (int) addrinfo->ai_addrlen) != 0) { - sock_geterror("bind()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, "bind() failed"); closesocket(sock); return INVALID_SOCKET; } @@ -393,7 +587,8 @@ SOCKET sock_open(struct addrinfo *addrinfo, int server, int nconn, char *errbuf, if (addrinfo->ai_socktype == SOCK_STREAM) if (listen(sock, nconn) == -1) { - sock_geterror("listen()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, + "listen() failed"); closesocket(sock); return INVALID_SOCKET; } @@ -403,68 +598,259 @@ SOCKET sock_open(struct addrinfo *addrinfo, int server, int nconn, char *errbuf, } else /* we're the client */ { + struct addr_status *addrs_to_try; struct addrinfo *tempaddrinfo; - char *errbufptr; - size_t bufspaceleft; - - tempaddrinfo = addrinfo; - errbufptr = errbuf; - bufspaceleft = errbuflen; - *errbufptr = 0; + size_t numaddrinfos; + size_t i; + int current_af = AF_UNSPEC; /* - * We have to loop though all the addinfo returned. - * For instance, we can have both IPv6 and IPv4 addresses, but the service we're trying - * to connect to is unavailable in IPv6, so we have to try in IPv4 as well + * We have to loop though all the addrinfos returned. + * For instance, we can have both IPv6 and IPv4 addresses, + * but the service we're trying to connect to is unavailable + * in IPv6, so we have to try in IPv4 as well. + * + * How many addrinfos do we have? */ - while (tempaddrinfo) + numaddrinfos = 0; + for (tempaddrinfo = addrinfo; tempaddrinfo != NULL; + tempaddrinfo = tempaddrinfo->ai_next) { + numaddrinfos++; + } - if (connect(sock, tempaddrinfo->ai_addr, (int) tempaddrinfo->ai_addrlen) == -1) - { - size_t msglen; - char TmpBuffer[100]; - char SocketErrorMessage[SOCK_ERRBUF_SIZE]; - - /* - * We have to retrieve the error message before any other socket call completes, otherwise - * the error message is lost - */ - sock_geterror("Connect to socket failed", - SocketErrorMessage, sizeof(SocketErrorMessage)); + if (numaddrinfos == 0) + { + snprintf(errbuf, errbuflen, + "There are no addresses in the address list"); + return INVALID_SOCKET; + } - /* Returns the numeric address of the host that triggered the error */ - sock_getascii_addrport((struct sockaddr_storage *) tempaddrinfo->ai_addr, TmpBuffer, sizeof(TmpBuffer), NULL, 0, NI_NUMERICHOST, TmpBuffer, sizeof(TmpBuffer)); + /* + * Allocate an array of struct addr_status and fill it in. + */ + addrs_to_try = calloc(numaddrinfos, sizeof *addrs_to_try); + if (addrs_to_try == NULL) + { + snprintf(errbuf, errbuflen, + "Out of memory connecting to %s", host); + return INVALID_SOCKET; + } - pcap_snprintf(errbufptr, bufspaceleft, - "Is the server properly installed on %s? %s", TmpBuffer, SocketErrorMessage); + for (tempaddrinfo = addrinfo, i = 0; tempaddrinfo != NULL; + tempaddrinfo = tempaddrinfo->ai_next, i++) + { + addrs_to_try[i].info = tempaddrinfo; + addrs_to_try[i].errcode = 0; + addrs_to_try[i].errtype = SOCK_NOERR; + } - /* In case more then one 'connect' fails, we manage to keep all the error messages */ - msglen = strlen(errbufptr); + /* + * Sort the structures to put the IPv4 addresses before the + * IPv6 addresses; we will have to create an IPv4 socket + * for the IPv4 addresses and an IPv6 socket for the IPv6 + * addresses (one of the arguments to socket() is the + * address/protocol family to use, and IPv4 and IPv6 are + * separate address/protocol families). + */ + qsort(addrs_to_try, numaddrinfos, sizeof *addrs_to_try, + compare_addrs_to_try_by_address_family); - errbufptr[msglen] = ' '; - errbufptr[msglen + 1] = 0; + /* Start out with no socket. */ + sock = INVALID_SOCKET; - bufspaceleft = bufspaceleft - (msglen + 1); - errbufptr += (msglen + 1); + /* + * Now try them all. + */ + for (i = 0; i < numaddrinfos; i++) + { + tempaddrinfo = addrs_to_try[i].info; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + break; +#endif + /* + * If we have a socket, but it's for a + * different address family, close it. + */ + if (sock != INVALID_SOCKET && + current_af != tempaddrinfo->ai_family) + { + closesocket(sock); + sock = INVALID_SOCKET; + } - tempaddrinfo = tempaddrinfo->ai_next; + /* + * If we don't have a socket, open one + * for *this* address's address family. + */ + if (sock == INVALID_SOCKET) + { + sock = sock_create_socket(tempaddrinfo, + errbuf, errbuflen); + if (sock == INVALID_SOCKET) + { + free(addrs_to_try); + return INVALID_SOCKET; + } + } + if (connect(sock, tempaddrinfo->ai_addr, (int) tempaddrinfo->ai_addrlen) == -1) + { + addrs_to_try[i].errcode = sock_geterrcode(); + addrs_to_try[i].errtype = + sock_geterrtype(addrs_to_try[i].errcode); } else break; } /* - * Check how we exit from the previous loop - * If tempaddrinfo is equal to NULL, it means that all the connect() failed. + * Check how we exited from the previous loop. + * If tempaddrinfo is equal to NULL, it means that all + * the connect() attempts failed. Construct an + * error message. */ - if (tempaddrinfo == NULL) + if (i == numaddrinfos) { + int same_error_for_all; + int first_error; + closesocket(sock); + + /* + * Sort the statuses to group together categories + * of errors, errors within categories, and + * address families within error sets. + */ + qsort(addrs_to_try, numaddrinfos, sizeof *addrs_to_try, + compare_addrs_to_try_by_status); + + /* + * Are all the errors the same? + */ + same_error_for_all = 1; + first_error = addrs_to_try[0].errcode; + for (i = 1; i < numaddrinfos; i++) + { + if (addrs_to_try[i].errcode != first_error) + { + same_error_for_all = 0; + break; + } + } + + if (same_error_for_all) { + /* + * Yes. No need to show the IP + * addresses. + */ + if (addrs_to_try[0].errtype == SOCK_CONNERR) { + /* + * Connection error; note that + * the daemon might not be set + * up correctly, or set up at all. + */ + sock_fmterrmsg(errbuf, errbuflen, + addrs_to_try[0].errcode, + "Is the server properly installed? Cannot connect to %s", + host); + } else { + sock_fmterrmsg(errbuf, errbuflen, + addrs_to_try[0].errcode, + "Cannot connect to %s", host); + } + } else { + /* + * Show all the errors and the IP addresses + * to which they apply. + */ + char *errbufptr; + size_t bufspaceleft; + size_t msglen; + + snprintf(errbuf, errbuflen, + "Connect to %s failed: ", host); + + msglen = strlen(errbuf); + errbufptr = errbuf + msglen; + bufspaceleft = errbuflen - msglen; + + for (i = 0; i < numaddrinfos && + addrs_to_try[i].errcode != SOCK_NOERR; + i++) + { + /* + * Get the string for the address + * and port that got this error. + */ + sock_getascii_addrport((struct sockaddr_storage *) addrs_to_try[i].info->ai_addr, + errbufptr, (int)bufspaceleft, + NULL, 0, NI_NUMERICHOST, NULL, 0); + msglen = strlen(errbuf); + errbufptr = errbuf + msglen; + bufspaceleft = errbuflen - msglen; + + if (i + 1 < numaddrinfos && + addrs_to_try[i + 1].errcode == addrs_to_try[i].errcode) + { + /* + * There's another error + * after this, and it has + * the same error code. + * + * Append a comma, as the + * list of addresses with + * this error has another + * entry. + */ + snprintf(errbufptr, bufspaceleft, + ", "); + } + else + { + /* + * Either there are no + * more errors after this, + * or the next error is + * different. + * + * Append a colon and + * the message for tis + * error, followed by a + * comma if there are + * more errors. + */ + sock_fmterrmsg(errbufptr, + bufspaceleft, + addrs_to_try[i].errcode, + "%s", ""); + msglen = strlen(errbuf); + errbufptr = errbuf + msglen; + bufspaceleft = errbuflen - msglen; + + if (i + 1 < numaddrinfos && + addrs_to_try[i + 1].errcode != SOCK_NOERR) + { + /* + * More to come. + */ + snprintf(errbufptr, + bufspaceleft, + ", "); + } + } + msglen = strlen(errbuf); + errbufptr = errbuf + msglen; + bufspaceleft = errbuflen - msglen; + } + } + free(addrs_to_try); return INVALID_SOCKET; } else + { + free(addrs_to_try); return sock; + } } } @@ -495,7 +881,7 @@ int sock_close(SOCKET sock, char *errbuf, int errbuflen) */ if (shutdown(sock, SHUT_WR)) { - sock_geterror("shutdown()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, "shutdown() feiled"); /* close the socket anyway */ closesocket(sock); return -1; @@ -506,7 +892,7 @@ int sock_close(SOCKET sock, char *errbuf, int errbuflen) } /* - * gai_errstring() has some problems: + * gai_strerror() has some problems: * * 1) on Windows, Microsoft explicitly says it's not thread-safe; * 2) on UN*X, the Single UNIX Specification doesn't say it *is* @@ -527,52 +913,52 @@ get_gai_errstring(char *errbuf, int errbuflen, const char *prefix, int err, char hostport[PCAP_ERRBUF_SIZE]; if (hostname != NULL && portname != NULL) - pcap_snprintf(hostport, PCAP_ERRBUF_SIZE, "%s:%s", + snprintf(hostport, PCAP_ERRBUF_SIZE, "host and port %s:%s", hostname, portname); else if (hostname != NULL) - pcap_snprintf(hostport, PCAP_ERRBUF_SIZE, "%s", + snprintf(hostport, PCAP_ERRBUF_SIZE, "host %s", hostname); else if (portname != NULL) - pcap_snprintf(hostport, PCAP_ERRBUF_SIZE, ":%s", + snprintf(hostport, PCAP_ERRBUF_SIZE, "port %s", portname); else - pcap_snprintf(hostport, PCAP_ERRBUF_SIZE, "<no host or port!>"); + snprintf(hostport, PCAP_ERRBUF_SIZE, "<no host or port!>"); switch (err) { #ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sAddress family for %s not supported", prefix, hostport); break; #endif case EAI_AGAIN: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%s%s could not be resolved at this time", prefix, hostport); break; case EAI_BADFLAGS: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sThe ai_flags parameter for looking up %s had an invalid value", prefix, hostport); break; case EAI_FAIL: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sA non-recoverable error occurred when attempting to resolve %s", prefix, hostport); break; case EAI_FAMILY: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sThe address family for looking up %s was not recognized", prefix, hostport); break; case EAI_MEMORY: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sOut of memory trying to allocate storage when looking up %s", prefix, hostport); break; @@ -589,26 +975,26 @@ get_gai_errstring(char *errbuf, int errbuflen, const char *prefix, int err, */ #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME case EAI_NODATA: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sNo address associated with %s", prefix, hostport); break; #endif case EAI_NONAME: - pcap_snprintf(errbuf, errbuflen, - "%sThe host name %s couldn't be resolved", + snprintf(errbuf, errbuflen, + "%sThe %s couldn't be resolved", prefix, hostport); break; case EAI_SERVICE: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sThe service value specified when looking up %s as not recognized for the socket type", prefix, hostport); break; case EAI_SOCKTYPE: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sThe socket type specified when looking up %s as not recognized", prefix, hostport); break; @@ -618,15 +1004,15 @@ get_gai_errstring(char *errbuf, int errbuflen, const char *prefix, int err, /* * Assumed to be UN*X. */ - pcap_snprintf(errbuf, errbuflen, - "%sAn error occurred when looking up %s: %s", - prefix, hostport, pcap_strerror(errno)); + pcap_fmt_errmsg_for_errno(errbuf, errbuflen, errno, + "%sAn error occurred when looking up %s", + prefix, hostport); break; #endif #ifdef EAI_BADHINTS case EAI_BADHINTS: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sInvalid value for hints when looking up %s", prefix, hostport); break; @@ -634,7 +1020,7 @@ get_gai_errstring(char *errbuf, int errbuflen, const char *prefix, int err, #ifdef EAI_PROTOCOL case EAI_PROTOCOL: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sResolved protocol when looking up %s is unknown", prefix, hostport); break; @@ -642,14 +1028,14 @@ get_gai_errstring(char *errbuf, int errbuflen, const char *prefix, int err, #ifdef EAI_OVERFLOW case EAI_OVERFLOW: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sArgument buffer overflow when looking up %s", prefix, hostport); break; #endif default: - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "%sgetaddrinfo() error %d when looking up %s", prefix, err, hostport); break; @@ -699,13 +1085,58 @@ int sock_initaddress(const char *host, const char *port, { int retval; - retval = getaddrinfo(host, port, hints, addrinfo); + /* + * We allow both the host and port to be null, but getaddrinfo() + * is not guaranteed to do so; to handle that, if port is null, + * we provide "0" as the port number. + * + * This results in better error messages from get_gai_errstring(), + * as those messages won't talk about a problem with the port if + * no port was specified. + */ + retval = getaddrinfo(host, port == NULL ? "0" : port, hints, addrinfo); if (retval != 0) { if (errbuf) { - get_gai_errstring(errbuf, errbuflen, "", retval, - host, port); + if (host != NULL && port != NULL) { + /* + * Try with just a host, to distinguish + * between "host is bad" and "port is + * bad". + */ + int try_retval; + + try_retval = getaddrinfo(host, NULL, hints, + addrinfo); + if (try_retval == 0) { + /* + * Worked with just the host, + * so assume the problem is + * with the port. + * + * Free up the address info first. + */ + freeaddrinfo(*addrinfo); + get_gai_errstring(errbuf, errbuflen, + "", retval, NULL, port); + } else { + /* + * Didn't work with just the host, + * so assume the problem is + * with the host. + */ + get_gai_errstring(errbuf, errbuflen, + "", retval, host, NULL); + } + } else { + /* + * Either the host or port was null, so + * there's nothing to determine. + */ + get_gai_errstring(errbuf, errbuflen, "", + retval, host, port); + } } return -1; } @@ -726,7 +1157,7 @@ int sock_initaddress(const char *host, const char *port, ((*addrinfo)->ai_family != PF_INET6)) { if (errbuf) - pcap_snprintf(errbuf, errbuflen, "getaddrinfo(): socket type not supported"); + snprintf(errbuf, errbuflen, "getaddrinfo(): socket type not supported"); freeaddrinfo(*addrinfo); *addrinfo = NULL; return -1; @@ -739,7 +1170,7 @@ int sock_initaddress(const char *host, const char *port, (sock_ismcastaddr((*addrinfo)->ai_addr) == 0)) { if (errbuf) - pcap_snprintf(errbuf, errbuflen, "getaddrinfo(): multicast addresses are not valid when using TCP streams"); + snprintf(errbuf, errbuflen, "getaddrinfo(): multicast addresses are not valid when using TCP streams"); freeaddrinfo(*addrinfo); *addrinfo = NULL; return -1; @@ -775,7 +1206,7 @@ int sock_initaddress(const char *host, const char *port, * '-2' if we got one of those errors. * For errors, an error message is returned in the 'errbuf' variable. */ -int sock_send(SOCKET sock, const char *buffer, size_t size, +int sock_send(SOCKET sock, SSL *ssl _U_NOSSL_, const char *buffer, size_t size, char *errbuf, int errbuflen) { int remaining; @@ -785,7 +1216,7 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, { if (errbuf) { - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "Can't send more than %u bytes with sock_send", INT_MAX); } @@ -794,6 +1225,13 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, remaining = (int)size; do { +#ifdef HAVE_OPENSSL + if (ssl) return ssl_send(ssl, buffer, remaining, errbuf, errbuflen); +#endif + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + nsent = remaining; +#else #ifdef MSG_NOSIGNAL /* * Send with MSG_NOSIGNAL, so that we don't get SIGPIPE @@ -805,6 +1243,7 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, #else nsent = send(sock, buffer, remaining, 0); #endif +#endif //FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION if (nsent == -1) { @@ -828,7 +1267,8 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, */ return -2; } - sock_fmterror("send()", errcode, errbuf, errbuflen); + sock_fmterrmsg(errbuf, errbuflen, errcode, + "send() failed"); #else errcode = errno; if (errcode == ECONNRESET || errcode == EPIPE) @@ -840,7 +1280,8 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, */ return -2; } - sock_fmterror("send()", errcode, errbuf, errbuflen); + sock_fmterrmsg(errbuf, errbuflen, errcode, + "send() failed"); #endif return -1; } @@ -853,11 +1294,11 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, } /* - * \brief It copies the amount of data contained into 'buffer' into 'tempbuf'. + * \brief It copies the amount of data contained in 'data' into 'outbuf'. * and it checks for buffer overflows. * - * This function basically copies 'size' bytes of data contained into 'buffer' - * into 'tempbuf', starting at offset 'offset'. Before that, it checks that the + * This function basically copies 'size' bytes of data contained in 'data' + * into 'outbuf', starting at offset 'offset'. Before that, it checks that the * resulting buffer will not be larger than 'totsize'. Finally, it updates * the 'offset' variable in order to point to the first empty location of the buffer. * @@ -866,25 +1307,24 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, * 'offset' variable. This mode can be useful when the buffer already contains the * data (maybe because the producer writes directly into the target buffer), so * only the buffer overflow check has to be made. - * In this case, both 'buffer' and 'tempbuf' can be NULL values. + * In this case, both 'data' and 'outbuf' can be NULL values. * * This function is useful in case the userland application does not know immediately * all the data it has to write into the socket. This function provides a way to create * the "stream" step by step, appending the new data to the old one. Then, when all the * data has been bufferized, the application can call the sock_send() function. * - * \param buffer: a char pointer to a user-allocated buffer that keeps the data - * that has to be copied. + * \param data: a void pointer to the data that has to be copied. * * \param size: number of bytes that have to be copied. * - * \param tempbuf: user-allocated buffer (of size 'totsize') in which data + * \param outbuf: user-allocated buffer (of size 'totsize') into which data * has to be copied. * - * \param offset: an index into 'tempbuf' which keeps the location of its first + * \param offset: an index into 'outbuf' which keeps the location of its first * empty location. * - * \param totsize: total size of the buffer in which data is being copied. + * \param totsize: total size of the buffer into which data is being copied. * * \param checkonly: '1' if we do not want to copy data into the buffer and we * want just do a buffer ovreflow control, '0' if data has to be copied as well. @@ -897,7 +1337,7 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, * larger than 'errbuflen - 1' because the last char is reserved for the string terminator. * * \return '0' if everything is fine, '-1' if some errors occurred. The error message - * is returned in the 'errbuf' variable. When the function returns, 'tempbuf' will + * is returned in the 'errbuf' variable. When the function returns, 'outbuf' will * have the new string appended, and 'offset' will keep the length of that buffer. * In case of 'checkonly == 1', data is not copied, but 'offset' is updated in any case. * @@ -907,17 +1347,17 @@ int sock_send(SOCKET sock, const char *buffer, size_t size, * \warning In case of 'checkonly', be carefully to call this function *before* copying * the data into the buffer. Otherwise, the control about the buffer overflow is useless. */ -int sock_bufferize(const char *buffer, int size, char *tempbuf, int *offset, int totsize, int checkonly, char *errbuf, int errbuflen) +int sock_bufferize(const void *data, int size, char *outbuf, int *offset, int totsize, int checkonly, char *errbuf, int errbuflen) { if ((*offset + size) > totsize) { if (errbuf) - pcap_snprintf(errbuf, errbuflen, "Not enough space in the temporary send buffer."); + snprintf(errbuf, errbuflen, "Not enough space in the temporary send buffer."); return -1; } if (!checkonly) - memcpy(tempbuf + (*offset), buffer, size); + memcpy(outbuf + (*offset), data, size); (*offset) += size; @@ -970,9 +1410,10 @@ int sock_bufferize(const char *buffer, int size, char *tempbuf, int *offset, int * The error message is returned in the 'errbuf' variable. */ -int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, - char *errbuf, int errbuflen) +int sock_recv(SOCKET sock, SSL *ssl _U_NOSSL_, void *buffer, size_t size, + int flags, char *errbuf, int errbuflen) { + int recv_flags = 0; char *bufp = buffer; int remaining; ssize_t nread; @@ -985,13 +1426,16 @@ int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, { if (errbuf) { - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "Can't read more than %u bytes with sock_recv", INT_MAX); } return -1; } + if (flags & SOCK_MSG_PEEK) + recv_flags |= MSG_PEEK; + bufp = (char *) buffer; remaining = (int) size; @@ -1000,7 +1444,22 @@ int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, * Win32. */ for (;;) { - nread = recv(sock, bufp, remaining, 0); +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + nread = fuzz_recv(bufp, remaining); +#elif defined(HAVE_OPENSSL) + if (ssl) + { + /* + * XXX - what about MSG_PEEK? + */ + nread = ssl_recv(ssl, bufp, remaining, errbuf, errbuflen); + if (nread == -2) return -1; + } + else + nread = recv(sock, bufp, remaining, recv_flags); +#else + nread = recv(sock, bufp, remaining, recv_flags); +#endif if (nread == -1) { @@ -1008,7 +1467,7 @@ int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, if (errno == EINTR) return -3; #endif - sock_geterror("recv()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, "recv() failed"); return -1; } @@ -1024,7 +1483,7 @@ int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, */ if (errbuf) { - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "The other host terminated the connection."); } return -1; @@ -1058,7 +1517,7 @@ int sock_recv(SOCKET sock, void *buffer, size_t size, int flags, * * Returns the size of the datagram on success or -1 on error. */ -int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, +int sock_recv_dgram(SOCKET sock, SSL *ssl _U_NOSSL_, void *buffer, size_t size, char *errbuf, int errbuflen) { ssize_t nread; @@ -1075,20 +1534,29 @@ int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, { if (errbuf) { - pcap_snprintf(errbuf, errbuflen, + snprintf(errbuf, errbuflen, "Can't read more than %u bytes with sock_recv_dgram", INT_MAX); } return -1; } +#ifdef HAVE_OPENSSL + // TODO: DTLS + if (ssl) + { + snprintf(errbuf, errbuflen, "DTLS not implemented yet"); + return -1; + } +#endif + /* * This should be a datagram socket, so we should get the * entire datagram in one recv() or recvmsg() call, and * don't need to loop. */ #ifdef _WIN32 - nread = recv(sock, buffer, size, 0); + nread = recv(sock, buffer, (int)size, 0); if (nread == SOCKET_ERROR) { /* @@ -1104,7 +1572,8 @@ int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, * supplied to us, the excess data is discarded, * and we'll report an error. */ - sock_geterror("recv()", errbuf, errbuflen); + sock_fmterrmsg(errbuf, errbuflen, sock_geterrcode(), + "recv() failed"); return -1; } #else /* _WIN32 */ @@ -1132,12 +1601,16 @@ int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, #ifdef HAVE_STRUCT_MSGHDR_MSG_FLAGS message.msg_flags = 0; #endif +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + nread = fuzz_recv(buffer, size); +#else nread = recvmsg(sock, &message, 0); +#endif if (nread == -1) { if (errno == EINTR) return -3; - sock_geterror("recv()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, "recv() failed"); return -1; } #ifdef HAVE_STRUCT_MSGHDR_MSG_FLAGS @@ -1154,7 +1627,7 @@ int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, * Report this as an error, as the Microsoft documentation * implies we'd do in a similar case on Windows. */ - pcap_snprintf(errbuf, errbuflen, "recv(): Message too long"); + snprintf(errbuf, errbuflen, "recv(): Message too long"); return -1; } #endif /* HAVE_STRUCT_MSGHDR_MSG_FLAGS */ @@ -1192,7 +1665,7 @@ int sock_recv_dgram(SOCKET sock, void *buffer, size_t size, * \return '0' if everything is fine, '-1' if some errors occurred. * The error message is returned in the 'errbuf' variable. */ -int sock_discard(SOCKET sock, int size, char *errbuf, int errbuflen) +int sock_discard(SOCKET sock, SSL *ssl, int size, char *errbuf, int errbuflen) { #define TEMP_BUF_SIZE 32768 @@ -1208,7 +1681,7 @@ int sock_discard(SOCKET sock, int size, char *errbuf, int errbuflen) */ while (size > TEMP_BUF_SIZE) { - if (sock_recv(sock, buffer, TEMP_BUF_SIZE, SOCK_RECEIVEALL_YES, errbuf, errbuflen) == -1) + if (sock_recv(sock, ssl, buffer, TEMP_BUF_SIZE, SOCK_RECEIVEALL_YES, errbuf, errbuflen) == -1) return -1; size -= TEMP_BUF_SIZE; @@ -1220,7 +1693,7 @@ int sock_discard(SOCKET sock, int size, char *errbuf, int errbuflen) */ if (size) { - if (sock_recv(sock, buffer, size, SOCK_RECEIVEALL_YES, errbuf, errbuflen) == -1) + if (sock_recv(sock, ssl, buffer, size, SOCK_RECEIVEALL_YES, errbuf, errbuflen) == -1) return -1; } @@ -1273,7 +1746,8 @@ int sock_check_hostlist(char *hostlist, const char *sep, struct sockaddr_storage temphostlist = strdup(hostlist); if (temphostlist == NULL) { - sock_geterror("sock_check_hostlist(), malloc() failed", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, + "sock_check_hostlist(), malloc() failed"); return -2; } @@ -1358,7 +1832,7 @@ int sock_check_hostlist(char *hostlist, const char *sep, struct sockaddr_storage * the host wasn't in the list. */ if (errbuf) - pcap_snprintf(errbuf, errbuflen, "The host is not in the allowed host list. Connection refused."); + snprintf(errbuf, errbuflen, "The host is not in the allowed host list. Connection refused."); return -1; } } @@ -1459,7 +1933,7 @@ int sock_getmyinfo(SOCKET sock, char *address, int addrlen, char *port, int port if (getsockname(sock, (struct sockaddr *) &mysockaddr, &sockaddrlen) == -1) { - sock_geterror("getsockname()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, "getsockname() failed"); return 0; } @@ -1515,7 +1989,7 @@ int sock_getmyinfo(SOCKET sock, char *address, int addrlen, char *port, int port * and 'port'. * In any case, the returned strings are '0' terminated. */ -int sock_getascii_addrport(const struct sockaddr_storage *sockaddr, char *address, int addrlen, char *port, int portlen, int flags, char *errbuf, int errbuflen) +int sock_getascii_addrport(const struct sockaddr_storage *sockaddr, char *address, int addrlen, char *port, int portlen, int flags, char *errbuf, size_t errbuflen) { socklen_t sockaddrlen; int retval; /* Variable that keeps the return value; */ @@ -1547,7 +2021,8 @@ int sock_getascii_addrport(const struct sockaddr_storage *sockaddr, char *addres /* If the user wants to receive an error message */ if (errbuf) { - sock_geterror("getnameinfo()", errbuf, errbuflen); + sock_geterrmsg(errbuf, errbuflen, + "getnameinfo() failed"); errbuf[errbuflen - 1] = 0; } @@ -1628,7 +2103,7 @@ int sock_present2network(const char *address, struct sockaddr_storage *sockaddr, freeaddrinfo(addrinfo); if (errbuf) - pcap_snprintf(errbuf, errbuflen, "More than one socket requested; using the first one returned"); + snprintf(errbuf, errbuflen, "More than one socket requested; using the first one returned"); return -2; } |