Example #1
0
int openconnect_parse_url(struct openconnect_info *vpninfo, const char *url)
{
	char *scheme = NULL;
	int ret;

	UTF8CHECK(url);

	openconnect_set_hostname(vpninfo, NULL);
	free(vpninfo->urlpath);
	vpninfo->urlpath = NULL;

	ret = internal_parse_url(url, &scheme, &vpninfo->hostname,
				 &vpninfo->port, &vpninfo->urlpath, 443);

	if (ret) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Failed to parse server URL '%s'\n"),
			     url);
		return ret;
	}
	if (scheme && strcmp(scheme, "https")) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Only https:// permitted for server URL\n"));
		ret = -EINVAL;
	}
	free(scheme);
	return ret;
}
Example #2
0
/* cf. RFC2818 and RFC2459 */
static int match_cert_hostname(struct openconnect_info *vpninfo, X509 *peer_cert)
{
	STACK_OF(GENERAL_NAME) *altnames;
	X509_NAME *subjname;
	ASN1_STRING *subjasn1;
	char *subjstr = NULL;
	int addrlen = 0;
	int i, altdns = 0;
	char addrbuf[sizeof(struct in6_addr)];
	int ret;

	/* Allow GEN_IP in the certificate only if we actually connected
	   by IP address rather than by name. */
	if (inet_pton(AF_INET, vpninfo->hostname, addrbuf) > 0)
		addrlen = 4;
	else if (inet_pton(AF_INET6, vpninfo->hostname, addrbuf) > 0)
		addrlen = 16;
	else if (vpninfo->hostname[0] == '[' &&
		 vpninfo->hostname[strlen(vpninfo->hostname)-1] == ']') {
		char *p = &vpninfo->hostname[strlen(vpninfo->hostname)-1];
		*p = 0;
		if (inet_pton(AF_INET6, vpninfo->hostname + 1, addrbuf) > 0)
			addrlen = 16;
		*p = ']';
	}

	altnames = X509_get_ext_d2i(peer_cert, NID_subject_alt_name,
				    NULL, NULL);
	for (i = 0; i < sk_GENERAL_NAME_num(altnames); i++) {
		const GENERAL_NAME *this = sk_GENERAL_NAME_value(altnames, i);

		if (this->type == GEN_DNS) {
			char *str;

			int len = ASN1_STRING_to_UTF8((void *)&str, this->d.ia5);
			if (len < 0)
				continue;

			altdns = 1;

			/* We don't like names with embedded NUL */
			if (strlen(str) != len)
				continue;

			if (!match_hostname(vpninfo->hostname, str)) {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("Matched DNS altname '%s'\n"),
					     str);
				GENERAL_NAMES_free(altnames);
				OPENSSL_free(str);
				return 0;
			} else {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("No match for altname '%s'\n"),
					     str);
			}
			OPENSSL_free(str);
		} else if (this->type == GEN_IPADD && addrlen) {
			char host[80];
			int family;

			if (this->d.ip->length == 4) {
				family = AF_INET;
			} else if (this->d.ip->length == 16) {
				family = AF_INET6;
			} else {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Certificate has GEN_IPADD altname with bogus length %d\n"),
					     this->d.ip->length);
				continue;
			}

			/* We only do this for the debug messages */
			inet_ntop(family, this->d.ip->data, host, sizeof(host));

			if (this->d.ip->length == addrlen &&
			    !memcmp(addrbuf, this->d.ip->data, addrlen)) {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("Matched %s address '%s'\n"),
					     (family == AF_INET6) ? "IPv6" : "IPv4",
					     host);
				GENERAL_NAMES_free(altnames);
				return 0;
			} else {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("No match for %s address '%s'\n"),
					     (family == AF_INET6) ? "IPv6" : "IPv4",
					     host);
			}
		} else if (this->type == GEN_URI) {
			char *str;
			char *url_proto, *url_host, *url_path, *url_host2;
			int url_port;
			int len = ASN1_STRING_to_UTF8((void *)&str, this->d.ia5);

			if (len < 0)
				continue;

			/* We don't like names with embedded NUL */
			if (strlen(str) != len)
				continue;

			if (internal_parse_url(str, &url_proto, &url_host, &url_port, &url_path, 0)) {
				OPENSSL_free(str);
				continue;
			}

			if (!url_proto || strcasecmp(url_proto, "https"))
				goto no_uri_match;

			if (url_port != vpninfo->port)
				goto no_uri_match;

			/* Leave url_host as it was so that it can be freed */
			url_host2 = url_host;
			if (addrlen == 16 && vpninfo->hostname[0] != '[' &&
			    url_host[0] == '[' && url_host[strlen(url_host)-1] == ']') {
				/* Cope with https://[IPv6]/ when the hostname is bare IPv6 */
				url_host[strlen(url_host)-1] = 0;
				url_host2++;
			}

			if (strcasecmp(vpninfo->hostname, url_host2))
				goto no_uri_match;

			if (url_path) {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("URI '%s' has non-empty path; ignoring\n"),
					     str);
				goto no_uri_match_silent;
			}
			vpn_progress(vpninfo, PRG_TRACE,
				     _("Matched URI '%s'\n"),
				     str);
			free(url_proto);
			free(url_host);
			free(url_path);
			OPENSSL_free(str);
			GENERAL_NAMES_free(altnames);
			return 0;

		no_uri_match:
			vpn_progress(vpninfo, PRG_TRACE,
				     _("No match for URI '%s'\n"),
				     str);
		no_uri_match_silent:
			free(url_proto);
			free(url_host);
			free(url_path);
			OPENSSL_free(str);
		}
	}
	GENERAL_NAMES_free(altnames);

	/* According to RFC2818, we don't use the legacy subject name if
	   there was an altname with DNS type. */
	if (altdns) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("No altname in peer cert matched '%s'\n"),
			     vpninfo->hostname);
		return -EINVAL;
	}

	subjname = X509_get_subject_name(peer_cert);
	if (!subjname) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("No subject name in peer cert!\n"));
		return -EINVAL;
	}

	/* Find the _last_ (most specific) commonName */
	i = -1;
	while (1) {
		int j = X509_NAME_get_index_by_NID(subjname, NID_commonName, i);
		if (j >= 0)
			i = j;
		else
			break;
	}

	subjasn1 = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subjname, i));

	i = ASN1_STRING_to_UTF8((void *)&subjstr, subjasn1);

	if (!subjstr || strlen(subjstr) != i) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Failed to parse subject name in peer cert\n"));
		return -EINVAL;
	}
	ret = 0;

	if (match_hostname(vpninfo->hostname, subjstr)) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Peer cert subject mismatch ('%s' != '%s')\n"),
			     subjstr, vpninfo->hostname);
		ret = -EINVAL;
	} else {
		vpn_progress(vpninfo, PRG_TRACE,
			     _("Matched peer certificate subject name '%s'\n"),
			     subjstr);
	}

	OPENSSL_free(subjstr);
	return ret;
}
Example #3
0
int connect_https_socket(struct openconnect_info *vpninfo)
{
    int ssl_sock = -1;
    int err;

    if (!vpninfo->port)
        vpninfo->port = 443;

    if (vpninfo->peer_addr) {
reconnect:
#ifdef SOCK_CLOEXEC
        ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_IP);
        if (ssl_sock < 0)
#endif
        {
            ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM, IPPROTO_IP);
            if (ssl_sock < 0)
                goto reconn_err;
            set_fd_cloexec(ssl_sock);
        }
        if (cancellable_connect(vpninfo, ssl_sock, vpninfo->peer_addr, vpninfo->peer_addrlen)) {
reconn_err:
            if (vpninfo->proxy) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to reconnect to proxy %s\n"),
                             vpninfo->proxy);
            } else {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to reconnect to host %s\n"),
                             vpninfo->hostname);
            }
            if (ssl_sock >= 0)
                closesocket(ssl_sock);
            return -EINVAL;
        }
    } else {
        struct addrinfo hints, *result, *rp;
        char *hostname;
        char port[6];

        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
        hints.ai_protocol = 0;
        hints.ai_canonname = NULL;
        hints.ai_addr = NULL;
        hints.ai_next = NULL;

        /* The 'port' variable is a string because it's easier
           this way than if we pass NULL to getaddrinfo() and
           then try to fill in the numeric value into
           different types of returned sockaddr_in{6,}. */
#ifdef LIBPROXY_HDR
        if (vpninfo->proxy_factory) {
            char *url;
            char **proxies;
            int i = 0;

            free(vpninfo->proxy_type);
            vpninfo->proxy_type = NULL;
            free(vpninfo->proxy);
            vpninfo->proxy = NULL;

            if (vpninfo->port == 443)
                i = asprintf(&url, "https://%s/%s", vpninfo->hostname,
                             vpninfo->urlpath?:"");
            else
                i = asprintf(&url, "https://%s:%d/%s", vpninfo->hostname,
                             vpninfo->port, vpninfo->urlpath?:"");
            if (i == -1)
                return -ENOMEM;

            proxies = px_proxy_factory_get_proxies(vpninfo->proxy_factory,
                                                   url);

            i = 0;
            while (proxies && proxies[i]) {
                if (!vpninfo->proxy &&
                        (!strncmp(proxies[i], "http://", 7) ||
                         !strncmp(proxies[i], "socks://", 8) ||
                         !strncmp(proxies[i], "socks5://", 9)))
                    internal_parse_url(proxies[i], &vpninfo->proxy_type,
                                       &vpninfo->proxy, &vpninfo->proxy_port,
                                       NULL, 0);
                i++;
            }
            free(url);
            free(proxies);
            if (vpninfo->proxy)
                vpn_progress(vpninfo, PRG_DEBUG,
                             _("Proxy from libproxy: %s://%s:%d/\n"),
                             vpninfo->proxy_type, vpninfo->proxy, vpninfo->port);
        }
Example #4
0
int connect_https_socket(struct openconnect_info *vpninfo)
{
	int ssl_sock = -1;
	int err;

	if (!vpninfo->port)
		vpninfo->port = 443;

	/* If we're talking to a server which told us it has dynamic DNS, don't
	   just re-use its previous IP address. If we're talking to a proxy, we
	   can use *its* previous IP address. We expect it'll re-do the DNS
	   lookup for the server anyway. */
	if (vpninfo->peer_addr && (!vpninfo->is_dyndns || vpninfo->proxy)) {
	reconnect:
#ifdef SOCK_CLOEXEC
		ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_IP);
		if (ssl_sock < 0)
#endif
		{
			ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM, IPPROTO_IP);
			if (ssl_sock < 0) {
#ifdef _WIN32
				err = WSAGetLastError();
#else
				err = -errno;
#endif
				goto reconn_err;
			}
			set_fd_cloexec(ssl_sock);
		}
		err = cancellable_connect(vpninfo, ssl_sock, vpninfo->peer_addr, vpninfo->peer_addrlen);
		if (err) {
			char *errstr;
		reconn_err:
#ifdef _WIN32
			if (err > 0)
				errstr = openconnect__win32_strerror(err);
			else
#endif
				errstr = strerror(-err);
			if (vpninfo->proxy) {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Failed to reconnect to proxy %s: %s\n"),
					     vpninfo->proxy, errstr);
			} else {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Failed to reconnect to host %s: %s\n"),
					     vpninfo->hostname, errstr);
			}
