From 617daf37b618ae2bf0d00b877bf8df5f99de7f5c Mon Sep 17 00:00:00 2001 From: Selvam Krishnamoorthy Date: Mon, 27 Mar 2017 15:30:16 +0530 Subject: [PATCH 1/4] Add hostname validation feature --- src/Makefile.am | 12 +- src/libnopoll.def | 7 ++ src/nopoll.h | 1 + src/nopoll_conn.c | 23 ++++ src/nopoll_conn_opts.c | 20 +++ src/nopoll_conn_opts.h | 2 + src/nopoll_hostcheck.c | 124 ++++++++++++++++++ src/nopoll_hostcheck.h | 27 ++++ src/nopoll_hostname_validation.c | 151 ++++++++++++++++++++++ src/nopoll_hostname_validation.h | 57 +++++++++ src/nopoll_inet_pton.c | 207 +++++++++++++++++++++++++++++++ src/nopoll_inet_pton.h | 29 +++++ src/nopoll_private.h | 1 + src/nopoll_strcase.c | 61 +++++++++ src/nopoll_strcase.h | 33 +++++ test/hostname-check.pem | 81 ++++++++++++ test/nopoll-regression-client.c | 200 ++++++++++++++++++++++++++++- 17 files changed, 1028 insertions(+), 8 deletions(-) create mode 100644 src/nopoll_hostcheck.c create mode 100644 src/nopoll_hostcheck.h create mode 100644 src/nopoll_hostname_validation.c create mode 100644 src/nopoll_hostname_validation.h create mode 100644 src/nopoll_inet_pton.c create mode 100644 src/nopoll_inet_pton.h create mode 100644 src/nopoll_strcase.c create mode 100644 src/nopoll_strcase.h create mode 100644 test/hostname-check.pem diff --git a/src/Makefile.am b/src/Makefile.am index abfbeab..7890542 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,11 @@ libnopoll_la_SOURCES = \ nopoll_io.c \ nopoll_msg.c \ nopoll_win32.c \ - nopoll_conn_opts.c + nopoll_conn_opts.c \ + nopoll_hostname_validation.c \ + nopoll_inet_pton.c \ + nopoll_strcase.c \ + nopoll_hostcheck.c libnopollinclude_HEADERS = \ nopoll.h \ @@ -39,7 +43,11 @@ libnopollinclude_HEADERS = \ nopoll_io.h \ nopoll_msg.h \ nopoll_win32.h \ - nopoll_conn_opts.h + nopoll_conn_opts.h \ + nopoll_hostname_validation.h \ + nopoll_inet_pton.h \ + nopoll_strcase.h \ + nopoll_hostcheck.h libnopoll_la_LDFLAGS = -no-undefined -export-symbols-regex '^(nopoll|__nopoll|_nopoll).*' diff --git a/src/libnopoll.def b/src/libnopoll.def index e461a52..066a1c6 100644 --- a/src/libnopoll.def +++ b/src/libnopoll.def @@ -28,6 +28,7 @@ __nopoll_tls_was_init nopoll_base64_decode nopoll_base64_encode nopoll_calloc +nopoll_cert_hostcheck nopoll_cleanup_library nopoll_cmp nopoll_conn_accept @@ -83,6 +84,7 @@ nopoll_conn_opts_set_reuse nopoll_conn_opts_set_ssl_certs nopoll_conn_opts_set_ssl_protocol nopoll_conn_opts_skip_origin_check +nopoll_conn_opts_ssl_host_verify nopoll_conn_opts_ssl_peer_verify nopoll_conn_opts_unref nopoll_conn_pending_write_bytes @@ -143,6 +145,7 @@ nopoll_get_16bit nopoll_get_32bit nopoll_get_8bit nopoll_get_bit +nopoll_inet_pton nopoll_int2bin nopoll_int2bin_print nopoll_io_get_engine @@ -193,16 +196,20 @@ nopoll_mutex_lock nopoll_mutex_unlock nopoll_ncmp nopoll_nonce +nopoll_raw_toupper nopoll_realloc nopoll_set_16bit nopoll_set_32bit nopoll_set_bit nopoll_show_byte nopoll_sleep +nopoll_strcasecompare nopoll_strdup nopoll_strdup_printf nopoll_strdup_printfv +nopoll_strncasecompare nopoll_thread_handlers nopoll_timeval_substract nopoll_trim +nopoll_validate_hostname nopoll_vprintf_len diff --git a/src/nopoll.h b/src/nopoll.h index c1e6244..03f7238 100644 --- a/src/nopoll.h +++ b/src/nopoll.h @@ -57,6 +57,7 @@ BEGIN_C_DECLS #include #include #include +#include /** * \addtogroup nopoll_module diff --git a/src/nopoll_conn.c b/src/nopoll_conn.c index 0e5c146..35508bb 100644 --- a/src/nopoll_conn.c +++ b/src/nopoll_conn.c @@ -53,6 +53,7 @@ # include #endif +#include /** * @brief Allows to enable/disable non-blocking/blocking behavior on @@ -983,6 +984,28 @@ noPollConn * __nopoll_conn_new_common (noPollCtx * ctx, return conn; } + + /* Check for opts to verify hostname validation during conn handshake */ + if (options == NULL || options->host_verify) + { + HostnameValidationResult status = Error; + /* hostname should be validated without http/https, so pass conn->host_name */ + status = nopoll_validate_hostname(conn->host_name,server_cert); + if( status == MatchFound ) + { + nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Hostname validation SUCCESS, done as part of client \n"); + } + else + { + nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL,"Hostname validation FAILED with errno %d \n",status); + /* release connection options */ + nopoll_free (content); + nopoll_conn_shutdown (conn); + __nopoll_conn_opts_release_if_needed (options); + X509_free (server_cert); + return NULL; + } + } X509_free (server_cert); /* call to check post ssl checks after SSL finalization */ diff --git a/src/nopoll_conn_opts.c b/src/nopoll_conn_opts.c index d696f21..21ce7c4 100644 --- a/src/nopoll_conn_opts.c +++ b/src/nopoll_conn_opts.c @@ -70,6 +70,8 @@ noPollConnOpts * nopoll_conn_opts_new (void) /* by default, disable ssl peer verification */ result->disable_ssl_verify = nopoll_true; + /* by default, enable hostname validation */ + result->host_verify = nopoll_true; return result; } @@ -169,6 +171,24 @@ void nopoll_conn_opts_ssl_peer_verify (noPollConnOpts * opts, nopoll_bool verify return; } +/** + * @brief Allows to enable hostname validation + * + * @param opts The connection option to configure. + * + * @param hostVerify nopoll_true to enable hostname validation + * otherwise, nopoll_false should be used. By default hostname validation + * is enabled. + */ + +void nopoll_conn_opts_ssl_host_verify (noPollConnOpts * opts, nopoll_bool hostVerify) +{ + if (opts == NULL) + return; + opts->host_verify = hostVerify; + return; +} + /** * @brief Allows to set Cookie header content to be sent during the * connection handshake. If configured and the remote side server is a diff --git a/src/nopoll_conn_opts.h b/src/nopoll_conn_opts.h index bddc410..227473b 100644 --- a/src/nopoll_conn_opts.h +++ b/src/nopoll_conn_opts.h @@ -70,6 +70,8 @@ void nopoll_conn_opts_set_interface (noPollConnOpts * opts, const char * _int void nopoll_conn_opts_set_extra_headers (noPollConnOpts * opts, const char * extra_headers); void nopoll_conn_opts_free (noPollConnOpts * opts); +/* hostname validation */ +void nopoll_conn_opts_ssl_host_verify (noPollConnOpts * opts, nopoll_bool verify); /** internal API **/ void __nopoll_conn_opts_release_if_needed (noPollConnOpts * options); diff --git a/src/nopoll_hostcheck.c b/src/nopoll_hostcheck.c new file mode 100644 index 0000000..140b62f --- /dev/null +++ b/src/nopoll_hostcheck.c @@ -0,0 +1,124 @@ +/*************************************************************************** + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include + +static int hostmatch(char *hostname, char *pattern); + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * https://tools.ietf.org/html/rfc6125#section-6.4.3 + * + * In addition: ignore trailing dots in the host names and wildcards, so that + * the names are used normalized. This is what the browsers do. + * + * Do not allow wildcard matching on IP numbers. There are apparently + * certificates being used with an IP address in the CN field, thus making no + * apparent distinction between a name and an IP. We need to detect the use of + * an IP address and not wildcard match on such names. + * + * NOTE: hostmatch() gets called with copied buffers so that it can modify the + * contents at will. + */ + +static int hostmatch(char *hostname, char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + int wildcard_enabled; + size_t prefixlen, suffixlen; + struct in_addr ignored; + struct sockaddr_in6 si6; + + /* normalize pattern and hostname by stripping off trailing dots */ + size_t len = strlen(hostname); + if(hostname[len-1]=='.') + hostname[len-1]=0; + len = strlen(pattern); + if(pattern[len-1]=='.') + pattern[len-1]=0; + + pattern_wildcard = strchr(pattern, '*'); + if(pattern_wildcard == NULL) + return nopoll_strcasecompare(pattern, hostname) ? + NOPOLL_HOST_MATCH : NOPOLL_HOST_NOMATCH; + + /* detect IP address as hostname and fail the match if so */ + if(nopoll_inet_pton(AF_INET, hostname, &ignored) > 0) + return NOPOLL_HOST_NOMATCH; + else if(nopoll_inet_pton(AF_INET6, hostname, &si6.sin6_addr) > 0) + return NOPOLL_HOST_NOMATCH; + + /* We require at least 2 dots in pattern to avoid too wide wildcard + match. */ + wildcard_enabled = 1; + pattern_label_end = strchr(pattern, '.'); + if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || + pattern_wildcard > pattern_label_end || + nopoll_strncasecompare(pattern, "xn--", 4)) { + wildcard_enabled = 0; + } + if(!wildcard_enabled) + return nopoll_strcasecompare(pattern, hostname) ? + NOPOLL_HOST_MATCH : NOPOLL_HOST_NOMATCH; + + hostname_label_end = strchr(hostname, '.'); + if(hostname_label_end == NULL || + !nopoll_strcasecompare(pattern_label_end, hostname_label_end)) + return NOPOLL_HOST_NOMATCH; + + /* The wildcard must match at least one character, so the left-most + label of the hostname is at least as large as the left-most label + of the pattern. */ + if(hostname_label_end - hostname < pattern_label_end - pattern) + return NOPOLL_HOST_NOMATCH; + + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard+1); + return nopoll_strncasecompare(pattern, hostname, prefixlen) && + nopoll_strncasecompare(pattern_wildcard+1, hostname_label_end - suffixlen, + suffixlen) ? + NOPOLL_HOST_MATCH : NOPOLL_HOST_NOMATCH; +} + +int nopoll_cert_hostcheck(const char *match_pattern, const char *hostname) +{ + char *matchp; + char *hostp; + int res = 0; + if(!match_pattern || !*match_pattern || + !hostname || !*hostname) + ; + else { + matchp = strdup(match_pattern); + if(matchp) { + hostp = strdup(hostname); + if(hostp) { + if(hostmatch(hostp, matchp) == NOPOLL_HOST_MATCH) + res= 1; + free(hostp); + } + free(matchp); + } + } + + return res; +} + diff --git a/src/nopoll_hostcheck.h b/src/nopoll_hostcheck.h new file mode 100644 index 0000000..52f9b1f --- /dev/null +++ b/src/nopoll_hostcheck.h @@ -0,0 +1,27 @@ +/*************************************************************************** + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef __NOPOLL_HOSTCHECK_H__ +#define __NOPOLL_HOSTCHECK_H__ + +#define NOPOLL_HOST_NOMATCH 0 +#define NOPOLL_HOST_MATCH 1 + +int nopoll_cert_hostcheck(const char *match_pattern, const char *hostname); + +#endif /* __NOPOLL_HOSTCHECK_H__ */ + diff --git a/src/nopoll_hostname_validation.c b/src/nopoll_hostname_validation.c new file mode 100644 index 0000000..bf4f0d9 --- /dev/null +++ b/src/nopoll_hostname_validation.c @@ -0,0 +1,151 @@ +/* +*Copyright (C) 2012, iSEC Partners. + +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +* of the Software, and to permit persons to whom the Software is furnished to do +* so, subject to the following conditions: + +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#include + +static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert); +static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert); + +/** +* Tries to find a match for hostname in the certificate's Common Name field. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if the Common Name had a NUL character embedded in it. +* Returns Error if the Common Name could not be extracted. +*/ +static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) +{ + int common_name_loc = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + /* Find the position of the CN field in the Subject field of the certificate*/ + common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1); + if (common_name_loc < 0) { + return Error; + } + + /* Extract the CN field*/ + common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc); + if (common_name_entry == NULL) { + return Error; + } + + /* Convert the CN field to a C string*/ + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) { + return Error; + } + common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + /* Make sure there isn't an embedded NUL character in the CN*/ + if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) { + return MalformedCertificate; + } + + /* Compare expected hostname with the CN*/ + if (nopoll_cert_hostcheck(common_name_str, hostname) == NOPOLL_HOST_MATCH) { + return MatchFound; + } + else { + return MatchNotFound; + } +} + + +/** +* Tries to find a match for hostname in the certificate's Subject Alternative Name extension. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it. +* Returns NoSANPresent if the SAN extension was not present in the certificate. +*/ +static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) { + HostnameValidationResult result = MatchNotFound; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + /* Try to extract the names within the SAN extension from the certificate*/ + san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return NoSANPresent; + } + san_names_nb = sk_GENERAL_NAME_num(san_names); + + /* Check each name within the extension*/ + for (i=0; itype == GEN_DNS) { + /* Current name is a DNS name, let's check it*/ + char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + + /* Make sure there isn't an embedded NUL character in the DNS name*/ + if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { + result = MalformedCertificate; + break; + } + else { /* Compare expected hostname with the DNS name*/ + if (nopoll_cert_hostcheck(dns_name, hostname) == NOPOLL_HOST_MATCH) { + result = MatchFound; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return result; +} + + +/** +* Validates the server's identity by looking for the expected hostname in the +* server's certificate. As described in RFC 6125, it first tries to find a match +* in the Subject Alternative Name extension. If the extension is not present in +* the certificate, it checks the Common Name instead. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it. +* Returns Error if there was an error. +*/ +HostnameValidationResult nopoll_validate_hostname(const char *hostname, const X509 *server_cert) { + HostnameValidationResult result; + + if((hostname == NULL) || (server_cert == NULL)) + return Error; + + /* First try the Subject Alternative Names extension*/ + result = matches_subject_alternative_name(hostname, server_cert); + if (result == NoSANPresent) { + /* Extension was not found: try the Common Name*/ + result = matches_common_name(hostname, server_cert); + } + + return result; +} diff --git a/src/nopoll_hostname_validation.h b/src/nopoll_hostname_validation.h new file mode 100644 index 0000000..afd0555 --- /dev/null +++ b/src/nopoll_hostname_validation.h @@ -0,0 +1,57 @@ +/* +*Copyright (C) 2012, iSEC Partners. + +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the "Software"), to deal in +* the Software without restriction, including without limitation the rights to +* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +* of the Software, and to permit persons to whom the Software is furnished to do +* so, subject to the following conditions: + +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +*/ + +#ifndef __NOPOLL_HOSTNAME_VALIDATION_H__ +#define __NOPOLL_HOSTNAME_VALIDATION_H__ + +#include +#include + +BEGIN_C_DECLS + +#include +#include +#include + +typedef enum { + MatchFound, + MatchNotFound, + NoSANPresent, + MalformedCertificate, + Error +} HostnameValidationResult; + +/** +* Validates the server's identity by looking for the expected hostname in the +* server's certificate. As described in RFC 6125, it first tries to find a match +* in the Subject Alternative Name extension. If the extension is not present in +* the certificate, it checks the Common Name instead. +* +* Returns MatchFound if a match was found. +* Returns MatchNotFound if no matches were found. +* Returns MalformedCertificate if any of the hostnames had a NULL character embedded in it. +* Returns Error if there was an error. +*/ +HostnameValidationResult nopoll_validate_hostname(const char *hostname, const X509 *server_cert); +END_C_DECLS +#endif /* __NOPOLL_HOSTNAME_VALIDATION_H__ */ diff --git a/src/nopoll_inet_pton.c b/src/nopoll_inet_pton.c new file mode 100644 index 0000000..e5c4abf --- /dev/null +++ b/src/nopoll_inet_pton.c @@ -0,0 +1,207 @@ +/* This is from the BIND 4.9.4 release, modified to compile by itself */ + +/* Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * notice: + * On Windows we store the error in the thread errno, not + * in the winsock error code. This is to avoid losing the + * actual last winsock error. So use macro ERRNO to fetch the + * errno this function sets when returning (-1), not SOCKERRNO. + * author: + * Paul Vixie, 1996. + */ +int nopoll_inet_pton(int af, const char *src, void *dst) +{ + switch(af) { + case AF_INET: + return (inet_pton4(src, (unsigned char *)dst)); + case AF_INET6: + return (inet_pton6(src, (unsigned char *)dst)); + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + tp = tmp; + *tp = 0; + while((ch = *src++) != '\0') { + const char *pch; + + pch = strchr(digits, ch); + if(pch) { + unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + + if(saw_digit && *tp == 0) + return (0); + if(val > 255) + return (0); + *tp = (unsigned char)val; + if(! saw_digit) { + if(++octets > 4) + return (0); + saw_digit = 1; + } + } + else if(ch == '.' && saw_digit) { + if(octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } + else + return (0); + } + if(octets < 4) + return (0); + memcpy(dst, tmp, INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + size_t val; + + memset((tp = tmp), 0, IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if(*src == ':') + if(*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while((ch = *src++) != '\0') { + const char *pch; + + pch = strchr((xdigits = xdigits_l), ch); + if(!pch) + pch = strchr((xdigits = xdigits_u), ch); + if(pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if(++saw_xdigit > 4) + return (0); + continue; + } + if(ch == ':') { + curtok = src; + if(!saw_xdigit) { + if(colonp) + return (0); + colonp = tp; + continue; + } + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + saw_xdigit = 0; + val = 0; + continue; + } + if(ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if(saw_xdigit) { + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) ((val >> 8) & 0xff); + *tp++ = (unsigned char) (val & 0xff); + } + if(colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const ssize_t n = tp - colonp; + ssize_t i; + + if(tp == endp) + return (0); + for(i = 1; i <= n; i++) { + *(endp - i) = *(colonp + n - i); + *(colonp + n - i) = 0; + } + tp = endp; + } + if(tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} diff --git a/src/nopoll_inet_pton.h b/src/nopoll_inet_pton.h new file mode 100644 index 0000000..1b7f7ca --- /dev/null +++ b/src/nopoll_inet_pton.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef __NOPOLL_INET_PTON_H__ +#define __NOPOLL_INET_PTON_H__ + +#include + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +int nopoll_inet_pton(int, const char *, void *); + +#endif /* __NOPOLL_INET_PTON_H__ */ + diff --git a/src/nopoll_private.h b/src/nopoll_private.h index 59b2f8b..189f480 100644 --- a/src/nopoll_private.h +++ b/src/nopoll_private.h @@ -397,6 +397,7 @@ struct _noPollConnOpts { char * ca_certificate; nopoll_bool disable_ssl_verify; + nopoll_bool host_verify; /* cookie support */ char * cookie; diff --git a/src/nopoll_strcase.c b/src/nopoll_strcase.c new file mode 100644 index 0000000..8bdf2e3 --- /dev/null +++ b/src/nopoll_strcase.c @@ -0,0 +1,61 @@ +/*************************************************************************** + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + + +#include + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +char nopoll_raw_toupper(char in) +{ + if(in >= 'a' && in <= 'z') + return (char)('A' + in - 'a'); + return in; +} + +int nopoll_strcasecompare(const char *first, const char *second) +{ + while(*first && *second) { + if(nopoll_raw_toupper(*first) != nopoll_raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (nopoll_raw_toupper(*first) == nopoll_raw_toupper(*second)); +} + + +int nopoll_strncasecompare(const char *first, const char *second, size_t max) +{ + while(*first && *second && max) { + if(nopoll_raw_toupper(*first) != nopoll_raw_toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return nopoll_raw_toupper(*first) == nopoll_raw_toupper(*second); +} + diff --git a/src/nopoll_strcase.h b/src/nopoll_strcase.h new file mode 100644 index 0000000..15ec7ca --- /dev/null +++ b/src/nopoll_strcase.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * + * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef __NOPOLL_STRCASE_H__ +#define __NOPOLL_STRCASE_H__ + +/* + * Only "raw" case insensitive strings. This is meant to be locale independent + * and only compare strings we know are safe for this. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ +int nopoll_strcasecompare(const char *first, const char *second); +int nopoll_strncasecompare(const char *first, const char *second, size_t max); + +char nopoll_raw_toupper(char in); + +#endif /* __NOPOLL_STRCASE_H__ */ diff --git a/test/hostname-check.pem b/test/hostname-check.pem new file mode 100644 index 0000000..d21938f --- /dev/null +++ b/test/hostname-check.pem @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIIFATCCAumgAwIBAgIJAN77SMdujDSpMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV +BAMMDCoubm9wb2xsLmNvbTAeFw0xNzAzMjQxNDQxMjZaFw0yNzAzMjIxNDQxMjZa +MBcxFTATBgNVBAMMDCoubm9wb2xsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALWiCeEfLLyUxJkGovn7irvWz0c/HkhLwyX5mMChMNHwQg0GGBry +f/swSYv5i1KSczZwuc8X0hq48oRAd5LgeXGpnb9CDvinVJ/iUPMSaBZPXMgwagUE +1vD8MoLrjdqlpOtaI8UZs1K9POM5voJR3BDrOUcgPEwpqJBdtJ8bOMGcyzTm6G2/ +/OKsxypk1Qlbjw/ET/CDw2BoFMZl/rZmqJ8npNkA91QeYxhrccm9UKnez+isr4Xv +nCfQws1CkvFVXdTMFiSs7x9wRW6Q7sxTKsNzwuDxJuzXkYoRli98CTHmz+nJEEfa +Q5apZmoKUBx4c/bUaVXP5SSaL7TGnIXB9A61VMnThgAAnYLrgWsri2CPz3VZyFNV +FRUegTMxfzPSyMxPCd/eheyfsOlv9A7pjVtyujL981tCCUWVMarMNQCZ4AVa6qAi +5lHtDeg5dT3m4N/URJBA2lLTlmbTjciOPNnxdAAeZah/XJx++Z7QFnsRN247gZXA +L/atb7n67XlpM8b65jIiSusOEUCeAopqAnlf3yY9HYmmXQBLZZPZ8lV7Dm/CAi7f +kWNpmVkWJxlkjdV7NMWmuPwRmxQLVFz1E6NNpodsJ34WulhyokKEkDU5M+VSIC0V +76V54ROLUyxPkKcF0Fr1uMsyrIxF64ejKj7ZLn7tZh+h7KSjWYj0g5M/AgMBAAGj +UDBOMB0GA1UdDgQWBBRDwb0UAHUdZ3N43ul3ynL7ThErlTAfBgNVHSMEGDAWgBRD +wb0UAHUdZ3N43ul3ynL7ThErlTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4ICAQCbOGZfRb/DEcO15LYDViQoPB14vcpPnAilXMNERtc+VtdJf0znv7PAWEwH +ffqws8yhaWAecNfQ8y2CqVZgZTZKQwvKi6UP0cY+aXl/qyRWto/njVGdkiK21XuB +vo5xD5L023I++MeZH106V/S7b/Zm7na38yVhfObzDU0GnWm8Ozhk3CoIK7Zb0Edt +MnfIFdV1HQwb35pvyON4/1KYPACI7OUmMSPCW+icG5WUQq1vBa+lf5feg2POmN2C +m2HZJUK4JMO6FzdPNrWXO+3A8Q24xuHfDhoVCY2Uby+kVonzIt0GX3nBMCDGQxil +lefrFIG+0BGwP2+wTuiF65C1Wa1PPW79O1RI7zQaKXgoci23JRrBrVQt0zBJP6ji +KZES6IsSWtE2tMlxkVzYVBjb3Hb6Xog4rhOzYPEVBadQXhys9qCAGPIRJa7Kl9L8 +GYAUxJ+Sn8E24GoL1kYoPQY/ObJp33CJOH7Q8VwMLVxOn4ekWwGF4LmXgIonCa6L +0rHD8KqZz5UQ+EX/04pQlvn1fUs24ndKyadar4BCFFYIvFETw0gPlUCsE4bpnOBj +N/Q5HmMDdm1I25q1EtyMTHiWGDkNvWU9bRygZFck5VM1krdUMDqaRQ4P/UdysSlm +f5PJ4S+rJS4dyGgGmVmDjGxn0c8m8LbzevkmmsSrOJMp9arq1Q== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +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 +P1TqAkHWaLYqVon5fVweJgAPVLPZnppV +-----END PRIVATE KEY----- diff --git a/test/nopoll-regression-client.c b/test/nopoll-regression-client.c index 67b9f1a..316749e 100644 --- a/test/nopoll-regression-client.c +++ b/test/nopoll-regression-client.c @@ -216,6 +216,122 @@ nopoll_bool test_01_masking (void) { return nopoll_true; } + +nopoll_bool test_01_hostname_check (void) { + + /*success case*/ + if(! nopoll_cert_hostcheck("www.example.com", "www.example.com")) + { + printf ("ERROR (1): expected to match hostname validation www.example.com..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("*.example.com", "www.example.com")) + { + printf ("ERROR (1): expected to match hostname validation *.example.com..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("xxx*.example.com", "xxxwww.example.com")) + { + printf ("ERROR (1): expected to match hostname validation xxx*.example.com..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("f*.example.com", "foo.example.com")) + { + printf ("ERROR (1): expected to match hostname validation f*.example.com..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("192.168.0.0", "192.168.0.0")) + { + printf ("ERROR (1): expected to match hostname validation 192.168.0.0..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("example.com","example.com")) + { + printf ("ERROR (1): expected to match hostname validation example.com..\n"); + return nopoll_false; + } + if(! nopoll_cert_hostcheck("fe80::3285:a9ff:fe46:b619","fe80::3285:a9ff:fe46:b619")) + { + printf ("ERROR (1): expected to match hostname validation fe80::3285:a9ff:fe46:b619..\n"); + return nopoll_false; + } + + /*Failure case*/ + + if(nopoll_cert_hostcheck("xxx.example.com", "www.example.com")) + { + printf ("ERROR (1): expected not to match hostname validation xxx.example.com\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*", "www.example.com")) + { + printf ("ERROR (1): expected not to match hostname validation for * with www.example.com\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.*.com", "www.example.com")) + { + printf ("ERROR (1): expected to not match hostname validation *.*.com\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.example.com", "baa.foo.example.com")) + { + printf ("ERROR (1): expected to not match hostname validation *.example.com with baa.foo.example.com..\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("f*.example.com", "baa.example.com")) + { + printf ("ERROR (1): expected to not match hostname validation f*.example.com with baa.example.com\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.com", "example.com")) + { + printf ("ERROR (1): expected to not match hostname validation *.com with example.com.\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*fail.com", "example.com")) + { + printf ("ERROR (1): expected to not match hostname validation for *fail.com with example.com\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.example.", "www.example.")) + { + printf ("ERROR (1): expected to not match hostname validation for *.example. with www.example.\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.example.", "www.example")) + { + printf ("ERROR (1): expected to not match hostname validation for *.example. with www.example\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("", "www")) + { + printf ("ERROR (1): expected to not match hostname validation NULL with www \n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*", "www")) + { + printf ("ERROR (1): expected to not match hostname validation * with www\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*::3285:a9ff:fe46:b619","fe80::3285:a9ff:fe46:b619")) + { + printf ("ERROR (1): expected to not match hostname validation *::3285:a9ff:fe46:b619 with fe80::3285:a9ff:fe46:b619 \n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("*.168.0.0", "192.168.0.0")) + { + printf ("ERROR (1): expected to not match hostname validation *.168.0.0 with 192.168.0.0\n"); + return nopoll_false; + } + if(nopoll_cert_hostcheck("www.example.com", "192.168.0.0")) + { + printf ("ERROR (1): expected to not match hostname validation for www.example.com with 192.168.0.0\n"); + return nopoll_false; + } + return nopoll_true; +} + + nopoll_bool test_01 (void) { noPollCtx * ctx; noPollConn * conn; @@ -981,6 +1097,50 @@ nopoll_bool test_05 (void) { return nopoll_true; } + +nopoll_bool test_05_hostname_validation (void) { + + char * fileName = "hostname-check.pem"; + char *hostname_valid = "test.nopoll.com"; + char *hostname_invalid = "invalid.com"; + FILE *fp = NULL; + + #if defined(NOPOLL_OS_WIN32) + fp = fopen (fileName, "rb"); + #else + fp = fopen (fileName, "r"); + #endif + + if(!fp) + { + printf("unable to open cert file for hostname validation: %s\n", fileName); + return nopoll_false; + } + + X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL); + if(!cert) + { + printf("unable to parse certificate for hostname validation : %s\n", fileName); + fclose(fp); + return nopoll_false; + } + + if(nopoll_validate_hostname(hostname_valid, cert)) + { + printf("hostname %s doesn't match with dnsname \n",hostname_valid); + return nopoll_false; + } + + if(!nopoll_validate_hostname(hostname_invalid, cert)) + { + printf("hostname %s matched with dnsname \n",hostname_invalid); + return nopoll_false; + } + X509_free(cert); + fclose(fp); + return nopoll_true; +} + nopoll_bool test_06 (void) { noPollCtx * ctx; @@ -993,6 +1153,7 @@ nopoll_bool test_06 (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* call to create a connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); @@ -1041,7 +1202,8 @@ nopoll_bool test_06a (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); + /* call to create a connection */ conn = nopoll_conn_tls_new6 (ctx, opts, "::1", "2235", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -1089,6 +1251,7 @@ nopoll_bool test_07 (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* call to create a connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); @@ -1780,6 +1943,7 @@ nopoll_bool test_18 (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* call to create a connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); @@ -1818,6 +1982,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_SSLV23); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1236", NULL, NULL, NULL, NULL); @@ -1841,6 +2006,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_SSLV23); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); @@ -1862,6 +2028,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_SSLV3); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ printf ("Test 19: checking SSLv3 with TLSv1..\n"); @@ -1883,6 +2050,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_TLSV1); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); @@ -1906,6 +2074,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_TLSV1_1); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1238", NULL, NULL, NULL, NULL); @@ -1927,6 +2096,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_TLSV1_1); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new6 (ctx, opts, "::1", "2238", NULL, NULL, NULL, NULL); @@ -1951,6 +2121,7 @@ nopoll_bool test_19 (void) { /* create options */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_ssl_protocol (opts, NOPOLL_METHOD_TLSV1_2); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1240", NULL, NULL, NULL, NULL); @@ -2036,6 +2207,7 @@ nopoll_bool test_21 (void) { NULL, /* ca certificate */ "root.pem"); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1239", NULL, NULL, NULL, NULL); if (! test_sending_and_check_echo (conn, "Test 21", "This is a test")) { printf ("ERROR: it should WORK, client certificate isn't working..\n"); @@ -2115,7 +2287,7 @@ nopoll_bool test_22 (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* call to create a connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -2243,7 +2415,7 @@ nopoll_bool test_23 (void) { /* disable verification */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* call to create a connection */ conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -2261,6 +2433,7 @@ nopoll_bool test_23 (void) { /* call to create a connection second connection */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_ssl_peer_verify (opts, nopoll_false); + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); conn = nopoll_conn_tls_new (ctx, opts, "localhost", "1235", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { printf ("ERROR: Expected to find proper client connection status, but found error..\n"); @@ -2297,7 +2470,8 @@ nopoll_bool test_24 (void) { /* configure cookie */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_cookie (opts, "theme=light; sessionToken=abc123"); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); + /* create connection */ conn = nopoll_conn_new_opts (ctx, opts, "localhost", "1234", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -2352,7 +2526,7 @@ nopoll_bool test_25_check_cookie (noPollCtx * ctx, const char * cookie) { /* set a cookie bigger than 1044 */ nopoll_conn_opts_set_cookie (opts, cookie); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_new_opts (ctx, opts, "localhost", "1234", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -2556,7 +2730,7 @@ nopoll_bool test_29 (void) { /* configure extra headers */ opts = nopoll_conn_opts_new (); nopoll_conn_opts_set_extra_headers (opts, "\r\nfoo: bar"); - + nopoll_conn_opts_ssl_host_verify (opts, nopoll_false); /* create connection */ conn = nopoll_conn_new_opts (ctx, opts, "localhost", "1234", NULL, NULL, NULL, NULL); if (! nopoll_conn_is_ok (conn)) { @@ -2834,6 +3008,13 @@ int main (int argc, char ** argv) printf ("Test 01-masking: Library websocket content masking support [ FAILED ]\n"); return -1; } + + if (test_01_hostname_check ()) { + printf ("Test 01_hostname_check: Library websocket hostname validation [ OK ]\n"); + }else { + printf ("Test 01_hostname_check: Library websocket hostname validation [ FAILED ]\n"); + return -1; + } if (test_01 ()) { printf ("Test 01: Simple connect and disconnect [ OK ]\n"); @@ -2920,6 +3101,13 @@ int main (int argc, char ** argv) return -1; } + if (test_05_hostname_validation()) { + printf ("Test 05 hostname_validation: testing basic TLS connect with hostname validation [ OK ]\n"); + } else { + printf ("Test 05 hostname_validation: testing basic TLS connect with hostname validation[ FAILED ]\n"); + return -1; + } + if (test_06 ()) { printf ("Test 06: testing basic TLS connect [ OK ]\n"); } else { From 9628353912b4e4ee118ec4d0c80c9853df08c959 Mon Sep 17 00:00:00 2001 From: Selvam Krishnamoorthy Date: Fri, 14 Apr 2017 12:53:00 +0530 Subject: [PATCH 2/4] Commit changes to testing-branch --- src/libnopoll.def | 4 + src/nopoll_conn.c | 170 ++++++++++++++++++++++++++++---- src/nopoll_conn.h | 4 +- src/nopoll_ctx.c | 17 +++- src/nopoll_ctx.h | 4 + src/nopoll_decl.h | 8 +- src/nopoll_log.c | 6 ++ src/nopoll_loop.c | 93 ++++++++++++++++- src/nopoll_loop.h | 2 + src/nopoll_private.h | 11 ++- test/nopoll-regression-client.c | 16 +-- 11 files changed, 299 insertions(+), 36 deletions(-) diff --git a/src/libnopoll.def b/src/libnopoll.def index 066a1c6..2e8c300 100644 --- a/src/libnopoll.def +++ b/src/libnopoll.def @@ -18,6 +18,7 @@ __nopoll_listener_new_opts_internal __nopoll_listener_sock_listen_internal __nopoll_listener_tls_new_opts_internal __nopoll_log +__nopoll_msg_join __nopoll_mutex_create __nopoll_mutex_destroy __nopoll_mutex_lock @@ -108,6 +109,7 @@ nopoll_conn_set_bind_interface nopoll_conn_set_hook nopoll_conn_set_on_close nopoll_conn_set_on_msg +nopoll_conn_set_on_ping_msg nopoll_conn_set_on_ready nopoll_conn_set_sock_block nopoll_conn_set_sock_tcp_nodelay @@ -134,6 +136,7 @@ nopoll_ctx_set_certificate nopoll_ctx_set_on_accept nopoll_ctx_set_on_msg nopoll_ctx_set_on_open +nopoll_ctx_set_on_ping_msg nopoll_ctx_set_on_ready nopoll_ctx_set_post_ssl_check nopoll_ctx_set_protocol_version @@ -174,6 +177,7 @@ nopoll_log_color_is_enabled nopoll_log_enable nopoll_log_is_enabled nopoll_log_set_handler +nopoll_loop_ended nopoll_loop_init nopoll_loop_process nopoll_loop_process_data diff --git a/src/nopoll_conn.c b/src/nopoll_conn.c index 35508bb..d8325cb 100644 --- a/src/nopoll_conn.c +++ b/src/nopoll_conn.c @@ -48,12 +48,12 @@ #include #include - +#include +#include #if defined(NOPOLL_OS_UNIX) # include #endif -#include /** * @brief Allows to enable/disable non-blocking/blocking behavior on @@ -555,7 +555,14 @@ int nopoll_conn_tls_send (noPollConn * conn, char * buffer, int buffer_size) int res; nopoll_bool needs_retry; int tries = 0; - + int ret = 0; + static pthread_mutex_t mut = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + + ret = pthread_mutex_lock (&mut); + if(ret != 0) + { + nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "mutex failed to lock ( ret = %d) ( errno = %d)",ret, errno); + } /* call to read content */ while (tries < 50) { res = SSL_write (conn->ssl, buffer, buffer_size); @@ -572,6 +579,11 @@ int nopoll_conn_tls_send (noPollConn * conn, char * buffer, int buffer_size) nopoll_sleep (tries * 10000); tries++; } + ret = pthread_mutex_unlock (&mut); + if(ret != 0) + { + nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "mutex failed to unlock ( ret = %d) ( errno = %d)",ret, errno); + } return res; } @@ -788,7 +800,7 @@ noPollConn * __nopoll_conn_new_common (noPollCtx * ctx, if (session == NOPOLL_INVALID_SOCKET) { /* release connection options */ __nopoll_conn_opts_release_if_needed (options); - nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to connect to remote host %s:%s", host_ip, host_port); + nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Failed to connect to remote host %s:%s", host_ip, host_port); return NULL; } /* end if */ @@ -1557,7 +1569,9 @@ nopoll_bool nopoll_conn_is_ready (noPollConn * conn) return nopoll_false; if (conn->session == NOPOLL_INVALID_SOCKET) return nopoll_false; - if (! conn->handshake_ok) { + + /* conn->handshake->received_307 will always be false other than http redirect */ + if (! conn->handshake_ok && !conn->handshake->received_307) { /* acquire here handshake mutex */ nopoll_mutex_lock (conn->ref_mutex); @@ -1567,6 +1581,11 @@ nopoll_bool nopoll_conn_is_ready (noPollConn * conn) /* release here handshake mutex */ nopoll_mutex_unlock (conn->ref_mutex); } + + if(conn->handshake->received_307) + { + return nopoll_true; /* in case of http redirection, conn->handshake_ok will never be true as the response buffer from the server will never have "Sec-Websocket-Accept". Consequently, we have to return true to break the loop inside nopoll_conn_wait_until_connection_ready() */ + } return conn->handshake_ok; } @@ -1884,6 +1903,13 @@ void nopoll_conn_shutdown (noPollConn * conn) if (conn->session != NOPOLL_INVALID_SOCKET && conn->on_close) conn->on_close (conn->ctx, conn, conn->on_close_data); + if(conn->on_close_data != NULL) + { + nopoll_log(conn->ctx, NOPOLL_LEVEL_DEBUG,"freeing conn->on_close_data from shutdown...\n"); + nopoll_free(conn->on_close_data); + conn->on_close_data = NULL; + } + /* shutdown connection here */ if (conn->session != NOPOLL_INVALID_SOCKET) { shutdown (conn->session, SHUT_RDWR); @@ -2265,6 +2291,32 @@ void __nopoll_pack_content (char * buffer, int start, int bytes) return; } +/** + * @internal Function to delay + * @note delay goes up by factor of .125 each time + * @note up to 1 sec max +*/ +static void __nopoll_receive_delay (long *wait_usecs) +{ + long rem; + long t = *wait_usecs; + + nopoll_sleep (t); + + if (t == 1000000) { + return; + } + + rem = t >> 3; + t += rem; + if (t > 1000000) { + t = 1000000; + } + + *wait_usecs = t; +} + + /** * @internal Function used to read bytes from the wire. * @@ -2274,6 +2326,7 @@ void __nopoll_pack_content (char * buffer, int start, int bytes) int __nopoll_conn_receive (noPollConn * conn, char * buffer, int maxlen) { int nread; + long wait_usecs = 500; if (conn->pending_buf_bytes > 0) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Calling with bytes we can reuse (%d), requested: %d", @@ -2310,14 +2363,21 @@ int __nopoll_conn_receive (noPollConn * conn, char * buffer, int maxl #if defined(NOPOLL_OS_UNIX) errno = 0; #endif - if ((nread = conn->receive (conn, buffer, maxlen)) == NOPOLL_SOCKET_ERROR) { - /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning errno=%d (%s)", errno, strerror (errno)); */ - if (errno == NOPOLL_EAGAIN) - return 0; - if (errno == NOPOLL_EWOULDBLOCK) + /* if ((nread = conn->receive (conn, buffer, maxlen)) == NOPOLL_SOCKET_ERROR) { */ + if ((nread = conn->receive (conn, buffer, maxlen)) < 0) { + nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, " conn receive nread=%d, errno=%d (%s)", nread,errno, strerror (errno)); + if (errno == NOPOLL_EAGAIN) { + __nopoll_receive_delay (&wait_usecs); + goto keep_reading; + /* return 0; */ + } + if (errno == NOPOLL_EWOULDBLOCK) { return 0; - if (errno == NOPOLL_EINTR) + } + if (errno == NOPOLL_EINTR) { + __nopoll_receive_delay (&wait_usecs); goto keep_reading; + } nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "unable to readn=%d, error code was: %d (%s) (shutting down connection)", maxlen, errno, strerror (errno)); nopoll_conn_shutdown (conn); @@ -2327,6 +2387,12 @@ int __nopoll_conn_receive (noPollConn * conn, char * buffer, int maxl /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning bytes read = %d", nread); */ if (nread == 0) { /* check for blocking operations */ + if (errno == NOPOLL_EAGAIN) { + __nopoll_receive_delay (&wait_usecs); + goto keep_reading; + /* return 0; */ + } + nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "conn receive zero bytes, errno=%d (%s)", errno, strerror (errno)); if (errno == NOPOLL_EAGAIN || errno == NOPOLL_EWOULDBLOCK) { nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "unable to read from conn-id=%d (%s:%s), connection is not ready (errno: %d : %s)", conn->id, conn->host, conn->port, errno, strerror (errno)); @@ -2336,12 +2402,15 @@ int __nopoll_conn_receive (noPollConn * conn, char * buffer, int maxl nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "received connection close while reading from conn id %d (errno=%d : %s) (%d, %d, %d), shutting down connection..", conn->id, errno, strerror (errno), NOPOLL_EAGAIN, NOPOLL_EWOULDBLOCK, NOPOLL_EINTR); + conn->on_close_data = nopoll_strdup ("SSL_Socket_Close:received connection close while reading from conn: shutting down connection"); nopoll_conn_shutdown (conn); } /* end if */ /* ensure we don't access outside the array */ - if (nread < 0) + if (nread < 0) { + nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "** nread < 0 (%d)", nread); nread = 0; + } buffer[nread] = 0; return nread; @@ -2834,6 +2903,13 @@ int nopoll_conn_complete_handshake_client (noPollCtx * ctx, noPollConn * conn, c iterator++; if (! nopoll_ncmp (buffer + iterator, "101", 3)) { nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "websocket server denied connection with: %s", buffer + iterator); + if(nopoll_ncmp (buffer + iterator, "307", 3)|| nopoll_ncmp (buffer + iterator, "302", 3) || nopoll_ncmp (buffer + iterator, "303", 3) ) + { + nopoll_log (ctx, NOPOLL_LEVEL_INFO, "Received HTTP 30x response from server"); + /* Mark 307 flag as true */ + conn->handshake->received_307 = nopoll_true; + return 1; /* continue to read next lines for redirect Location */ + } return 0; /* do not continue */ } /* end if */ @@ -2871,7 +2947,14 @@ int nopoll_conn_complete_handshake_client (noPollCtx * ctx, noPollConn * conn, c } else if (strcasecmp (header, "Connection") == 0) { conn->handshake->connection_upgrade = 1; nopoll_free (value); - } else { + } else if (strcasecmp (header, "Location") == 0) { + if(conn->handshake->received_307) + { + conn->handshake->redirectURL = value; + nopoll_log (ctx, NOPOLL_LEVEL_INFO, "nopoll_conn_complete_handshake_client: conn->handshake->redirectURL: %s",conn->handshake->redirectURL); + } + } + else { /* release value, no body claimed it */ nopoll_free (value); } /* end if */ @@ -3197,7 +3280,7 @@ noPollMsg * nopoll_conn_get_msg (noPollConn * conn) memcpy (conn->pending_buf + conn->pending_buf_bytes, buffer, bytes); conn->pending_buf_bytes += bytes; - nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, + nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Expected to receive complete websocket frame header but found only %d bytes over conn-id=%d, saving to reuse later", bytes, conn->id); return NULL; @@ -3327,7 +3410,7 @@ noPollMsg * nopoll_conn_get_msg (noPollConn * conn) /* nothing more to add here, close frame without content received, so we have no reason to keep on reading */ - nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Proper connection close frame received id=%d, shutting down", conn->id); + nopoll_log (conn->ctx, NOPOLL_LEVEL_INFO, "Proper connection close frame received id=%d, shutting down", conn->id); nopoll_msg_unref (msg); nopoll_conn_shutdown (conn); return NULL; @@ -3338,6 +3421,16 @@ noPollMsg * nopoll_conn_get_msg (noPollConn * conn) conn->id, msg->payload_size); } /* end if */ + if (msg->op_code == NOPOLL_PING_FRAME) { + nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "PING received over connection id=%d, replying PONG", conn->id); + /*nopoll_msg_unref (msg); + + call to send pong */ + /*nopoll_conn_send_pong (conn); + + return NULL;*/ + } /* end if */ + /* get more bytes */ if (msg->is_masked) { bytes = __nopoll_conn_receive (conn, (char *) msg->mask, 4); @@ -3438,6 +3531,8 @@ noPollMsg * nopoll_conn_get_msg (noPollConn * conn) /* update was a fragment */ conn->previous_was_fragment = msg->is_fragment && msg->has_fin == 0; + nopoll_log(conn->ctx, NOPOLL_LEVEL_DEBUG, "bytes %d, msg->payload_size %d, msg->remain_bytes %d, msg->has_fin %d, msg->op_code %d\n",bytes,msg->payload_size,msg->remain_bytes,msg->has_fin,msg->op_code); + /* do not notify any frame since no content was found */ if (bytes == 0 && msg == conn->previous_msg) { nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "bytes == %d, msg (%p) == conn->previous_msg (%p)", @@ -3930,6 +4025,20 @@ void nopoll_conn_set_on_msg (noPollConn * conn, return; } +void nopoll_conn_set_on_ping_msg (noPollConn * conn, + noPollOnMessageHandler on_ping_msg, + noPollPtr user_data) +{ + if (conn == NULL) + return; + + /* configure on message handler */ + conn->on_ping_msg = on_ping_msg; + conn->on_ping_msg_data = user_data; + + return; +} + /** * @brief Allows to configure a handler that is called when the @@ -4713,14 +4822,18 @@ nopoll_bool nopoll_conn_accept_complete (noPollCtx * ctx, noPollConn * listener, * @param timeout The timeout operation to limit the wait * operation. Timeout is provided in seconds. * + * @param message in-out parameter of 64 byte. The response message string description indicating + * "Success", "Failure" or "Redirect: Redirect_URL". Caller needs to allocate memory for this. + * * @return The function returns when the timeout was reached or the * connection is ready. In the case the connection is ready when the * function finished nopoll_true is returned, otherwise nopoll_false. */ nopoll_bool nopoll_conn_wait_until_connection_ready (noPollConn * conn, - int timeout) + int timeout, char * message) { long int total_timeout = timeout * 1000000; + nopoll_bool result = nopoll_false; /* check if the connection already finished its connection handshake */ @@ -4737,8 +4850,31 @@ nopoll_bool nopoll_conn_wait_until_connection_ready (noPollConn * conn, total_timeout = total_timeout - 500; } /* end if */ + result = nopoll_conn_is_ok (conn) && nopoll_conn_is_ready (conn); + + if(conn->handshake->received_307 == nopoll_true && (conn->handshake->redirectURL != NULL)) + { + if(message != NULL) + { + snprintf(message, strlen(conn->handshake->redirectURL) + 10, "Redirect:%s", conn->handshake->redirectURL); + nopoll_free (conn->handshake->redirectURL); + } + conn->handshake->received_307 = nopoll_false; + nopoll_log (conn->ctx, NOPOLL_LEVEL_INFO, "nopoll_conn_wait_until_connection_ready() response: message: %s" ,message ); + return nopoll_false; /* retry with redirection URLs */ + } + else if(result && message != NULL) + { + strncpy(message, "Success", strlen("Success")+1); + } + else if(message != NULL) + { + strncpy(message, "Failure", strlen("Failure")+1); + } + nopoll_log (conn->ctx, NOPOLL_LEVEL_INFO, "*****End nopoll_conn_wait_until_connection_ready ****"); + /* report if the connection is ok */ - return nopoll_conn_is_ok (conn) && nopoll_conn_is_ready (conn); + return result; } /* @} */ diff --git a/src/nopoll_conn.h b/src/nopoll_conn.h index 9790575..9d9c7d0 100644 --- a/src/nopoll_conn.h +++ b/src/nopoll_conn.h @@ -111,6 +111,8 @@ noPollConn * nopoll_conn_accept (noPollCtx * ctx, noPollConn * listener); noPollConn * nopoll_conn_accept_socket (noPollCtx * ctx, noPollConn * listener, NOPOLL_SOCKET session); +void nopoll_conn_set_on_ping_msg (noPollConn * conn, noPollOnMessageHandler on_ping_msg, noPollPtr user_data); + nopoll_bool nopoll_conn_accept_complete (noPollCtx * ctx, noPollConn * listener, noPollConn * conn, @@ -224,7 +226,7 @@ int __nopoll_conn_send_common (noPollConn * conn, noPollOpCode frame_type); nopoll_bool nopoll_conn_wait_until_connection_ready (noPollConn * conn, - int timeout); + int timeout, char * message); void nopoll_conn_connect_timeout (noPollCtx * ctx, long microseconds_to_wait); diff --git a/src/nopoll_ctx.c b/src/nopoll_ctx.c index f9957bd..b61bbd7 100644 --- a/src/nopoll_ctx.c +++ b/src/nopoll_ctx.c @@ -334,8 +334,8 @@ void nopoll_ctx_unregister_conn (noPollCtx * ctx, /* acquire a reference to the conection */ nopoll_conn_unref (conn); - - return; + nopoll_log (ctx, NOPOLL_LEVEL_INFO, "Returning, unlock of mutex is not required "); + return; } /* end if */ iterator++; @@ -650,6 +650,19 @@ void nopoll_ctx_set_on_msg (noPollCtx * ctx, return; } +void nopoll_ctx_set_on_ping_msg (noPollCtx * ctx, + noPollOnMessageHandler on_ping_msg, + noPollPtr user_data) +{ + nopoll_return_if_fail (ctx, ctx); + + /* set new handler */ + ctx->on_ping_msg = on_ping_msg; + ctx->on_ping_msg_data = user_data; + + return; +} + /** * @brief Allows to configure the handler that will be used to let * user land code to define OpenSSL SSL_CTX object. diff --git a/src/nopoll_ctx.h b/src/nopoll_ctx.h index 1949771..483ceae 100644 --- a/src/nopoll_ctx.h +++ b/src/nopoll_ctx.h @@ -87,6 +87,10 @@ void nopoll_ctx_set_on_msg (noPollCtx * ctx, noPollOnMessageHandler on_msg, noPollPtr user_data); +void nopoll_ctx_set_on_ping_msg (noPollCtx * ctx, + noPollOnMessageHandler on_ping_msg, + noPollPtr user_data); + void nopoll_ctx_set_ssl_context_creator (noPollCtx * ctx, noPollSslContextCreator context_creator, noPollPtr user_data); diff --git a/src/nopoll_decl.h b/src/nopoll_decl.h index 80bb67b..f58ac1b 100644 --- a/src/nopoll_decl.h +++ b/src/nopoll_decl.h @@ -146,6 +146,7 @@ * @brief Portable definition for EWOULDBLOCK errno code. */ #define NOPOLL_EWOULDBLOCK EWOULDBLOCK +#define NOPOLL_ETIMEDOUT ETIMEDOUT #define NOPOLL_EINPROGRESS EINPROGRESS #define NOPOLL_ENOTCONN ENOTCONN #define NOPOLL_EAGAIN EAGAIN @@ -316,7 +317,12 @@ typedef enum { * @brief Debug level. Only used to report common * circumstances that represent the proper functionality. */ - NOPOLL_LEVEL_DEBUG, + NOPOLL_LEVEL_DEBUG, + /** + * @brief Info level. Only used to report information for debugging, common + * circumstances that represent the proper functionality. + */ + NOPOLL_LEVEL_INFO, /** * @brief Warning level. Only used to report that an internal * issue have happend that could be interesting while diff --git a/src/nopoll_log.c b/src/nopoll_log.c index dd6a972..89620b5 100644 --- a/src/nopoll_log.c +++ b/src/nopoll_log.c @@ -203,6 +203,9 @@ void __nopoll_log (noPollCtx * ctx, const char * function_name, const char * fil case NOPOLL_LEVEL_DEBUG: printf ("(\e[1;32mdebug\e[0m) "); break; + case NOPOLL_LEVEL_INFO: + printf ("(\e[1;32minfo\e[0m) "); + break; case NOPOLL_LEVEL_WARNING: printf ("(\e[1;33mwarning\e[0m) "); break; @@ -215,6 +218,9 @@ void __nopoll_log (noPollCtx * ctx, const char * function_name, const char * fil case NOPOLL_LEVEL_DEBUG: printf ("(debug)"); break; + case NOPOLL_LEVEL_INFO: + printf ("(info)"); + break; case NOPOLL_LEVEL_WARNING: printf ("(warning)"); break; diff --git a/src/nopoll_loop.c b/src/nopoll_loop.c index 0ef8141..41ceee7 100644 --- a/src/nopoll_loop.c +++ b/src/nopoll_loop.c @@ -48,6 +48,12 @@ * @{ */ +/*----------------------------------------------------------------------------*/ +/* File Scoped Variables */ +/*----------------------------------------------------------------------------*/ +noPollMsg * fragMsg; +int isPreviousMsgFragment = 0; + /** * @internal Function used by nopoll_loop_wait to register all * connections into the io waiting object. @@ -76,6 +82,15 @@ nopoll_bool nopoll_loop_register (noPollCtx * ctx, noPollConn * conn, noPollPtr return nopoll_false; /* keep foreach, don't stop */ } +noPollMsg * __nopoll_msg_join(noPollMsg *fragMsg, noPollMsg *msg) +{ + noPollMsg *tempMsg = nopoll_msg_join(fragMsg,msg); + nopoll_msg_unref (fragMsg); + nopoll_msg_unref (msg); + + return tempMsg; +} + /** * @internal Function used to handle incoming data from from the * connection and to notify this data on the connection. @@ -89,11 +104,64 @@ void nopoll_loop_process_data (noPollCtx * ctx, noPollConn * conn) if (msg == NULL) return; - /* found message, notify it */ - if (conn->on_msg) - conn->on_msg (ctx, conn, msg, conn->on_msg_data); - else if (ctx->on_msg) - ctx->on_msg (ctx, conn, msg, ctx->on_msg_data); + if(msg->op_code == NOPOLL_PING_FRAME) + { + /* Initialized ping msg handler */ + if (conn->on_ping_msg) + conn->on_ping_msg (ctx, conn, msg, conn->on_ping_msg_data); + else if (ctx->on_ping_msg) + ctx->on_ping_msg (ctx, conn, msg, ctx->on_ping_msg_data); + } + else { + /* found message, notify it */ + /* Initialized msg handler */ + + if(msg->has_fin == 0) + { + nopoll_log(ctx, NOPOLL_LEVEL_INFO, "Received Fragment - FIN: %d, Opcode: %d, payload size: %d, Remaining bytes: %d",msg->has_fin,msg->op_code,nopoll_msg_get_payload_size(msg),msg->remain_bytes); + isPreviousMsgFragment = 1; + if(fragMsg == NULL) + { + fragMsg = msg; + nopoll_log(ctx, NOPOLL_LEVEL_INFO, "Received fragment, joined the message, waiting for last fragment"); + return; + } + else + { + if(nopoll_msg_get_payload_size(msg) == msg->remain_bytes) + { + nopoll_log(ctx, NOPOLL_LEVEL_DEBUG,"nopoll_msg_ref_count(fragMsg) %d, nopoll_msg_ref_count(msg) %d\n",nopoll_msg_ref_count(fragMsg),nopoll_msg_ref_count(msg)); + msg = __nopoll_msg_join(fragMsg,msg); + nopoll_log(ctx, NOPOLL_LEVEL_INFO,"Received all the pending bytes, hence which means the complete message is received"); + fragMsg = NULL; + isPreviousMsgFragment = 0; + nopoll_log(ctx, NOPOLL_LEVEL_INFO,"Received last fragment payload size %d, joined the old fragment messages",msg->payload_size); + } + else + { + nopoll_log(ctx, NOPOLL_LEVEL_DEBUG,"nopoll_msg_ref_count(fragMsg) %d, nopoll_msg_ref_count(msg) %d\n",nopoll_msg_ref_count(fragMsg),nopoll_msg_ref_count(msg)); + fragMsg = __nopoll_msg_join(fragMsg,msg); + nopoll_log(ctx, NOPOLL_LEVEL_INFO, "Received fragment, joined the message, waiting for last fragment"); + return; + } + + } + } + else if(msg->has_fin == 1 && isPreviousMsgFragment && msg->op_code == NOPOLL_CONTINUATION_FRAME) + { + nopoll_log(ctx, NOPOLL_LEVEL_INFO, "Received Fragment - FIN: %d, Opcode: %d, payload size: %d, Remaining bytes: %d",msg->has_fin,msg->op_code,nopoll_msg_get_payload_size(msg),msg->remain_bytes); + nopoll_log(ctx, NOPOLL_LEVEL_DEBUG,"nopoll_msg_ref_count(fragMsg) %d, nopoll_msg_ref_count(msg) %d\n",nopoll_msg_ref_count(fragMsg),nopoll_msg_ref_count(msg)); + msg = __nopoll_msg_join(fragMsg,msg); + fragMsg = NULL; + isPreviousMsgFragment = 0; + nopoll_log(ctx, NOPOLL_LEVEL_INFO,"Received last fragment payload size %d, joined the old fragment messages",msg->payload_size); + } + + if (conn->on_msg) + conn->on_msg (ctx, conn, msg, conn->on_msg_data); + else if (ctx->on_msg) + ctx->on_msg (ctx, conn, msg, ctx->on_msg_data); + } /* release message */ nopoll_msg_unref (msg); @@ -268,6 +336,21 @@ int nopoll_loop_wait (noPollCtx * ctx, long timeout) return 0; } +/** + * @brief To determine if nopoll loop wait has ended/terminated. + * This is to identify termination i.e. when the nopoll loop wait stops and + * there are no connections then this returns 1 else 0. + * + * @param ctx The context object. + * + * @return The function returns 0 nopoll loop wait is running and + * 1 when the nopoll loop wait has ended/terminatedss + */ +int nopoll_loop_ended (noPollCtx * ctx) +{ + return (NULL == ctx->io_engine); +} + /* @} */ diff --git a/src/nopoll_loop.h b/src/nopoll_loop.h index 38f0798..d4a1c4e 100644 --- a/src/nopoll_loop.h +++ b/src/nopoll_loop.h @@ -47,6 +47,8 @@ int nopoll_loop_wait (noPollCtx * ctx, long timeout); void nopoll_loop_stop (noPollCtx * ctx); +int nopoll_loop_ended (noPollCtx * ctx); + END_C_DECLS #endif diff --git a/src/nopoll_private.h b/src/nopoll_private.h index 189f480..af373d6 100644 --- a/src/nopoll_private.h +++ b/src/nopoll_private.h @@ -123,7 +123,9 @@ struct _noPollCtx { */ noPollOnMessageHandler on_msg; noPollPtr on_msg_data; - + + noPollOnMessageHandler on_ping_msg; + noPollPtr on_ping_msg_data; /** * @internal Basic fake support for protocol version, by * default: 13, due to RFC6455 standard @@ -232,7 +234,9 @@ struct _noPollConn { */ noPollOnMessageHandler on_msg; noPollPtr on_msg_data; - + + noPollOnMessageHandler on_ping_msg; + noPollPtr on_ping_msg_data; /** * @internal Reference to defined on ready handling. */ @@ -370,6 +374,7 @@ struct _noPollHandshake { nopoll_bool upgrade_websocket; nopoll_bool connection_upgrade; nopoll_bool received_101; + nopoll_bool received_307; char * websocket_key; char * websocket_version; char * websocket_accept; @@ -377,6 +382,8 @@ struct _noPollHandshake { /* reference to cookie header */ char * cookie; + /* redirect Location URL */ + char * redirectURL; }; struct _noPollConnOpts { diff --git a/test/nopoll-regression-client.c b/test/nopoll-regression-client.c index 316749e..9ffd61d 100644 --- a/test/nopoll-regression-client.c +++ b/test/nopoll-regression-client.c @@ -786,7 +786,7 @@ nopoll_bool test_04b (void) { } printf ("Test 04-b: waiting until connection is ok\n"); - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5, NULL); printf ("Test 04-b: sending was quick as possible to flood local buffers..\n"); @@ -854,7 +854,7 @@ nopoll_bool test_04b (void) { } printf ("Test 04-b: waiting until connection is ok\n"); - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5, NULL); /* send a cleanup message */ bytes_written = nopoll_conn_send_text (conn, "release-message", 15); @@ -902,7 +902,7 @@ nopoll_bool test_04c (void) { } printf ("Test 04-c: waiting until connection is ok\n"); - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5, NULL); /* remove local file */ if (stat ("copy-test-04c.txt", &file_info) == 0) { @@ -1385,7 +1385,7 @@ nopoll_bool test_11 (void) { /* create a working connection */ conn = nopoll_conn_new (ctx, "localhost", "1234", NULL, NULL, NULL, NULL); - if (! nopoll_conn_wait_until_connection_ready (conn, 5)) { + if (! nopoll_conn_wait_until_connection_ready (conn, 5, NULL)) { printf ("ERROR: Expected a FAILING connection status due to origing denied, but it working..\n"); return nopoll_false; } /* end if */ @@ -1427,7 +1427,7 @@ nopoll_bool test_12 (void) { /* create a working connection */ conn = nopoll_conn_new (ctx, "localhost", "1234", NULL, NULL, NULL, NULL); - if (! nopoll_conn_wait_until_connection_ready (conn, 5)) { + if (! nopoll_conn_wait_until_connection_ready (conn, 5, NULL)) { printf ("ERROR: Expected NOT to find a FAILING connection status, errno is=%d..\n", errno); return nopoll_false; } /* end if */ @@ -2677,7 +2677,7 @@ nopoll_bool test_28 (void) { } /* end if */ /* wait until it is connected */ - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5, NULL); /* send a message to request connection close with a particular message */ if (nopoll_conn_send_text (conn, "close with message", 18) != 18) { @@ -2739,7 +2739,7 @@ nopoll_bool test_29 (void) { } /* end if */ /* wait until it is connected */ - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5,NULL); /* close connection */ nopoll_conn_close (conn); @@ -2777,7 +2777,7 @@ nopoll_bool test_30_common_header_stop (const char * label, int bytes_to_send_be printf ("Test %s: waiting until connection is ready..\n", label); /* wait until it is connected */ - nopoll_conn_wait_until_connection_ready (conn, 5); + nopoll_conn_wait_until_connection_ready (conn, 5,NULL); printf ("Test %s: ok..\n", label); /* send a message to request connection close with a particular message */ From 5be47f85a3398fc4fbd70427263028343013131d Mon Sep 17 00:00:00 2001 From: Selvam Krishnamoorthy Date: Mon, 24 Apr 2017 16:15:33 +0530 Subject: [PATCH 3/4] Create socket in blocking mode --- src/nopoll_conn.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nopoll_conn.c b/src/nopoll_conn.c index d8325cb..16a7771 100644 --- a/src/nopoll_conn.c +++ b/src/nopoll_conn.c @@ -297,11 +297,11 @@ NOPOLL_SOCKET __nopoll_conn_sock_connect_opts_internal (noPollCtx * ctx, } /* end if */ /* set non blocking status */ - nopoll_conn_set_sock_block (session, nopoll_false); + nopoll_log (ctx, NOPOLL_LEVEL_INFO,"Create socket with blocking-mode"); + nopoll_conn_set_sock_block (session, nopoll_true); /* do a tcp connect */ if (connect (session, res->ai_addr, res->ai_addrlen) < 0) { - if(errno != NOPOLL_EINPROGRESS && errno != NOPOLL_EWOULDBLOCK && errno != NOPOLL_ENOTCONN) { shutdown (session, SHUT_RDWR); nopoll_close_socket (session); @@ -312,7 +312,10 @@ NOPOLL_SOCKET __nopoll_conn_sock_connect_opts_internal (noPollCtx * ctx, freeaddrinfo (res); return -1; - } /* end if */ + } + else + { + nopoll_log (ctx, NOPOLL_LEVEL_INFO,"socket connect successfull"); } /* end if */ /* relase address info */ From d0706433d4d67811f66e91e879df30d431ae67ed Mon Sep 17 00:00:00 2001 From: Selvam Krishnamoorthy Date: Tue, 25 Apr 2017 11:58:45 +0530 Subject: [PATCH 4/4] Log debug messages for blocking mode --- src/nopoll_conn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nopoll_conn.c b/src/nopoll_conn.c index 16a7771..959edb8 100644 --- a/src/nopoll_conn.c +++ b/src/nopoll_conn.c @@ -297,7 +297,7 @@ NOPOLL_SOCKET __nopoll_conn_sock_connect_opts_internal (noPollCtx * ctx, } /* end if */ /* set non blocking status */ - nopoll_log (ctx, NOPOLL_LEVEL_INFO,"Create socket with blocking-mode"); + nopoll_log (ctx, NOPOLL_LEVEL_DEBUG,"Create socket with blocking-mode"); nopoll_conn_set_sock_block (session, nopoll_true); /* do a tcp connect */ @@ -315,7 +315,7 @@ NOPOLL_SOCKET __nopoll_conn_sock_connect_opts_internal (noPollCtx * ctx, } else { - nopoll_log (ctx, NOPOLL_LEVEL_INFO,"socket connect successfull"); + nopoll_log (ctx, NOPOLL_LEVEL_DEBUG,"socket connect successfull"); } /* end if */ /* relase address info */