/*------------------------------------------------------------------
* Wrapper for the GnuTLS module.
*
*------------------------------------------------------------------
*/
#include "driver.h"
#include "machine.h"
#if defined(USE_TLS) && defined(HAS_GNUTLS)
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION)
# include <dirent.h>
# define generic_dirent dirent
# define DIRENT_NLENGTH(dirent) (strlen((dirent)->d_name))
#else /* not (DIRENT or _POSIX_VERSION) */
# define generic_dirent direct
# define DIRENT_NLENGTH(dirent) ((dirent)->d_namlen)
# ifdef HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif /* SYSNDIR */
# ifdef HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif /* SYSDIR */
# ifdef HAVE_NDIR_H
# include <ndir.h>
# endif /* NDIR */
#endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */
#ifndef S_ISREG
# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
#endif
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include "pkg-tls.h"
#include "actions.h"
#include "array.h"
#include "comm.h"
#include "interpret.h"
#include "main.h"
#include "mstrings.h"
#include "object.h"
#include "sha1.h"
#include "svalue.h"
#include "xalloc.h"
#include "../mudlib/sys/tls.h"
/*-------------------------------------------------------------------------*/
/* Variables */
static Bool tls_is_available = MY_FALSE;
/* Set to TRUE when the TLS layer has been initialised successfully.
*/
static gnutls_certificate_server_credentials x509_cred;
/* The x509 credentials. */
static gnutls_dh_params dh_params;
/* The Diffie-Hellmann parameters */
/*-------------------------------------------------------------------------*/
static int
generate_dh_params (void)
/* GnuTLS: Generate Diffie Hellman parameters and store them in the global
* <dh_params>. They are for use with DHE kx algorithms. These should be
* discarded and regenerated once a day, once a week or once a month. Depends
* on the security requirements.
*
* tls_is_available must be TRUE.
*/
{
#if HAS_GNUTLS_VERSION < 8
gnutls_datum prime, generator;
gnutls_dh_params_init( &dh_params);
gnutls_dh_params_generate( &prime, &generator, DH_BITS);
gnutls_dh_params_set( dh_params, prime, generator, DH_BITS);
free( prime.data);
free( generator.data);
#else
gnutls_dh_params_init( &dh_params);
gnutls_dh_params_generate2( dh_params, DH_BITS);
#endif
return 0;
} /* generate_dh_params() */
/*-------------------------------------------------------------------------*/
static void
initialize_tls_session (gnutls_session *session)
/* GnuTLS: Initialise a TLS <session>.
* tls_is_available must be TRUE.
*/
{
gnutls_init(session, GNUTLS_SERVER);
/* avoid calling all the priority functions, since the defaults
* are adequate.
*/
gnutls_set_default_priority( *session);
gnutls_credentials_set( *session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_certificate_server_set_request( *session, GNUTLS_CERT_REQUEST);
gnutls_dh_set_prime_bits( *session, DH_BITS);
} /* initialize_tls_session() */
/*-------------------------------------------------------------------------*/
static void *
tls_xalloc (size_t size)
/* Wrapper function so that (gnu)tls will use the driver's allocator.
* The wrapper is required as 'pxalloc' itself is a macro.
*/
{
return pxalloc(size);
} /* tls_xalloc() */
/*-------------------------------------------------------------------------*/
static void *
tls_rexalloc (void *old, size_t size)
/* Wrapper function so that (gnu)tls will use the driver's allocator.
* The wrapper is required as 'prexalloc' itself is a macro.
*/
{
return prexalloc(old, size);
} /* tls_rexalloc() */
/*-------------------------------------------------------------------------*/
static void
tls_xfree (void *p)
/* Wrapper function so that (gnu)tls will use the driver's allocator.
* The wrapper is not exactly required for pfree(), but it keeps things
* consistent.
*/
{
return pfree(p);
} /* tls_xfree() */
/*-------------------------------------------------------------------------*/
void
tls_verify_init (void)
/* initialize or reinitialize tls certificate storage and revocation lists.
*/
{
gnutls_certificate_free_cas(x509_cred);
if (tls_trustfile != NULL)
{
int err;
printf("%s TLS: (GnuTLS) trusted x509 certificates from '%s'.\n"
, time_stamp(), tls_trustfile);
debug_message("%s TLS: (GnuTLS) trusted x509 certificates from '%s'.\n"
, time_stamp(), tls_trustfile);
err = gnutls_certificate_set_x509_trust_file(x509_cred, tls_trustfile, GNUTLS_X509_FMT_PEM);
if (err < 0)
{
printf("%s TLS: Error setting x509 verification certificates: %s\n"
, time_stamp(), gnutls_strerror(err));
debug_message("%s TLS: Error setting x509 verification certificates: %s\n"
, time_stamp(), gnutls_strerror(err));
}
}
if (tls_trustdirectory)
{
DIR * d;
char *fname;
size_t dirlen;
int err;
printf("%s TLS: (GnuTLS) trusted x509 certificates from directory '%s'.\n"
, time_stamp(), tls_trustdirectory);
debug_message("%s TLS: (GnuTLS) trusted x509 certificates from directory '%s'.\n"
, time_stamp(), tls_trustdirectory);
dirlen = strlen(tls_trustdirectory);
fname = (char*) xalloc(dirlen + NAME_MAX + 2);
if (!fname)
{
errno = ENOMEM;
d = NULL;
}
else
{
strcpy(fname, tls_trustdirectory);
fname[dirlen++] = '/';
d = opendir(tls_trustdirectory);
}
if (d == NULL)
{
printf("%s TLS: Can't read trust directory: %s.\n"
, time_stamp(), strerror(errno));
debug_message("%s TLS: Can't read trust directory: %s\n"
, time_stamp(), strerror(errno));
}
else
{
struct dirent *file;
while ((file = readdir(d)) != NULL)
{
struct stat st;
strcpy(fname+dirlen, file->d_name);
stat(fname, &st);
if (S_ISREG(st.st_mode))
{
err = gnutls_certificate_set_x509_trust_file(x509_cred, fname, GNUTLS_X509_FMT_PEM);
if (err < 0)
{
printf("%s TLS: Error setting x509 verification certificates from '%s': %s\n"
, time_stamp(), fname, gnutls_strerror(err));
debug_message("%s TLS: Error setting x509 verification certificates from '%s': %s\n"
, time_stamp(), fname, gnutls_strerror(err));
}
}
}
closedir(d);
}
xfree(fname);
}
else if(tls_trustfile == NULL)
{
printf("%s TLS: (GnuTLS) Trusted x509 certificates locations not specified.\n"
, time_stamp());
debug_message("%s TLS: (GnuTLS) trusted x509 certificates locations not specified.\n"
, time_stamp());
}
gnutls_certificate_free_crls(x509_cred);
if (tls_crlfile != NULL)
{
int err;
printf("%s TLS: (GnuTLS) CRLs from '%s'.\n"
, time_stamp(), tls_crlfile);
debug_message("%s TLS: (GnuTLS) CRLs from '%s'.\n"
, time_stamp(), tls_crlfile);
err = gnutls_certificate_set_x509_crl_file(x509_cred, tls_crlfile, GNUTLS_X509_FMT_PEM);
if (err < 0)
{
printf("%s TLS: Error loading CRLs: %s\n"
, time_stamp(), gnutls_strerror(err));
debug_message("%s TLS: Error loading CRLs: %s\n"
, time_stamp(), gnutls_strerror(err));
}
}
if (tls_crldirectory)
{
DIR * d;
char *fname;
size_t dirlen;
int err;
printf("%s TLS: (GnuTLS) CRLs from directory '%s'.\n"
, time_stamp(), tls_crldirectory);
debug_message("%s TLS: (GnuTLS) CRLs from directory '%s'.\n"
, time_stamp(), tls_crldirectory);
dirlen = strlen(tls_crldirectory);
fname = (char*) xalloc(dirlen + NAME_MAX + 2);
if (!fname)
{
errno = ENOMEM;
d = NULL;
}
else
{
strcpy(fname, tls_crldirectory);
fname[dirlen++] = '/';
d = opendir(tls_crldirectory);
}
if (d == NULL)
{
printf("%s TLS: Can't read CRL directory: %s.\n"
, time_stamp(), strerror(errno));
debug_message("%s TLS: Can't read CRL directory: %s\n"
, time_stamp(), strerror(errno));
}
else
{
struct dirent *file;
while ((file = readdir(d)) != NULL)
{
struct stat st;
strcpy(fname+dirlen, file->d_name);
stat(fname, &st);
if (S_ISREG(st.st_mode))
{
err = gnutls_certificate_set_x509_crl_file(x509_cred, fname, GNUTLS_X509_FMT_PEM);
if (err < 0)
{
printf("%s TLS: Error loading CRL from '%s': %s\n"
, time_stamp(), fname, gnutls_strerror(err));
debug_message("%s TLS: Error loading CRL from '%s': %s\n"
, time_stamp(), fname, gnutls_strerror(err));
}
}
}
closedir(d);
}
xfree(fname);
}
else if(!tls_crlfile)
{
printf("%s TLS: (GnuTLS) CRL checking disabled.\n"
, time_stamp());
debug_message("%s TLS: (GnuTLS) CRL checking disabled.\n"
, time_stamp());
}
}
/*-------------------------------------------------------------------------*/
void
tls_global_init (void)
/* Initialise the TLS package; to be called once at program startup.
*/
{
int f;
if (tls_keyfile == NULL)
{
printf("%s TLS deactivated.\n", time_stamp());
return;
}
/* In order to be able to identify gnutls allocations as such, we redirect
* all allocations through the driver's allocator. The wrapper functions
* make sure that the allocations are annotated properly with this source
* file.
*/
gnutls_global_set_mem_functions(tls_xalloc,
tls_xalloc,
NULL,
tls_rexalloc,
tls_xfree);
gnutls_global_init();
gnutls_certificate_allocate_credentials(&x509_cred);
printf("%s TLS: (GnuTLS) x509 keyfile '%s', certfile '%s'\n"
, time_stamp(), tls_keyfile, tls_certfile);
debug_message("%s TLS: (GnuTLS) Keyfile '%s', Certfile '%s'\n"
, time_stamp(), tls_keyfile, tls_certfile);
f = gnutls_certificate_set_x509_key_file(x509_cred, tls_certfile, tls_keyfile, GNUTLS_X509_FMT_PEM);
if (f < 0)
{
printf("%s TLS: Error setting x509 keyfile: %s\n"
, time_stamp(), gnutls_strerror(f));
debug_message("%s TLS: Error setting x509 keyfile: %s\n"
, time_stamp(), gnutls_strerror(f));
}
else
{
printf("%s TLS: x509 keyfile and certificate set.\n", time_stamp());
tls_verify_init();
generate_dh_params();
gnutls_certificate_set_dh_params( x509_cred, dh_params);
tls_is_available = MY_TRUE;
}
} /* tls_global_init() */
/*-------------------------------------------------------------------------*/
void
tls_global_deinit (void)
/* Clean up the TLS package on program termination.
*/
{
if (tls_is_available)
{
gnutls_certificate_free_credentials(x509_cred);
}
gnutls_global_deinit();
tls_is_available = MY_FALSE;
} /* tls_global_deinit() */
/*-------------------------------------------------------------------------*/
int
tls_read (interactive_t *ip, char *buffer, int length)
/* Read up to <length> bytes data for the TLS connection of <ip>
* and store it in <buffer>.
* Return then number of bytes read, or a negative number if an error
* occured.
*/
{
int ret = -11;
do {
ret = gnutls_record_recv(ip->tls_session, buffer, length);
} while ( ret < 0 && (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) );
if (ret == 0)
{
tls_deinit_connection(ip);
}
else if (ret < 0)
{
debug_message("%s GnuTLS: Error in receiving data (%s). "
"Closing the connection.\n"
, time_stamp(), gnutls_strerror(ret));
tls_deinit_connection(ip);
}
return (ret < 0 ? -1 : ret);
} /* tls_read() */
/*-------------------------------------------------------------------------*/
int
tls_write (interactive_t *ip, char *buffer, int length)
/* Write <length> bytes from <buffer> to the TLS connection of <ip>
* Return the number of bytes written, or a negative number if an error
* occured.
*/
{
int ret = -1;
ret = gnutls_record_send( ip->tls_session, buffer, length );
if (ret < 0)
{
/* Let comm.c handle EINTR and EWOULDBLOCK.
* We are then called again later with the
* same content.
*/
if (ret == GNUTLS_E_INTERRUPTED)
{
errno = EINTR;
return -1;
}
else if (ret == GNUTLS_E_AGAIN)
{
errno = EWOULDBLOCK;
return -1;
}
debug_message("%s GnuTLS: Error in sending data (%s). "
"Closing the connection.\n"
, time_stamp(), gnutls_strerror(ret));
tls_deinit_connection(ip);
}
return (ret<0 ? -1 : ret);
} /* tls_write() */
/*-------------------------------------------------------------------------*/
int
tls_do_handshake (interactive_t *ip)
/* Continue the TLS initialisation handshake for interactive <ip>.
* Return a negative error code if the connection can not be set up.
* Return 0 if the connection is still begin set up.
* Return 1 if the connection is now active (or if no secure connection
* had been requested).
*
* This function does only the package specific part of
* tls_continue_handshake.
*/
{
int ret;
ret = gnutls_handshake(ip->tls_session);
if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
{
if (ret < 0)
{
/* Setup failed */
gnutls_deinit(ip->tls_session);
ip->tls_session = NULL;
}
else
{
/* Setup finished */
ret = 1;
}
}
else
ret = 0;
return ret;
} /* tls_do_handshake() */
/*-------------------------------------------------------------------------*/
int
tls_init_connection (interactive_t *ip)
/* Starts a TLS secured connection to the interactive <ip>.
* Returns a negative error code or 0 for success.
*
* After a successful start tls_do_handshake will be called.
*/
{
initialize_tls_session(&ip->tls_session);
gnutls_transport_set_ptr(ip->tls_session, (gnutls_transport_ptr)(ip->socket));
return 0;
} /* tls_init_connection() */
/*-------------------------------------------------------------------------*/
vector_t *
tls_check_certificate (interactive_t *ip, Bool more)
/* Checks the certificate of the secured connection and returns
* an array with the information about it for the efun
* tls_check_certificate().
*/
{
vector_t *v = NULL;
const gnutls_datum_t *cert_list;
gnutls_x509_crt_t cert;
#if LIBGNUTLS_VERSION_MAJOR > 1 || (LIBGNUTLS_VERSION_MAJOR==1 && (LIBGNUTLS_VERSION_MINOR > 7 || (LIBGNUTLS_VERSION_MINOR == 7 && LIBGNUTLS_VERSION_MINOR >= 8)))
#define GNUTLS_NEW_DN_API
gnutls_x509_dn_t subject;
#endif
unsigned int cert_list_size;
unsigned int result;
time_t t, now;
int err;
cert_list = gnutls_certificate_get_peers(ip->tls_session, &cert_list_size);
if (cert_list == NULL || cert_list_size == 0)
return v;
err = gnutls_certificate_verify_peers2(ip->tls_session, &result);
if (err < 0)
return v;
if (gnutls_x509_crt_init(&cert) < 0)
return v;
if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
{
gnutls_x509_crt_deinit(cert);
return v;
}
now = time(0);
t = gnutls_x509_crt_get_expiration_time(cert);
if (t == (time_t)-1 || t < now)
result |= GNUTLS_CERT_INVALID;
t = gnutls_x509_crt_get_activation_time(cert);
if (t == (time_t)-1 || t >= now)
result |= GNUTLS_CERT_INVALID;
v = allocate_array(more ? 3 : 2);
put_number(&(v->item[0]), result);
#ifdef GNUTLS_NEW_DN_API
err = gnutls_x509_crt_get_subject(cert, &subject);
if (err < 0)
put_number(&(v->item[1]), 0);
else
{
int count;
int rdn, ava_nr;
gnutls_x509_ava_st ava;
vector_t *extra;
count = 0;
for(rdn=0; !gnutls_x509_dn_get_rdn_ava(subject, rdn, 0, &ava); rdn++)
{
count++;
for(ava_nr=1; !gnutls_x509_dn_get_rdn_ava(subject, rdn, ava_nr, &ava); ava_nr++)
count++;
}
extra = allocate_array(3 * count);
count = 0;
for(rdn=0; !gnutls_x509_dn_get_rdn_ava(subject, rdn, 0, &ava); rdn++)
for(ava_nr=0; ava_nr==0 || !gnutls_x509_dn_get_rdn_ava(subject, rdn, ava_nr, &ava); ava_nr++)
{
put_c_n_string(&(extra->item[count++]), (char*)ava.oid.data, ava.oid.size-1);
put_number(&(extra->item[count]), 0); count++;
put_c_n_string(&(extra->item[count++]), (char*)ava.value.data, ava.value.size);
}
put_array(&(v->item[1]), extra);
}
#else
{
int count, nr;
vector_t *extra;
char oid[128];
char data[256];
char *ptr;
size_t osize, dsize;
count = 0;
nr = 0;
while(1)
{
osize = sizeof(oid);
err = gnutls_x509_crt_get_dn_oid(cert, nr, oid, &osize);
if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (err >= 0)
count++;
nr++;
}
extra = allocate_array(3 * count);
count = 0;
ptr = 0;
nr = 0;
while (1)
{
osize = sizeof(oid);
err = gnutls_x509_crt_get_dn_oid(cert, nr, oid, &osize);
if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (err >= 0)
{
dsize = sizeof(data);
ptr = NULL;
err = gnutls_x509_crt_get_dn_by_oid(cert, oid, 0, 0, data, &dsize);
if (err == GNUTLS_E_SHORT_MEMORY_BUFFER)
{
ptr = (char*) xalloc(dsize);
if (ptr)
err = gnutls_x509_crt_get_dn_by_oid(cert, oid, 0, 0, ptr, &dsize);
}
put_c_n_string(&(extra->item[count++]), oid, osize);
put_number(&(extra->item[count]), 0); count++;
if (err >= 0)
put_c_n_string(&(extra->item[count++]), (ptr!=NULL) ? ptr : data, dsize);
else
{
put_number(&(extra->item[count]), 0); count++;
}
if (ptr)
xfree(ptr);
}
nr++;
}
put_array(&(v->item[1]), extra);
}
#endif
if (more)
{
int count, nr;
vector_t *extra;
char oid[128];
char data[256];
char *ptr;
size_t osize, dsize;
count = 0;
nr = 0;
while(1)
{
osize = sizeof(oid);
err = gnutls_x509_crt_get_extension_oid(cert, nr, oid, &osize);
if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (err >= 0)
count++;
nr++;
}
extra = allocate_array(3 * count);
count = 0;
ptr = 0;
nr = 0;
while (1)
{
osize = sizeof(oid);
err = gnutls_x509_crt_get_extension_oid(cert, nr, oid, &osize);
if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
break;
if (err >= 0)
{
#ifndef GNUTLS_NEW_DN_API
unsigned int critical;
#endif
dsize = sizeof(data);
ptr = NULL;
#ifdef GNUTLS_NEW_DN_API
err = gnutls_x509_crt_get_extension_data(cert, nr, data, &dsize);
if (err == GNUTLS_E_SHORT_MEMORY_BUFFER)
{
ptr = (char*) xalloc(dsize);
if (ptr)
err = gnutls_x509_crt_get_extension_data(cert, nr, ptr, &dsize);
}
#else
err = gnutls_x509_crt_get_extension_by_oid(cert, oid, 0, data, &dsize, &critical);
if (err == GNUTLS_E_SHORT_MEMORY_BUFFER)
{
ptr = (char*) xalloc(dsize);
if (ptr)
err = gnutls_x509_crt_get_extension_by_oid(cert, oid, 0, ptr, &dsize, &critical);
}
#endif
put_c_n_string(&(extra->item[count++]), oid, osize);
put_number(&(extra->item[count]), 0); count++;
if (err >= 0)
put_c_n_string(&(extra->item[count++]), (ptr!=NULL) ? ptr : data, dsize);
else
{
put_number(&(extra->item[count]), 0); count++;
}
if (ptr)
xfree(ptr);
}
nr++;
}
put_array(&(v->item[2]), extra);
}
gnutls_x509_crt_deinit(cert);
return v;
} /* tls_check_certificate() */
/*-------------------------------------------------------------------------*/
void
tls_deinit_connection (interactive_t *ip)
/* Close the TLS connection for the interactive <ip> if there is one.
*/
{
if (ip->tls_status != TLS_INACTIVE)
{
gnutls_bye( ip->tls_session, GNUTLS_SHUT_WR);
gnutls_deinit(ip->tls_session);
ip->tls_session = NULL;
}
if (ip->tls_cb != NULL)
{
free_callback(ip->tls_cb);
xfree(ip->tls_cb);
ip->tls_cb = NULL;
}
ip->tls_status = TLS_INACTIVE;
} /* tls_deinit_connection() */
/*-------------------------------------------------------------------------*/
const char *
tls_error(int err)
/* Returns a string describing the error behind the
* error number <err>, which is always negative.
*/
{
return gnutls_strerror(err);
} /* tls_error() */
/*-------------------------------------------------------------------------*/
vector_t *
tls_query_connection_info (interactive_t *ip)
/* Returns the connection info array for the efun
* tls_query_connection_info(). <ip> is guaranteed
* to have a TLS secured connection.
*/
{
vector_t * rc;
rc = allocate_array(TLS_INFO_MAX);
put_number(&(rc->item[TLS_CIPHER])
, gnutls_cipher_get(ip->tls_session));
put_number(&(rc->item[TLS_COMP])
, gnutls_compression_get(ip->tls_session));
put_number(&(rc->item[TLS_KX])
, gnutls_kx_get(ip->tls_session));
put_number(&(rc->item[TLS_MAC])
, gnutls_mac_get(ip->tls_session));
put_number(&(rc->item[TLS_PROT])
, gnutls_protocol_get_version(ip->tls_session));
return rc;
} /* tls_query_connection_info() */
/*-------------------------------------------------------------------------*/
Bool
tls_available ()
/* If the global TLS Initialisation could not been set up, tls_available()
* returns MY_FALSE, otherwise MY_TRUE.
*/
{
return tls_is_available;
} /* tls_available() */
/***************************************************************************/
#endif /* USE_TLS && HAS_GNUTLS */