#ifdef _WIN32
			if (err > 0)
				free(errstr);
#endif
			if (ssl_sock >= 0)
				closesocket(ssl_sock);
			ssl_sock = -EINVAL;
			goto out;
		}
	} else {
		struct addrinfo hints, *result, *rp;
		char *hostname;
		char port[6];

		memset(&hints, 0, sizeof(struct addrinfo));
		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
		hints.ai_protocol = 0;
		hints.ai_canonname = NULL;
		hints.ai_addr = NULL;
		hints.ai_next = NULL;

		/* The 'port' variable is a string because it's easier
		   this way than if we pass NULL to getaddrinfo() and
		   then try to fill in the numeric value into
		   different types of returned sockaddr_in{6,}. */
#ifdef LIBPROXY_HDR
		if (vpninfo->proxy_factory) {
			struct oc_text_buf *url_buf = buf_alloc();
			char **proxies;
			int i = 0;

			free(vpninfo->proxy_type);
			vpninfo->proxy_type = NULL;
			free(vpninfo->proxy);
			vpninfo->proxy = NULL;

			buf_append(url_buf, "https://%s", vpninfo->hostname);
			if (vpninfo->port != 443)
				buf_append(url_buf, ":%d", vpninfo->port);
			buf_append(url_buf, "/%s", vpninfo->urlpath?:"");
			if (buf_error(url_buf)) {
				buf_free(url_buf);
				ssl_sock = -ENOMEM;
				goto out;
			}

			proxies = px_proxy_factory_get_proxies(vpninfo->proxy_factory,
							       url_buf->data);
			i = 0;
			while (proxies && proxies[i]) {
				if (!vpninfo->proxy &&
				    (!strncmp(proxies[i], "http://", 7) ||
				     !strncmp(proxies[i], "socks://", 8) ||
				     !strncmp(proxies[i], "socks5://", 9)))
					internal_parse_url(proxies[i], &vpninfo->proxy_type,
						  &vpninfo->proxy, &vpninfo->proxy_port,
						  NULL, 0);
				i++;
			}
			buf_free(url_buf);
			free(proxies);
			if (vpninfo->proxy)
				vpn_progress(vpninfo, PRG_DEBUG,
					     _("Proxy from libproxy: %s://%s:%d/\n"),
					     vpninfo->proxy_type, vpninfo->proxy, vpninfo->port);
		}
#endif
		if (vpninfo->proxy) {
			hostname = vpninfo->proxy;
			snprintf(port, 6, "%d", vpninfo->proxy_port);
		} else {
			hostname = vpninfo->hostname;
			snprintf(port, 6, "%d", vpninfo->port);
		}

		if (hostname[0] == '[' && hostname[strlen(hostname)-1] == ']') {
			hostname = strndup(hostname + 1, strlen(hostname) - 2);
			if (!hostname) {
				ssl_sock = -ENOMEM;
				goto out;
			}
			hints.ai_flags |= AI_NUMERICHOST;
		}

		if (vpninfo->getaddrinfo_override)
			err = vpninfo->getaddrinfo_override(vpninfo->cbdata, hostname, port, &hints, &result);
		else
			err = getaddrinfo(hostname, port, &hints, &result);

		if (err) {
			vpn_progress(vpninfo, PRG_ERR,
				     _("getaddrinfo failed for host '%s': %s\n"),
				     hostname, gai_strerror(err));
			if (hints.ai_flags & AI_NUMERICHOST)
				free(hostname);
			ssl_sock = -EINVAL;
			/* If we were just retrying for dynamic DNS, reconnct using
			   the previously-known IP address */
			if (vpninfo->peer_addr) {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Reconnecting to DynDNS server using previously cached IP address\n"));
				goto reconnect;
			}
			goto out;
		}
		if (hints.ai_flags & AI_NUMERICHOST)
			free(hostname);

		for (rp = result; rp ; rp = rp->ai_next) {
			char host[80];

			host[0] = 0;
			if (!getnameinfo(rp->ai_addr, rp->ai_addrlen, host,
					 sizeof(host), NULL, 0, NI_NUMERICHOST))
				vpn_progress(vpninfo, PRG_DEBUG, vpninfo->proxy_type ?
						     _("Attempting to connect to proxy %s%s%s:%s\n") :
						     _("Attempting to connect to server %s%s%s:%s\n"),
					     rp->ai_family == AF_INET6 ? "[" : "",
					     host,
					     rp->ai_family == AF_INET6 ? "]" : "",
					     port);

			ssl_sock = socket(rp->ai_family, rp->ai_socktype,
					  rp->ai_protocol);
			if (ssl_sock < 0)
				continue;
			set_fd_cloexec(ssl_sock);
			err = cancellable_connect(vpninfo, ssl_sock, rp->ai_addr, rp->ai_addrlen);
			if (!err) {
				/* Store the peer address we actually used, so that DTLS can
				   use it again later */
				free(vpninfo->ip_info.gateway_addr);
				vpninfo->ip_info.gateway_addr = NULL;

				if (host[0]) {
					vpninfo->ip_info.gateway_addr = strdup(host);
					vpn_progress(vpninfo, PRG_INFO, _("Connected to %s%s%s:%s\n"),
						     rp->ai_family == AF_INET6 ? "[" : "",
						     host,
						     rp->ai_family == AF_INET6 ? "]" : "",
						     port);
				}

				free(vpninfo->peer_addr);
				vpninfo->peer_addrlen = 0;
				vpninfo->peer_addr = malloc(rp->ai_addrlen);
				if (!vpninfo->peer_addr) {
					vpn_progress(vpninfo, PRG_ERR,
						     _("Failed to allocate sockaddr storage\n"));
					closesocket(ssl_sock);
					ssl_sock = -ENOMEM;
					goto out;
				}
				vpninfo->peer_addrlen = rp->ai_addrlen;
				memcpy(vpninfo->peer_addr, rp->ai_addr, rp->ai_addrlen);
				/* If no proxy, ensure that we output *this* IP address in
				 * authentication results because we're going to need to
				 * reconnect to the *same* server from the rotation. And with
				 * some trick DNS setups, it might possibly be a "rotation"
				 * even if we only got one result from getaddrinfo() this
				 * time.
				 *
				 * If there's a proxy, we're kind of screwed; we can't know
				 * which IP address we connected to. Perhaps we ought to do
				 * the DNS lookup locally and connect to a specific IP? */
				if (!vpninfo->proxy && host[0]) {
					char *p = malloc(strlen(host) + 3);
					if (p) {
						free(vpninfo->unique_hostname);
						vpninfo->unique_hostname = p;
						if (rp->ai_family == AF_INET6)
							*p++ = '[';
						memcpy(p, host, strlen(host));
						p += strlen(host);
						if (rp->ai_family == AF_INET6)
							*p++ = ']';
						*p = 0;
					}
				}
				break;
			}
			if (host[0]) {
				char *errstr;
#ifdef _WIN32
				if (err > 0)
					errstr = openconnect__win32_strerror(err);
				else
#endif
					errstr = strerror(-err);

				vpn_progress(vpninfo, PRG_INFO, _("Failed to connect to %s%s%s:%s: %s\n"),
					     rp->ai_family == AF_INET6 ? "[" : "",
					     host,
					     rp->ai_family == AF_INET6 ? "]" : "",
					     port, errstr);
#ifdef _WIN32
				if (err > 0)
					free(errstr);
#endif
			}
			closesocket(ssl_sock);
			ssl_sock = -1;

			/* If we're in DynDNS mode but this *was* the cached IP address,
			 * don't bother falling back to it if it didn't work. */
			if (vpninfo->peer_addr && vpninfo->peer_addrlen == rp->ai_addrlen &&
			    match_sockaddr(vpninfo->peer_addr, rp->ai_addr)) {
				vpn_progress(vpninfo, PRG_TRACE,
					     _("Forgetting non-functional previous peer address\n"));
				free(vpninfo->peer_addr);
				vpninfo->peer_addr = 0;
				vpninfo->peer_addrlen = 0;
				free(vpninfo->ip_info.gateway_addr);
				vpninfo->ip_info.gateway_addr = NULL;
			}
		}
		freeaddrinfo(result);

		if (ssl_sock < 0) {
			vpn_progress(vpninfo, PRG_ERR,
				     _("Failed to connect to host %s\n"),
				     vpninfo->proxy?:vpninfo->hostname);
			ssl_sock = -EINVAL;
			if (vpninfo->peer_addr) {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Reconnecting to DynDNS server using previously cached IP address\n"));
				goto reconnect;
			}
			goto out;
		}
	}