mirror of
https://github.com/rkd77/elinks.git
synced 2025-10-21 19:54:04 -04:00
613 lines
17 KiB
C
613 lines
17 KiB
C
/* SSL socket workshop */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef CONFIG_OPENSSL
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/x509v3.h>
|
|
#define USE_OPENSSL
|
|
#elif defined(CONFIG_NSS_COMPAT_OSSL)
|
|
#include <nss_compat_ossl/nss_compat_ossl.h>
|
|
#define USE_OPENSSL
|
|
#elif defined(CONFIG_GNUTLS)
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/x509.h>
|
|
#else
|
|
#error "Huh?! You have SSL enabled, but not OPENSSL nor GNUTLS!! And then you want exactly *what* from me?"
|
|
#endif
|
|
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "config/options.h"
|
|
#include "main/select.h"
|
|
#include "network/connection.h"
|
|
#include "network/socket.h"
|
|
#include "network/ssl/match-hostname.h"
|
|
#include "network/ssl/socket.h"
|
|
#include "network/ssl/ssl.h"
|
|
#include "protocol/uri.h"
|
|
#include "util/memory.h"
|
|
|
|
|
|
/* SSL errors */
|
|
#ifdef USE_OPENSSL
|
|
#define SSL_ERROR_WANT_READ2 9999 /* XXX */
|
|
#define SSL_ERROR_WANT_WRITE2 SSL_ERROR_WANT_WRITE
|
|
#define SSL_ERROR_SYSCALL2 SSL_ERROR_SYSCALL
|
|
#elif defined(CONFIG_GNUTLS)
|
|
#define SSL_ERROR_NONE GNUTLS_E_SUCCESS
|
|
#define SSL_ERROR_WANT_READ GNUTLS_E_AGAIN
|
|
#define SSL_ERROR_WANT_READ2 GNUTLS_E_INTERRUPTED
|
|
#define SSL_ERROR_WANT_WRITE GNUTLS_E_AGAIN
|
|
#define SSL_ERROR_WANT_WRITE2 GNUTLS_E_INTERRUPTED
|
|
#define SSL_ERROR_SYSCALL GNUTLS_E_PUSH_ERROR
|
|
#define SSL_ERROR_SYSCALL2 GNUTLS_E_PULL_ERROR
|
|
#endif
|
|
|
|
#ifdef USE_OPENSSL
|
|
|
|
#define ssl_do_connect(socket) SSL_get_error((SSL *)socket->ssl, SSL_connect((SSL *)socket->ssl))
|
|
#define ssl_do_write(socket, data, len) SSL_write((SSL *)socket->ssl, data, len)
|
|
#define ssl_do_read(socket, data, len) SSL_read((SSL *)socket->ssl, data, len)
|
|
#define ssl_do_close(socket) /* Hmh? No idea.. */
|
|
|
|
#elif defined(CONFIG_GNUTLS)
|
|
|
|
#define ssl_do_write(socket, data, len) gnutls_record_send(*((ssl_t *) socket->ssl), data, len)
|
|
#define ssl_do_read(socket, data, len) gnutls_record_recv(*((ssl_t *) socket->ssl), data, len)
|
|
/* We probably don't handle this entirely correctly.. */
|
|
#define ssl_do_close(socket) gnutls_bye(*((ssl_t *) socket->ssl), GNUTLS_SHUT_RDWR);
|
|
|
|
#endif
|
|
|
|
|
|
/* Refuse to negotiate TLS 1.0 and later protocols on @socket->ssl.
|
|
* Without this, connecting to <https://www-s.uiuc.edu/> with GnuTLS
|
|
* 1.3.5 would result in an SSL error. The bug may be in the server
|
|
* (Netscape-Enterprise/3.6 SP3), in GnuTLS, or in ELinks; please log
|
|
* your findings to ELinks bug 712. */
|
|
static void
|
|
ssl_set_no_tls(struct socket *socket)
|
|
{
|
|
#ifdef CONFIG_OPENSSL
|
|
#ifdef SSL_OP_NO_TLSv1
|
|
SSL_set_options((ssl_t *)socket->ssl, SSL_OP_NO_TLSv1);
|
|
#endif
|
|
#elif defined(CONFIG_GNUTLS)
|
|
/* There is another gnutls_priority_set_direct call elsewhere
|
|
* in ELinks. If you change the priorities here, please check
|
|
* whether that one needs to be changed as well.
|
|
*
|
|
* GnuTLS 2.12.x is said to support "-VERS-TLS-ALL" too, but
|
|
* that version hasn't yet been released as of May 2011. */
|
|
gnutls_priority_set_direct(*(ssl_t *) socket->ssl,
|
|
"SECURE:-CTYPE-OPENPGP"
|
|
":-VERS-SSL3.0:-VERS-TLS1.0"
|
|
":-VERS-TLS1.1:-VERS-TLS1.2"
|
|
":%SSL3_RECORD_VERSION",
|
|
NULL);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_GNUTLS
|
|
static int
|
|
verify_certificates(struct socket *socket)
|
|
{
|
|
gnutls_x509_crt_t cert;
|
|
gnutls_session_t session = *(ssl_t *)socket->ssl;
|
|
struct connection *conn = socket->conn;
|
|
const gnutls_datum_t *cert_list;
|
|
char *hostname;
|
|
int ret;
|
|
unsigned int cert_list_size, status;
|
|
|
|
|
|
ret = gnutls_certificate_verify_peers2(session, &status);
|
|
if (ret) return ret;
|
|
if (status) return status;
|
|
|
|
/* If the certificate is of a type for which verification has
|
|
* not yet been implemented, then reject it. This way, a fake
|
|
* server cannot avoid verification by using a strange type of
|
|
* certificate.
|
|
*
|
|
* OpenPGP certificates shouldn't even get this far anyway,
|
|
* because init_ssl_connection() tells GnuTLS to disable
|
|
* OpenPGP, and ELinks never calls
|
|
* gnutls_certificate_set_openpgp_keyring_file, so status
|
|
* should have been GNUTLS_CERT_SIGNER_NOT_FOUND. */
|
|
if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
|
|
return -7;
|
|
|
|
if (gnutls_x509_crt_init(&cert) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
|
|
if (!cert_list) {
|
|
return -2;
|
|
}
|
|
|
|
if (gnutls_x509_crt_import(cert, &cert_list[0],
|
|
GNUTLS_X509_FMT_DER) < 0) {
|
|
return -3;
|
|
}
|
|
if (gnutls_x509_crt_get_expiration_time(cert) < time(NULL)) {
|
|
gnutls_x509_crt_deinit(cert);
|
|
return -4;
|
|
}
|
|
|
|
if (gnutls_x509_crt_get_activation_time(cert) > time(NULL)) {
|
|
gnutls_x509_crt_deinit(cert);
|
|
return -5;
|
|
}
|
|
|
|
/* Because RFC 5280 defines dNSName as an IA5String, it can
|
|
* only contain ASCII characters. Internationalized domain
|
|
* names must thus be in Punycode form. Because GnuTLS 2.8.6
|
|
* does not itself support IDN, ELinks must convert. */
|
|
hostname = get_uri_string(conn->proxied_uri, URI_DNS_HOST);
|
|
if (!hostname) return -6;
|
|
|
|
ret = !gnutls_x509_crt_check_hostname(cert, hostname);
|
|
gnutls_x509_crt_deinit(cert);
|
|
mem_free(hostname);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_GNUTLS */
|
|
|
|
#ifdef USE_OPENSSL
|
|
|
|
/** Checks whether the host component of a URI matches a host name in
|
|
* the server certificate.
|
|
*
|
|
* @param[in] uri_host
|
|
* The host name (or IP address) to which the user wanted to connect.
|
|
* Should be in UTF-8.
|
|
* @param[in] cert_host_asn1
|
|
* A host name found in the server certificate: either as commonName
|
|
* in the subject field, or as a dNSName in the subjectAltName
|
|
* extension. This may contain wildcards, as specified in RFC 2818
|
|
* section 3.1.
|
|
*
|
|
* @return
|
|
* Nonzero if the host matches. Zero if it doesn't, or on error.
|
|
*
|
|
* If @a uri_host is an IP address literal rather than a host name,
|
|
* then this function returns 0, meaning that the host name does not match.
|
|
* According to RFC 2818, if the certificate is intended to match an
|
|
* IP address, then it must have that IP address as an iPAddress
|
|
* SubjectAltName, rather than in commonName. For comparing those,
|
|
* match_uri_host_ip() must be used instead of this function. */
|
|
static int
|
|
match_uri_host_name(const char *uri_host,
|
|
ASN1_STRING *cert_host_asn1)
|
|
{
|
|
const size_t uri_host_len = strlen(uri_host);
|
|
unsigned char *cert_host = NULL;
|
|
int cert_host_len;
|
|
int matched = 0;
|
|
|
|
if (is_ip_address(uri_host, uri_host_len))
|
|
goto mismatch;
|
|
|
|
/* This function is used for both dNSName and commonName.
|
|
* Although dNSName is always an IA5 string, commonName allows
|
|
* many other encodings too. Normalize all to UTF-8. */
|
|
cert_host_len = ASN1_STRING_to_UTF8(&cert_host,
|
|
cert_host_asn1);
|
|
if (cert_host_len < 0)
|
|
goto mismatch;
|
|
|
|
matched = match_hostname_pattern(uri_host, uri_host_len,
|
|
(char *)cert_host, cert_host_len);
|
|
|
|
mismatch:
|
|
if (cert_host)
|
|
OPENSSL_free(cert_host);
|
|
return matched;
|
|
}
|
|
|
|
/** Checks whether the host component of a URI matches an IP address
|
|
* in the server certificate.
|
|
*
|
|
* @param[in] uri_host
|
|
* The IP address (or host name) to which the user wanted to connect.
|
|
* Should be in UTF-8.
|
|
* @param[in] cert_host_asn1
|
|
* An IP address found as iPAddress in the subjectAltName extension
|
|
* of the server certificate. According to RFC 5280 section 4.2.1.6,
|
|
* that is an octet string in network byte order. According to
|
|
* RFC 2818 section 3.1, wildcards are not allowed.
|
|
*
|
|
* @return
|
|
* Nonzero if the host matches. Zero if it doesn't, or on error.
|
|
*
|
|
* If @a uri_host is a host name rather than an IP address literal,
|
|
* then this function returns 0, meaning that the address does not match.
|
|
* This function does not try to resolve the host name to an IP address
|
|
* and compare that to @a cert_host_asn1, because such an approach would
|
|
* be vulnerable to DNS spoofing.
|
|
*
|
|
* This function does not support the address-and-netmask format used
|
|
* in the name constraints extension of a CA certificate (RFC 5280
|
|
* section 4.2.1.10). */
|
|
static int
|
|
match_uri_host_ip(const char *uri_host,
|
|
ASN1_OCTET_STRING *cert_host_asn1)
|
|
{
|
|
#ifdef HAVE_ASN1_STRING_GET0_DATA
|
|
const unsigned char *cert_host_addr = ASN1_STRING_get0_data(cert_host_asn1);
|
|
#else
|
|
const unsigned char *cert_host_addr = ASN1_STRING_data(cert_host_asn1);
|
|
#endif
|
|
|
|
struct in_addr uri_host_in;
|
|
#ifdef CONFIG_IPV6
|
|
struct in6_addr uri_host_in6;
|
|
#endif
|
|
|
|
/* RFC 5280 defines the iPAddress alternative of GeneralName
|
|
* as an OCTET STRING. Verify that the type is indeed that.
|
|
* This is an assertion because, if someone puts the wrong
|
|
* type of data there, then it will not even be recognized as
|
|
* an iPAddress, and this function will not be called.
|
|
*
|
|
* (Because GeneralName is defined in an implicitly tagged
|
|
* ASN.1 module, the OCTET STRING tag is not part of the DER
|
|
* encoding. BER also allows a constructed encoding where
|
|
* each substring begins with the OCTET STRING tag; but ITU-T
|
|
* Rec. X.690 (07/2002) subclause 8.21 says those would be
|
|
* OCTET STRING even if the outer string were of some other
|
|
* type. "A Layman's Guide to a Subset of ASN.1, BER, and
|
|
* DER" (Kaliski, 1993) claims otherwise, though.) */
|
|
assert(ASN1_STRING_type(cert_host_asn1) == V_ASN1_OCTET_STRING);
|
|
if_assert_failed return 0;
|
|
|
|
/* cert_host_addr, url_host_in, and url_host_in6 are all in
|
|
* network byte order. */
|
|
switch (ASN1_STRING_length(cert_host_asn1)) {
|
|
case 4:
|
|
return inet_aton(uri_host, &uri_host_in) != 0
|
|
&& memcmp(cert_host_addr, &uri_host_in.s_addr, 4) == 0;
|
|
|
|
#ifdef CONFIG_IPV6
|
|
case 16:
|
|
return inet_pton(AF_INET6, uri_host, &uri_host_in6) == 1
|
|
&& memcmp(cert_host_addr, &uri_host_in6.s6_addr, 16) == 0;
|
|
#endif
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/** Verify one certificate in the server certificate chain.
|
|
* This callback is documented in SSL_set_verify(3). */
|
|
static int
|
|
verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
|
|
{
|
|
X509 *cert;
|
|
SSL *ssl;
|
|
struct socket *socket;
|
|
struct connection *conn;
|
|
char *host_in_uri;
|
|
GENERAL_NAMES *alts;
|
|
int saw_dns_name = 0;
|
|
int matched = 0;
|
|
|
|
/* If OpenSSL already found a problem, keep that. */
|
|
if (!preverify_ok)
|
|
return 0;
|
|
|
|
/* Examine only the server certificate, not CA certificates. */
|
|
if (X509_STORE_CTX_get_error_depth(ctx) != 0)
|
|
return preverify_ok;
|
|
|
|
cert = X509_STORE_CTX_get_current_cert(ctx);
|
|
ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
socket = (struct socket *)SSL_get_ex_data(ssl, socket_SSL_ex_data_idx);
|
|
conn = (struct connection *)socket->conn;
|
|
host_in_uri = get_uri_string(conn->proxied_uri, URI_DNS_HOST);
|
|
if (!host_in_uri)
|
|
return 0;
|
|
|
|
/* RFC 5280 section 4.2.1.6 describes the subjectAltName extension.
|
|
* RFC 2818 section 3.1 says Common Name must not be used
|
|
* if dNSName is present. */
|
|
alts = (GENERAL_NAMES *)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
|
if (alts != NULL) {
|
|
int alt_count;
|
|
int alt_pos;
|
|
GENERAL_NAME *alt;
|
|
|
|
alt_count = sk_GENERAL_NAME_num(alts);
|
|
for (alt_pos = 0; !matched && alt_pos < alt_count; ++alt_pos) {
|
|
alt = sk_GENERAL_NAME_value(alts, alt_pos);
|
|
if (alt->type == GEN_DNS) {
|
|
saw_dns_name = 1;
|
|
matched = match_uri_host_name(host_in_uri,
|
|
alt->d.dNSName);
|
|
} else if (alt->type == GEN_IPADD) {
|
|
matched = match_uri_host_ip(host_in_uri,
|
|
alt->d.iPAddress);
|
|
}
|
|
}
|
|
|
|
/* Free the GENERAL_NAMES list and each element. */
|
|
sk_GENERAL_NAME_pop_free(alts, GENERAL_NAME_free);
|
|
}
|
|
|
|
if (!matched && !saw_dns_name) {
|
|
X509_NAME *name;
|
|
int cn_index;
|
|
X509_NAME_ENTRY *entry = NULL;
|
|
|
|
name = X509_get_subject_name(cert);
|
|
cn_index = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
|
|
if (cn_index >= 0)
|
|
entry = X509_NAME_get_entry(name, cn_index);
|
|
if (entry != NULL)
|
|
matched = match_uri_host_name(host_in_uri,
|
|
X509_NAME_ENTRY_get_data(entry));
|
|
}
|
|
|
|
mem_free(host_in_uri);
|
|
return matched;
|
|
}
|
|
|
|
#endif /* USE_OPENSSL */
|
|
|
|
#if defined(CONFIG_GNUTLS)
|
|
static int
|
|
ssl_do_connect(struct socket *socket)
|
|
{
|
|
int ret;
|
|
|
|
gnutls_handshake_set_timeout(*(ssl_t *)(socket->ssl), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
|
|
|
|
do {
|
|
ret = gnutls_handshake(*(ssl_t *)socket->ssl);
|
|
} while (ret < 0 && !gnutls_error_is_fatal(ret));
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ssl_want_read(struct socket *socket)
|
|
{
|
|
if (socket->no_tls)
|
|
ssl_set_no_tls(socket);
|
|
|
|
switch (ssl_do_connect(socket)) {
|
|
case SSL_ERROR_NONE:
|
|
#ifdef CONFIG_GNUTLS
|
|
if (socket->verify && get_opt_bool("connection.ssl.cert_verify", NULL)
|
|
&& verify_certificates(socket)) {
|
|
socket->ops->retry(socket, connection_state(S_SSL_ERROR));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Report successful SSL connection setup. */
|
|
complete_connect_socket(socket, NULL, NULL);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_READ2:
|
|
break;
|
|
|
|
default:
|
|
socket->no_tls = !socket->no_tls;
|
|
socket->ops->retry(socket, connection_state(S_SSL_ERROR));
|
|
}
|
|
}
|
|
|
|
/* Return -1 on error, 0 or success. */
|
|
int
|
|
ssl_connect(struct socket *socket)
|
|
{
|
|
int ret;
|
|
char *server_name;
|
|
struct connection *conn = (struct connection *)socket->conn;
|
|
|
|
/* TODO: Recode server_name to UTF-8. */
|
|
server_name = get_uri_string(conn->proxied_uri, URI_HOST);
|
|
|
|
if (!server_name) {
|
|
socket->ops->done(socket, connection_state(S_OUT_OF_MEM));
|
|
return -1;
|
|
}
|
|
|
|
/* RFC 3546 says literal IPv4 and IPv6 addresses are not allowed. */
|
|
if (is_ip_address(server_name, strlen(server_name)))
|
|
mem_free_set(&server_name, NULL);
|
|
|
|
if (init_ssl_connection(socket, server_name) == S_SSL_ERROR) {
|
|
mem_free_if(server_name);
|
|
socket->ops->done(socket, connection_state(S_SSL_ERROR));
|
|
return -1;
|
|
}
|
|
|
|
mem_free_if(server_name);
|
|
|
|
if (socket->no_tls)
|
|
ssl_set_no_tls(socket);
|
|
|
|
#ifdef USE_OPENSSL
|
|
SSL_set_fd((SSL *)socket->ssl, socket->fd);
|
|
|
|
if (socket->verify && get_opt_bool("connection.ssl.cert_verify", NULL))
|
|
SSL_set_verify((SSL *)socket->ssl, SSL_VERIFY_PEER
|
|
| SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
verify_callback);
|
|
|
|
if (get_opt_bool("connection.ssl.client_cert.enable", NULL)) {
|
|
char *client_cert;
|
|
|
|
#ifdef CONFIG_NSS_COMPAT_OSSL
|
|
client_cert = get_opt_str(
|
|
"connection.ssl.client_cert.nickname", NULL);
|
|
#else
|
|
client_cert = get_opt_str(
|
|
"connection.ssl.client_cert.file", NULL);
|
|
#endif
|
|
if (!*client_cert) {
|
|
client_cert = getenv("X509_CLIENT_CERT");
|
|
if (client_cert && !*client_cert)
|
|
client_cert = NULL;
|
|
}
|
|
|
|
if (client_cert) {
|
|
#ifdef CONFIG_NSS_COMPAT_OSSL
|
|
SSL_CTX_use_certificate_chain_file(
|
|
(SSL *) socket->ssl,
|
|
client_cert);
|
|
#else
|
|
SSL_CTX *ctx = SSL_get_SSL_CTX((SSL *) socket->ssl);
|
|
|
|
SSL_CTX_use_certificate_chain_file(ctx, client_cert);
|
|
SSL_CTX_use_PrivateKey_file(ctx, client_cert,
|
|
SSL_FILETYPE_PEM);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#elif defined(CONFIG_GNUTLS)
|
|
/* GnuTLS uses function pointers for network I/O. The default
|
|
* functions take a file descriptor, but it must be passed in
|
|
* as a pointer. GnuTLS uses the GNUTLS_INT_TO_POINTER and
|
|
* GNUTLS_POINTER_TO_INT macros for these conversions, but
|
|
* those are unfortunately not in any public header. So
|
|
* ELinks must just cast the pointer the best it can and hope
|
|
* that the conversions match. */
|
|
gnutls_transport_set_ptr(*((ssl_t *) socket->ssl),
|
|
(gnutls_transport_ptr_t) (longptr_T) socket->fd);
|
|
|
|
/* TODO: Some certificates fuss. --pasky */
|
|
#endif
|
|
|
|
ret = ssl_do_connect(socket);
|
|
|
|
switch (ret) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_READ2:
|
|
socket->ops->set_state(socket, connection_state(S_SSL_NEG));
|
|
set_handlers(socket->fd, (select_handler_T) ssl_want_read,
|
|
NULL, (select_handler_T) dns_exception, socket);
|
|
return -1;
|
|
|
|
case SSL_ERROR_NONE:
|
|
#ifdef CONFIG_GNUTLS
|
|
if (!socket->verify || !get_opt_bool("connection.ssl.cert_verify", NULL))
|
|
break;
|
|
|
|
if (!verify_certificates(socket))
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
if (ret != SSL_ERROR_NONE) {
|
|
/* DBG("sslerr %s", gnutls_strerror(ret)); */
|
|
socket->no_tls = !socket->no_tls;
|
|
}
|
|
connect_socket(socket, connection_state(S_SSL_ERROR));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Return enum socket_error on error, bytes written on success. */
|
|
ssize_t
|
|
ssl_write(struct socket *socket, char *data, int len)
|
|
{
|
|
ssize_t wr = ssl_do_write(socket, data, len);
|
|
|
|
if (wr <= 0) {
|
|
#ifdef USE_OPENSSL
|
|
int err = SSL_get_error((SSL *)socket->ssl, wr);
|
|
#elif defined(CONFIG_GNUTLS)
|
|
int err = wr;
|
|
#endif
|
|
if (err == SSL_ERROR_WANT_WRITE ||
|
|
err == SSL_ERROR_WANT_WRITE2) {
|
|
return -1;
|
|
}
|
|
|
|
if (!wr) return SOCKET_CANT_WRITE;
|
|
|
|
if (err == SSL_ERROR_SYSCALL)
|
|
return SOCKET_SYSCALL_ERROR;
|
|
|
|
errno = S_SSL_ERROR;
|
|
return SOCKET_INTERNAL_ERROR;
|
|
}
|
|
|
|
return wr;
|
|
}
|
|
|
|
/* Return enum socket_error on error, bytes read on success. */
|
|
ssize_t
|
|
ssl_read(struct socket *socket, char *data, int len)
|
|
{
|
|
ssize_t rd = ssl_do_read(socket, data, len);
|
|
|
|
if (rd <= 0) {
|
|
#ifdef USE_OPENSSL
|
|
int err = SSL_get_error((SSL *)socket->ssl, rd);
|
|
#elif defined(CONFIG_GNUTLS)
|
|
int err = rd;
|
|
#endif
|
|
|
|
#ifdef CONFIG_GNUTLS
|
|
if (err == GNUTLS_E_PREMATURE_TERMINATION) {
|
|
return SOCKET_CANT_READ;
|
|
}
|
|
|
|
if (err == GNUTLS_E_REHANDSHAKE) {
|
|
err = ssl_do_connect(socket);
|
|
if (err == 0) {
|
|
return SOCKET_SSL_WANT_READ;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (err == SSL_ERROR_WANT_READ ||
|
|
err == SSL_ERROR_WANT_READ2) {
|
|
return SOCKET_SSL_WANT_READ;
|
|
}
|
|
|
|
if (!rd) return SOCKET_CANT_READ;
|
|
|
|
if (err == SSL_ERROR_SYSCALL2)
|
|
return SOCKET_SYSCALL_ERROR;
|
|
|
|
errno = S_SSL_ERROR;
|
|
return SOCKET_INTERNAL_ERROR;
|
|
}
|
|
|
|
return rd;
|
|
}
|
|
|
|
int
|
|
ssl_close(struct socket *socket)
|
|
{
|
|
ssl_do_close(socket);
|
|
done_ssl_connection(socket);
|
|
|
|
return 0;
|
|
}
|