#include "connect.h"
#include "url.h"
#include "ptimer.h"
+#include "hash.h"
#include "ssl.h"
#include <sys/fcntl.h>
{
/* Becomes true if GnuTLS is initialized. */
static bool ssl_initialized = false;
+ const char *ca_directory;
+ DIR *dir;
+ int ncerts = -1;
/* GnuTLS should be initialized only once. */
if (ssl_initialized)
return true;
- const char *ca_directory;
- DIR *dir;
-
gnutls_global_init ();
gnutls_certificate_allocate_credentials (&credentials);
- gnutls_certificate_set_verify_flags(credentials,
- GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
+ gnutls_certificate_set_verify_flags (credentials,
+ GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
- ca_directory = opt.ca_directory ? opt.ca_directory : "/etc/ssl/certs";
+#if GNUTLS_VERSION_MAJOR >= 3
+ if (!opt.ca_directory)
+ ncerts = gnutls_certificate_set_x509_system_trust (credentials);
+#endif
- dir = opendir (ca_directory);
- if (dir == NULL)
+ /* If GnuTLS version is too old or CA loading failed, fallback to old behaviour.
+ * Also use old behaviour if the CA directory is user-provided. */
+ if (ncerts <= 0)
{
- if (opt.ca_directory && *opt.ca_directory)
- logprintf (LOG_NOTQUIET, _("ERROR: Cannot open directory %s.\n"),
- opt.ca_directory);
- }
- else
- {
- struct dirent *dent;
- while ((dent = readdir (dir)) != NULL)
+ ca_directory = opt.ca_directory ? opt.ca_directory : "/etc/ssl/certs";
+ if ((dir = opendir (ca_directory)) == NULL)
{
- struct stat st;
- char *ca_file;
- asprintf (&ca_file, "%s/%s", ca_directory, dent->d_name);
+ if (opt.ca_directory && *opt.ca_directory)
+ logprintf (LOG_NOTQUIET, _("ERROR: Cannot open directory %s.\n"),
+ opt.ca_directory);
+ }
+ else
+ {
+ struct hash_table *inode_map = hash_table_new (196, NULL, NULL);
+ struct dirent *dent;
+ size_t dirlen = strlen(ca_directory);
+ int rc;
- stat (ca_file, &st);
+ ncerts = 0;
- if (S_ISREG (st.st_mode))
- gnutls_certificate_set_x509_trust_file (credentials, ca_file,
- GNUTLS_X509_FMT_PEM);
+ while ((dent = readdir (dir)) != NULL)
+ {
+ struct stat st;
+ char ca_file[dirlen + strlen(dent->d_name) + 2];
+
+ snprintf (ca_file, sizeof(ca_file), "%s/%s", ca_directory, dent->d_name);
+ if (stat (ca_file, &st) != 0)
+ continue;
+
+ if (! S_ISREG (st.st_mode))
+ continue;
+
+ /* avoid loading the same file twice by checking the inode. */
+ if (hash_table_contains (inode_map, (void *)(intptr_t) st.st_ino))
+ continue;
+
+ hash_table_put (inode_map, (void *)(intptr_t) st.st_ino, NULL);
+ if ((rc = gnutls_certificate_set_x509_trust_file (credentials, ca_file,
+ GNUTLS_X509_FMT_PEM)) <= 0)
+ logprintf (LOG_NOTQUIET, _("ERROR: Failed to open cert %s: (%d).\n"),
+ ca_file, rc);
+ else
+ ncerts += rc;
+ }
- free (ca_file);
+ hash_table_destroy (inode_map);
+ closedir (dir);
}
-
- closedir (dir);
}
+ DEBUGP (("Certificates loaded: %d\n", ncerts));
+
/* Use the private key from the cert file unless otherwise specified. */
if (opt.cert_file && !opt.private_key)
{
{
int type;
if (opt.private_key_type != opt.cert_type)
- {
- /* GnuTLS can't handle this */
- logprintf (LOG_NOTQUIET, _("ERROR: GnuTLS requires the key and the \
+ {
+ /* GnuTLS can't handle this */
+ logprintf (LOG_NOTQUIET, _("ERROR: GnuTLS requires the key and the \
cert to be of the same type.\n"));
- }
+ }
type = key_type_to_gnutls_type (opt.private_key_type);
gnutls_certificate_set_x509_key_file (credentials, opt.cert_file,
- opt.private_key,
- type);
+ opt.private_key,
+ type);
}
if (opt.ca_cert)
}
static int
-wgnutls_write (int fd, char *buf, int bufsize, void *arg)
+wgnutls_write (int fd _GL_UNUSED, char *buf, int bufsize, void *arg)
{
int ret;
struct wgnutls_transport_context *ctx = arg;
return offset;
}
- if (bufsize > sizeof ctx->peekbuf)
+ if (bufsize > (int) sizeof ctx->peekbuf)
bufsize = sizeof ctx->peekbuf;
if (bufsize > offset)
}
static const char *
-wgnutls_errstr (int fd, void *arg)
+wgnutls_errstr (int fd _GL_UNUSED, void *arg)
{
struct wgnutls_transport_context *ctx = arg;
return gnutls_strerror (ctx->last_error);
bool
ssl_connect_wget (int fd, const char *hostname)
{
+#ifdef F_GETFL
+ int flags = 0;
+#endif
struct wgnutls_transport_context *ctx;
gnutls_session_t session;
int err,alert;
if (! is_valid_ip_address (hostname))
{
gnutls_server_name_set (session, GNUTLS_NAME_DNS, hostname,
- strlen (hostname));
+ strlen (hostname));
}
gnutls_set_default_priority (session);
#ifndef FD_TO_SOCKET
# define FD_TO_SOCKET(X) (X)
#endif
+#ifdef HAVE_INTPTR_T
+ gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) (intptr_t) FD_TO_SOCKET (fd));
+#else
gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) FD_TO_SOCKET (fd));
+#endif
err = 0;
#if HAVE_GNUTLS_PRIORITY_SET_DIRECT
case secure_protocol_tlsv1:
err = gnutls_priority_set_direct (session, "NORMAL:-VERS-SSL3.0", NULL);
break;
+ case secure_protocol_pfs:
+ if (gnutls_check_version("3.2.4"))
+ err = gnutls_priority_set_direct (session, "PFS", NULL);
+ else
+ err = gnutls_priority_set_direct (session, "NORMAL:-RSA", NULL);
+ break;
default:
abort ();
}
return false;
}
+ if (opt.connect_timeout)
+ {
+#ifdef F_GETFL
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags < 0)
+ return flags;
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK))
+ return -1;
+#else
+ /* XXX: Assume it was blocking before. */
+ const int one = 1;
+ if (ioctl (fd, FIONBIO, &one) < 0)
+ return -1;
+#endif
+ }
+
/* We don't stop the handshake process for non-fatal errors */
do
{
err = gnutls_handshake (session);
- if (err < 0)
+
+ if (opt.connect_timeout && err == GNUTLS_E_AGAIN)
+ {
+ if (gnutls_record_get_direction (session))
+ {
+ /* wait for writeability */
+ err = select_fd (fd, opt.connect_timeout, WAIT_FOR_WRITE);
+ }
+ else
+ {
+ /* wait for readability */
+ err = select_fd (fd, opt.connect_timeout, WAIT_FOR_READ);
+ }
+
+ if (err <= 0)
+ {
+ if (err == 0)
+ {
+ errno = ETIMEDOUT;
+ err = -1;
+ }
+ break;
+ }
+
+ if (err <= 0)
+ break;
+ }
+ else if (err < 0)
{
logprintf (LOG_NOTQUIET, "GnuTLS: %s\n", gnutls_strerror (err));
if (err == GNUTLS_E_WARNING_ALERT_RECEIVED ||
}
while (err == GNUTLS_E_WARNING_ALERT_RECEIVED && gnutls_error_is_fatal (err) == 0);
+ if (opt.connect_timeout)
+ {
+#ifdef F_GETFL
+ if (fcntl (fd, F_SETFL, flags) < 0)
+ return -1;
+#else
+ const int zero = 0;
+ if (ioctl (fd, FIONBIO, &zero) < 0)
+ return -1;
+#endif
+ }
+
if (err < 0)
{
gnutls_deinit (session);
return true;
}
+#define _CHECK_CERT(flag,msg) \
+ if (status & (flag))\
+ {\
+ logprintf (LOG_NOTQUIET, (msg),\
+ severity, quote (host));\
+ success = false;\
+ }
+
bool
ssl_check_certificate (int fd, const char *host)
{
goto out;
}
- if (status & GNUTLS_CERT_INVALID)
- {
- logprintf (LOG_NOTQUIET, _("%s: The certificate of %s is not trusted.\n"),
- severity, quote (host));
- success = false;
- }
- if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
- {
- logprintf (LOG_NOTQUIET, _("%s: The certificate of %s hasn't got a known issuer.\n"),
- severity, quote (host));
- success = false;
- }
- if (status & GNUTLS_CERT_REVOKED)
- {
- logprintf (LOG_NOTQUIET, _("%s: The certificate of %s has been revoked.\n"),
- severity, quote (host));
- success = false;
- }
+ _CHECK_CERT (GNUTLS_CERT_INVALID, _("%s: The certificate of %s is not trusted.\n"));
+ _CHECK_CERT (GNUTLS_CERT_SIGNER_NOT_FOUND, _("%s: The certificate of %s hasn't got a known issuer.\n"));
+ _CHECK_CERT (GNUTLS_CERT_REVOKED, _("%s: The certificate of %s has been revoked.\n"));
+ _CHECK_CERT (GNUTLS_CERT_SIGNER_NOT_CA, _("%s: The certificate signer of %s was not a CA.\n"));
+ _CHECK_CERT (GNUTLS_CERT_INSECURE_ALGORITHM, _("%s: The certificate of %s was signed using an insecure algorithm.\n"));
+ _CHECK_CERT (GNUTLS_CERT_NOT_ACTIVATED, _("%s: The certificate of %s is not yet activated.\n"));
+ _CHECK_CERT (GNUTLS_CERT_EXPIRED, _("%s: The certificate of %s has expired.\n"));
if (gnutls_certificate_type_get (ctx->session) == GNUTLS_CRT_X509)
{
}
crt_deinit:
gnutls_x509_crt_deinit (cert);
- }
+ }
+ else
+ {
+ logprintf (LOG_NOTQUIET, _("Certificate must be X.509\n"));
+ success = false;
+ }
out:
return opt.check_cert ? success : true;