123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802 |
- /*-
- * Copyright 2016 Vsevolod Stakhov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- #include "config.h"
- #include "libutil/util.h"
- #include "libutil/logger.h"
- #include "ssl_util.h"
-
- #include <openssl/ssl.h>
- #include <openssl/err.h>
- #include <openssl/rand.h>
- #include <openssl/conf.h>
- #include <openssl/x509v3.h>
-
- enum rspamd_ssl_state {
- ssl_conn_reset = 0,
- ssl_conn_init,
- ssl_conn_connected,
- ssl_next_read,
- ssl_next_write
- };
-
- enum rspamd_ssl_shutdown {
- ssl_shut_default = 0,
- ssl_shut_unclean,
- };
-
- struct rspamd_ssl_connection {
- gint fd;
- enum rspamd_ssl_state state;
- enum rspamd_ssl_shutdown shut;
- gboolean verify_peer;
- SSL *ssl;
- gchar *hostname;
- struct event *ev;
- struct event_base *ev_base;
- struct timeval *tv;
- rspamd_ssl_handler_t handler;
- rspamd_ssl_error_handler_t err_handler;
- gpointer handler_data;
- };
-
- static GQuark
- rspamd_ssl_quark (void)
- {
- return g_quark_from_static_string ("rspamd-ssl");
- }
-
- #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER)
- #ifndef X509_get_notBefore
- #define X509_get_notBefore(x) X509_get0_notBefore(x)
- #endif
- #ifndef X509_get_notAfter
- #define X509_get_notAfter(x) X509_get0_notAfter(x)
- #endif
- #ifndef ASN1_STRING_data
- #define ASN1_STRING_data(x) ASN1_STRING_get0_data(x)
- #endif
- #endif
-
- /* $OpenBSD: tls_verify.c,v 1.14 2015/09/29 10:17:04 deraadt Exp $ */
- /*
- * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
- *
- * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
- */
-
- static gboolean
- rspamd_tls_match_name (const char *cert_name, const char *name)
- {
- const char *cert_domain, *domain, *next_dot;
-
- if (g_ascii_strcasecmp (cert_name, name) == 0) {
- return TRUE;
- }
-
- /* Wildcard match? */
- if (cert_name[0] == '*') {
- /*
- * Valid wildcards:
- * - "*.domain.tld"
- * - "*.sub.domain.tld"
- * - etc.
- * Reject "*.tld".
- * No attempt to prevent the use of eg. "*.co.uk".
- */
- cert_domain = &cert_name[1];
- /* Disallow "*" */
- if (cert_domain[0] == '\0') {
- return FALSE;
- }
-
- /* Disallow "*foo" */
- if (cert_domain[0] != '.') {
- return FALSE;
- }
- /* Disallow "*.." */
- if (cert_domain[1] == '.') {
- return FALSE;
- }
- next_dot = strchr (&cert_domain[1], '.');
- /* Disallow "*.bar" */
- if (next_dot == NULL) {
- return FALSE;
- }
- /* Disallow "*.bar.." */
- if (next_dot[1] == '.') {
- return FALSE;
- }
-
- domain = strchr (name, '.');
-
- /* No wildcard match against a name with no host part. */
- if (name[0] == '.') {
- return FALSE;
- }
- /* No wildcard match against a name with no domain part. */
- if (domain == NULL || strlen (domain) == 1) {
- return FALSE;
- }
-
- if (g_ascii_strcasecmp (cert_domain, domain) == 0) {
- return TRUE;
- }
- }
-
- return FALSE;
- }
-
- /* See RFC 5280 section 4.2.1.6 for SubjectAltName details. */
- static gboolean
- rspamd_tls_check_subject_altname (X509 *cert, const char *name)
- {
- STACK_OF(GENERAL_NAME) *altname_stack = NULL;
- int addrlen, type;
- int count, i;
- union {
- struct in_addr ip4;
- struct in6_addr ip6;
- } addrbuf;
- gboolean ret = FALSE;
-
- altname_stack = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
-
- if (altname_stack == NULL) {
- return FALSE;
- }
-
- if (inet_pton (AF_INET, name, &addrbuf) == 1) {
- type = GEN_IPADD;
- addrlen = 4;
- }
- else if (inet_pton (AF_INET6, name, &addrbuf) == 1) {
- type = GEN_IPADD;
- addrlen = 16;
- }
- else {
- type = GEN_DNS;
- addrlen = 0;
- }
-
- count = sk_GENERAL_NAME_num (altname_stack);
-
- for (i = 0; i < count; i++) {
- GENERAL_NAME *altname;
-
- altname = sk_GENERAL_NAME_value (altname_stack, i);
-
- if (altname->type != type) {
- continue;
- }
-
- if (type == GEN_DNS) {
- const char *data;
- int format, len;
-
- format = ASN1_STRING_type (altname->d.dNSName);
-
- if (format == V_ASN1_IA5STRING) {
- data = (const char *)ASN1_STRING_data (altname->d.dNSName);
- len = ASN1_STRING_length (altname->d.dNSName);
-
- if (len < 0 || len != (gint)strlen (data)) {
- ret = FALSE;
- break;
- }
-
- /*
- * Per RFC 5280 section 4.2.1.6:
- * " " is a legal domain name, but that
- * dNSName must be rejected.
- */
- if (strcmp (data, " ") == 0) {
- ret = FALSE;
- break;
- }
-
- if (rspamd_tls_match_name (data, name)) {
- ret = TRUE;
- break;
- }
- }
- }
- else if (type == GEN_IPADD) {
- const char *data;
- int datalen;
-
- datalen = ASN1_STRING_length (altname->d.iPAddress);
- data = (const char *)ASN1_STRING_data (altname->d.iPAddress);
-
- if (datalen < 0) {
- ret = FALSE;
- break;
- }
-
- /*
- * Per RFC 5280 section 4.2.1.6:
- * IPv4 must use 4 octets and IPv6 must use 16 octets.
- */
- if (datalen == addrlen && memcmp (data, &addrbuf, addrlen) == 0) {
- ret = TRUE;
- break;
- }
- }
- }
-
- sk_GENERAL_NAME_pop_free (altname_stack, GENERAL_NAME_free);
- return ret;
- }
-
- static gboolean
- rspamd_tls_check_common_name (X509 *cert, const char *name)
- {
- X509_NAME *subject_name;
- char *common_name = NULL;
- union {
- struct in_addr ip4;
- struct in6_addr ip6;
- } addrbuf;
- int common_name_len;
- gboolean ret = FALSE;
-
- subject_name = X509_get_subject_name (cert);
- if (subject_name == NULL) {
- goto out;
- }
-
- common_name_len = X509_NAME_get_text_by_NID (subject_name, NID_commonName, NULL, 0);
-
- if (common_name_len < 0) {
- goto out;
- }
-
- common_name = g_malloc0 (common_name_len + 1);
- X509_NAME_get_text_by_NID (subject_name, NID_commonName, common_name,
- common_name_len + 1);
-
- /* NUL bytes in CN? */
- if (common_name_len != (gint)strlen (common_name)) {
- goto out;
- }
-
- if (inet_pton (AF_INET, name, &addrbuf) == 1
- || inet_pton (AF_INET6, name, &addrbuf) == 1) {
- /*
- * We don't want to attempt wildcard matching against IP
- * addresses, so perform a simple comparison here.
- */
- if (strcmp (common_name, name) == 0) {
- ret = TRUE;
- }
- else {
- ret = FALSE;
- }
-
- goto out;
- }
-
- if (rspamd_tls_match_name (common_name, name)) {
- ret = TRUE;
- }
-
- out:
- g_free (common_name);
-
- return ret;
- }
-
- static gboolean
- rspamd_tls_check_name (X509 *cert, const char *name)
- {
- gboolean ret;
-
- ret = rspamd_tls_check_subject_altname (cert, name);
- if (ret) {
- return ret;
- }
-
- return rspamd_tls_check_common_name (cert, name);
- }
-
- static gboolean
- rspamd_ssl_peer_verify (struct rspamd_ssl_connection *c)
- {
- X509 *server_cert;
- glong ver_err;
- GError *err = NULL;
-
- ver_err = SSL_get_verify_result (c->ssl);
-
- if (ver_err != X509_V_OK) {
- g_set_error (&err, rspamd_ssl_quark (), ver_err, "certificate validation "
- "failed: %s", X509_verify_cert_error_string (ver_err));
- c->err_handler (c->handler_data, err);
- g_error_free (err);
-
- return FALSE;
- }
-
- /* Get server's certificate */
- server_cert = SSL_get_peer_certificate (c->ssl);
- if (server_cert == NULL) {
- g_set_error (&err, rspamd_ssl_quark (), ver_err, "peer certificate is absent");
- c->err_handler (c->handler_data, err);
- g_error_free (err);
-
- return FALSE;
- }
-
- if (c->hostname) {
- if (!rspamd_tls_check_name (server_cert, c->hostname)) {
- X509_free (server_cert);
- g_set_error (&err, rspamd_ssl_quark (), ver_err, "peer certificate fails "
- "hostname verification for %s", c->hostname);
- c->err_handler (c->handler_data, err);
- g_error_free (err);
-
- return FALSE;
- }
- }
-
- X509_free (server_cert);
-
- return TRUE;
- }
-
- static void
- rspamd_tls_set_error (gint retcode, const gchar *stage, GError **err)
- {
- GString *reason;
- gchar buf[120];
- gint err_code = 0, last_err = 0;
-
- reason = g_string_sized_new (sizeof (buf));
-
- if (retcode == SSL_ERROR_SYSCALL) {
- rspamd_printf_gstring (reason, "syscall fail: %s", strerror (errno));
- err_code = errno;
- }
- else {
- while ((err_code = ERR_get_error()) != 0) {
- last_err = err_code;
- ERR_error_string (err_code, buf);
- rspamd_printf_gstring (reason, "ssl error: %s,", buf);
- }
-
- err_code = last_err;
-
- if (reason->str[reason->len - 1] == ',') {
- reason->str[reason->len - 1] = '\0';
- reason->len --;
- }
- }
-
- g_set_error (err, rspamd_ssl_quark (), err_code,
- "ssl %s error: %s", stage, reason->str);
- g_string_free (reason, TRUE);
- }
-
- static void
- rspamd_ssl_event_handler (gint fd, short what, gpointer ud)
- {
- struct rspamd_ssl_connection *c = ud;
- gint ret;
- GError *err = NULL;
-
- if (what == EV_TIMEOUT) {
- c->shut = ssl_shut_unclean;
- }
-
- switch (c->state) {
- case ssl_conn_init:
- /* Continue connection */
- ret = SSL_connect (c->ssl);
-
- if (ret == 1) {
- event_del (c->ev);
- /* Verify certificate */
- if ((!c->verify_peer) || rspamd_ssl_peer_verify (c)) {
- c->state = ssl_conn_connected;
- c->handler (fd, EV_WRITE, c->handler_data);
- }
- else {
- return;
- }
- }
- else {
- ret = SSL_get_error (c->ssl, ret);
-
- if (ret == SSL_ERROR_WANT_READ) {
- what = EV_READ;
- }
- else if (ret == SSL_ERROR_WANT_WRITE) {
- what = EV_WRITE;
- }
- else {
- rspamd_tls_set_error (ret, "connect", &err);
- c->err_handler (c->handler_data, err);
- g_error_free (err);
- return;
- }
-
- event_del (c->ev);
- event_set (c->ev, fd, what, rspamd_ssl_event_handler, c);
- event_base_set (c->ev_base, c->ev);
- event_add (c->ev, c->tv);
- }
- break;
- case ssl_next_read:
- event_del (c->ev);
- /* Restore handler */
- event_set (c->ev, c->fd, EV_READ|EV_PERSIST,
- c->handler, c->handler_data);
- event_base_set (c->ev_base, c->ev);
- event_add (c->ev, c->tv);
- c->state = ssl_conn_connected;
- c->handler (fd, EV_READ, c->handler_data);
- break;
- case ssl_next_write:
- case ssl_conn_connected:
- event_del (c->ev);
- /* Restore handler */
- event_set (c->ev, c->fd, EV_WRITE,
- c->handler, c->handler_data);
- event_base_set (c->ev_base, c->ev);
- event_add (c->ev, c->tv);
- c->state = ssl_conn_connected;
- c->handler (fd, EV_WRITE, c->handler_data);
- break;
- default:
- g_set_error (&err, rspamd_ssl_quark (), EINVAL,
- "ssl bad state error: %d", c->state);
- c->err_handler (c->handler_data, err);
- g_error_free (err);
- break;
- }
- }
-
- struct rspamd_ssl_connection *
- rspamd_ssl_connection_new (gpointer ssl_ctx, struct event_base *ev_base,
- gboolean verify_peer)
- {
- struct rspamd_ssl_connection *c;
-
- g_assert (ssl_ctx != NULL);
- c = g_malloc0 (sizeof (*c));
- c->ssl = SSL_new (ssl_ctx);
- c->ev_base = ev_base;
- c->verify_peer = verify_peer;
-
- return c;
- }
-
-
- gboolean
- rspamd_ssl_connect_fd (struct rspamd_ssl_connection *conn, gint fd,
- const gchar *hostname, struct event *ev, struct timeval *tv,
- rspamd_ssl_handler_t handler, rspamd_ssl_error_handler_t err_handler,
- gpointer handler_data)
- {
- gint ret;
- short what;
-
- g_assert (conn != NULL);
-
- if (conn->state != ssl_conn_reset) {
- return FALSE;
- }
-
- conn->fd = fd;
- conn->ev = ev;
- conn->handler = handler;
- conn->err_handler = err_handler;
- conn->handler_data = handler_data;
-
- if (SSL_set_fd (conn->ssl, fd) != 1) {
- return FALSE;
- }
-
- if (hostname) {
- conn->hostname = g_strdup (hostname);
- #ifdef HAVE_SSL_TLSEXT_HOSTNAME
- SSL_set_tlsext_host_name (conn->ssl, conn->hostname);
- #endif
- }
-
- conn->state = ssl_conn_init;
-
- ret = SSL_connect (conn->ssl);
-
- if (ret == 1) {
- conn->state = ssl_conn_connected;
-
- if (rspamd_event_pending (ev, EV_TIMEOUT|EV_WRITE|EV_READ)) {
- event_del (ev);
- }
-
- event_set (ev, fd, EV_WRITE, rspamd_ssl_event_handler, conn);
-
- if (conn->ev_base) {
- event_base_set (conn->ev_base, ev);
- }
-
- event_add (ev, tv);
- }
- else {
- ret = SSL_get_error (conn->ssl, ret);
-
- if (ret == SSL_ERROR_WANT_READ) {
- what = EV_READ;
- }
- else if (ret == SSL_ERROR_WANT_WRITE) {
- what = EV_WRITE;
- }
- else {
- conn->shut = ssl_shut_unclean;
-
- return FALSE;
- }
-
- if (rspamd_event_pending (ev, EV_TIMEOUT|EV_WRITE|EV_READ)) {
- event_del (ev);
- }
-
- event_set (ev, fd, what, rspamd_ssl_event_handler, conn);
- event_base_set (conn->ev_base, ev);
- event_add (ev, tv);
- }
-
- return TRUE;
- }
-
- gssize
- rspamd_ssl_read (struct rspamd_ssl_connection *conn, gpointer buf,
- gsize buflen)
- {
- gint ret;
- short what;
- GError *err = NULL;
-
- g_assert (conn != NULL);
-
- if (conn->state != ssl_conn_connected && conn->state != ssl_next_read) {
- errno = EINVAL;
- g_set_error (&err, rspamd_ssl_quark (), ECONNRESET,
- "ssl state error: cannot read data");
- conn->shut = ssl_shut_unclean;
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
-
- return -1;
- }
-
- ret = SSL_read (conn->ssl, buf, buflen);
-
- if (ret > 0) {
- conn->state = ssl_conn_connected;
- return ret;
- }
- else if (ret == 0) {
- ret = SSL_get_error (conn->ssl, ret);
-
- if (ret == SSL_ERROR_ZERO_RETURN || ret == SSL_ERROR_SYSCALL) {
- conn->state = ssl_conn_reset;
- return 0;
- }
- else {
- conn->shut = ssl_shut_unclean;
- rspamd_tls_set_error (ret, "read", &err);
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
- errno = EINVAL;
-
- return -1;
- }
- }
- else {
- ret = SSL_get_error (conn->ssl, ret);
- conn->state = ssl_next_read;
- what = 0;
-
- if (ret == SSL_ERROR_WANT_READ) {
- what |= EV_READ;
- }
- else if (ret == SSL_ERROR_WANT_WRITE) {
- what |= EV_WRITE;
- }
- else {
- conn->shut = ssl_shut_unclean;
- rspamd_tls_set_error (ret, "read", &err);
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
- errno = EINVAL;
-
- return -1;
- }
-
- event_del (conn->ev);
- event_set (conn->ev, conn->fd, what, rspamd_ssl_event_handler, conn);
- event_base_set (conn->ev_base, conn->ev);
- event_add (conn->ev, conn->tv);
-
- errno = EAGAIN;
-
- }
-
- return -1;
- }
-
- gssize
- rspamd_ssl_write (struct rspamd_ssl_connection *conn, gconstpointer buf,
- gsize buflen)
- {
- gint ret;
- short what;
- GError *err = NULL;
-
- g_assert (conn != NULL);
-
- if (conn->state != ssl_conn_connected && conn->state != ssl_next_write) {
- errno = EINVAL;
- return -1;
- }
-
- ret = SSL_write (conn->ssl, buf, buflen);
-
- if (ret > 0) {
- conn->state = ssl_conn_connected;
- return ret;
- }
- else if (ret == 0) {
- ret = SSL_get_error (conn->ssl, ret);
-
- if (ret == SSL_ERROR_ZERO_RETURN) {
- rspamd_tls_set_error (ret, "write", &err);
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
- errno = ECONNRESET;
- conn->state = ssl_conn_reset;
-
- return -1;
- }
- else {
- conn->shut = ssl_shut_unclean;
- rspamd_tls_set_error (ret, "write", &err);
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
- errno = EINVAL;
-
- return -1;
- }
- }
- else {
- ret = SSL_get_error (conn->ssl, ret);
- conn->state = ssl_next_read;
-
- if (ret == SSL_ERROR_WANT_READ) {
- what = EV_READ;
- }
- else if (ret == SSL_ERROR_WANT_WRITE) {
- what = EV_WRITE;
- }
- else {
- conn->shut = ssl_shut_unclean;
- rspamd_tls_set_error (ret, "write", &err);
- conn->err_handler (conn->handler_data, err);
- g_error_free (err);
- errno = EINVAL;
-
- return -1;
- }
-
- event_del (conn->ev);
- event_set (conn->ev, conn->fd, what, rspamd_ssl_event_handler, conn);
- event_base_set (conn->ev_base, conn->ev);
- event_add (conn->ev, conn->tv);
-
- errno = EAGAIN;
- }
-
- return -1;
- }
-
- gssize
- rspamd_ssl_writev (struct rspamd_ssl_connection *conn, struct iovec *iov,
- gsize iovlen)
- {
- static guchar ssl_buf[16000];
- guchar *p;
- struct iovec *cur;
- guint i, remain;
-
- remain = sizeof (ssl_buf);
- p = ssl_buf;
-
- for (i = 0; i < iovlen; i ++) {
- cur = &iov[i];
-
- if (cur->iov_len > 0) {
- if (remain >= cur->iov_len) {
- memcpy (p, cur->iov_base, cur->iov_len);
- p += cur->iov_len;
- remain -= cur->iov_len;
- }
- else {
- memcpy (p, cur->iov_base, remain);
- p += remain;
- remain = 0;
- break;
- }
- }
- }
-
- return rspamd_ssl_write (conn, ssl_buf, p - ssl_buf);
- }
-
- /**
- * Removes connection data
- * @param conn
- */
- void
- rspamd_ssl_connection_free (struct rspamd_ssl_connection *conn)
- {
- if (conn) {
- /*
- * SSL_RECEIVED_SHUTDOWN tells SSL_shutdown to act as if we had already
- * received a close notify from the other end. SSL_shutdown will then
- * send the final close notify in reply. The other end will receive the
- * close notify and send theirs. By this time, we will have already
- * closed the socket and the other end's real close notify will never be
- * received. In effect, both sides will think that they have completed a
- * clean shutdown and keep their sessions valid. This strategy will fail
- * if the socket is not ready for writing, in which case this hack will
- * lead to an unclean shutdown and lost session on the other end.
- */
- if (conn->shut == ssl_shut_unclean) {
- SSL_set_shutdown (conn->ssl, SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN);
- SSL_set_quiet_shutdown (conn->ssl, 1);
- }
- else {
- SSL_set_shutdown (conn->ssl, SSL_RECEIVED_SHUTDOWN);
- }
-
- /* Stupid hack to enforce SSL to do shutdown sequence */
- for (guint i = 0; i < 4; i++) {
- if (SSL_shutdown (conn->ssl)) {
- break;
- }
- }
-
- SSL_free (conn->ssl);
-
- if (conn->hostname) {
- g_free (conn->hostname);
- }
-
- g_free (conn);
- }
- }
|