2005-05-27 11:57:14 +00:00
/*
Unix SMB / CIFS implementation .
transport layer security handling code
Copyright ( C ) Andrew Tridgell 2005
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include "includes.h"
# include "smbd/service_task.h"
# include "smbd/service_stream.h"
# include "web_server/web_server.h"
# include "lib/events/events.h"
# include "system/network.h"
# if HAVE_LIBGNUTLS
# include "gnutls/gnutls.h"
# define DH_BITS 1024
/* hold per connection tls data */
struct tls_session {
gnutls_session session ;
BOOL done_handshake ;
} ;
/* hold persistent tls data */
struct tls_data {
gnutls_certificate_credentials x509_cred ;
gnutls_dh_params dh_params ;
} ;
/*
initialise global tls state
*/
void tls_initialise ( struct task_server * task )
{
struct esp_data * edata = talloc_get_type ( task - > private , struct esp_data ) ;
struct tls_data * tls ;
int ret ;
const char * keyfile = lp_web_keyfile ( ) ;
const char * certfile = lp_web_certfile ( ) ;
const char * cafile = lp_web_cafile ( ) ;
const char * crlfile = lp_web_crlfile ( ) ;
2005-05-27 12:23:52 +00:00
if ( ! lp_web_tls ( ) | | keyfile = = NULL | | * keyfile = = 0 ) {
2005-05-27 11:57:14 +00:00
return ;
}
tls = talloc_zero ( edata , struct tls_data ) ;
edata - > tls_data = tls ;
ret = gnutls_global_init ( ) ;
if ( ret < 0 ) goto init_failed ;
gnutls_certificate_allocate_credentials ( & tls - > x509_cred ) ;
if ( ret < 0 ) goto init_failed ;
ret = gnutls_certificate_set_x509_trust_file ( tls - > x509_cred , cafile ,
GNUTLS_X509_FMT_PEM ) ;
if ( ret < 0 ) {
DEBUG ( 0 , ( " TLS failed to initialise cafile %s \n " , cafile ) ) ;
goto init_failed ;
}
if ( crlfile & & * crlfile ) {
ret = gnutls_certificate_set_x509_crl_file ( tls - > x509_cred ,
crlfile ,
GNUTLS_X509_FMT_PEM ) ;
if ( ret < 0 ) {
DEBUG ( 0 , ( " TLS failed to initialise crlfile %s \n " , cafile ) ) ;
goto init_failed ;
}
}
ret = gnutls_certificate_set_x509_key_file ( tls - > x509_cred ,
certfile , keyfile ,
GNUTLS_X509_FMT_PEM ) ;
if ( ret < 0 ) {
DEBUG ( 0 , ( " TLS failed to initialise certfile %s and keyfile %s \n " ,
lp_web_certfile ( ) , lp_web_keyfile ( ) ) ) ;
goto init_failed ;
}
ret = gnutls_dh_params_init ( & tls - > dh_params ) ;
if ( ret < 0 ) goto init_failed ;
ret = gnutls_dh_params_generate2 ( tls - > dh_params , DH_BITS ) ;
if ( ret < 0 ) goto init_failed ;
gnutls_certificate_set_dh_params ( tls - > x509_cred , tls - > dh_params ) ;
return ;
init_failed :
DEBUG ( 0 , ( " GNUTLS failed to initialise with code %d - disabling \n " , ret ) ) ;
talloc_free ( tls ) ;
edata - > tls_data = NULL ;
}
/*
callback for reading from a socket
*/
static ssize_t tls_pull ( gnutls_transport_ptr ptr , void * buf , size_t size )
{
struct websrv_context * web = talloc_get_type ( ptr , struct websrv_context ) ;
NTSTATUS status ;
size_t nread ;
if ( web - > input . tls_first_char ) {
* ( uint8_t * ) buf = web - > input . first_byte ;
web - > input . tls_first_char = False ;
return 1 ;
}
status = socket_recv ( web - > conn - > socket , buf , size , & nread , 0 ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
EVENT_FD_READABLE ( web - > conn - > event . fde ) ;
EVENT_FD_NOT_WRITEABLE ( web - > conn - > event . fde ) ;
return - 1 ;
}
if ( web - > output . output_pending ) {
EVENT_FD_WRITEABLE ( web - > conn - > event . fde ) ;
}
if ( size ! = nread ) {
EVENT_FD_READABLE ( web - > conn - > event . fde ) ;
}
return nread ;
}
/*
callback for writing to a socket
*/
static ssize_t tls_push ( gnutls_transport_ptr ptr , const void * buf , size_t size )
{
struct websrv_context * web = talloc_get_type ( ptr , struct websrv_context ) ;
NTSTATUS status ;
size_t nwritten ;
DATA_BLOB b ;
if ( web - > tls_session = = NULL ) {
return size ;
}
b . data = discard_const ( buf ) ;
b . length = size ;
status = socket_send ( web - > conn - > socket , & b , & nwritten , 0 ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
EVENT_FD_WRITEABLE ( web - > conn - > event . fde ) ;
return - 1 ;
}
if ( size ! = nwritten ) {
EVENT_FD_WRITEABLE ( web - > conn - > event . fde ) ;
}
return nwritten ;
}
/*
destroy a tls session
*/
static int tls_destructor ( void * ptr )
{
struct tls_session * tls_session = talloc_get_type ( ptr , struct tls_session ) ;
gnutls_bye ( tls_session - > session , GNUTLS_SHUT_WR ) ;
return 0 ;
}
/*
setup for a new connection
*/
NTSTATUS tls_init_connection ( struct websrv_context * web )
{
struct esp_data * edata = talloc_get_type ( web - > task - > private , struct esp_data ) ;
struct tls_data * tls_data = talloc_get_type ( edata - > tls_data , struct tls_data ) ;
struct tls_session * tls_session ;
int ret ;
if ( edata - > tls_data = = NULL ) {
web - > tls_session = NULL ;
return NT_STATUS_OK ;
}
# define TLSCHECK(call) do { \
ret = call ; \
if ( ret < 0 ) { \
DEBUG ( 0 , ( " TLS failed with code %d - %s \n " , ret , # call ) ) ; \
goto failed ; \
} \
} while ( 0 )
tls_session = talloc_zero ( web , struct tls_session ) ;
web - > tls_session = tls_session ;
TLSCHECK ( gnutls_init ( & tls_session - > session , GNUTLS_SERVER ) ) ;
talloc_set_destructor ( tls_session , tls_destructor ) ;
TLSCHECK ( gnutls_set_default_priority ( tls_session - > session ) ) ;
TLSCHECK ( gnutls_credentials_set ( tls_session - > session , GNUTLS_CRD_CERTIFICATE , tls_data - > x509_cred ) ) ;
gnutls_certificate_server_set_request ( tls_session - > session , GNUTLS_CERT_REQUEST ) ;
gnutls_dh_set_prime_bits ( tls_session - > session , DH_BITS ) ;
gnutls_transport_set_ptr ( tls_session - > session , ( gnutls_transport_ptr ) web ) ;
gnutls_transport_set_pull_function ( tls_session - > session , ( gnutls_pull_func ) tls_pull ) ;
gnutls_transport_set_push_function ( tls_session - > session , ( gnutls_push_func ) tls_push ) ;
gnutls_transport_set_lowat ( tls_session - > session , 0 ) ;
web - > input . tls_detect = True ;
return NT_STATUS_OK ;
failed :
web - > tls_session = NULL ;
talloc_free ( tls_session ) ;
return NT_STATUS_OK ;
}
/*
possibly continue the handshake process
*/
static NTSTATUS tls_handshake ( struct tls_session * tls_session )
{
int ret ;
if ( tls_session - > done_handshake ) {
return NT_STATUS_OK ;
}
ret = gnutls_handshake ( tls_session - > session ) ;
if ( ret = = GNUTLS_E_INTERRUPTED | | ret = = GNUTLS_E_AGAIN ) {
return STATUS_MORE_ENTRIES ;
}
if ( ret < 0 ) {
return NT_STATUS_UNEXPECTED_NETWORK_ERROR ;
}
tls_session - > done_handshake = True ;
return NT_STATUS_OK ;
}
/*
receive data either by tls or normal socket_recv
*/
NTSTATUS tls_socket_recv ( struct websrv_context * web , void * buf , size_t wantlen ,
size_t * nread )
{
int ret ;
NTSTATUS status ;
struct tls_session * tls_session = talloc_get_type ( web - > tls_session ,
struct tls_session ) ;
if ( web - > tls_session ! = NULL & & web - > input . tls_detect ) {
status = socket_recv ( web - > conn - > socket , & web - > input . first_byte ,
1 , nread , 0 ) ;
NT_STATUS_NOT_OK_RETURN ( status ) ;
if ( * nread = = 0 ) return NT_STATUS_OK ;
web - > input . tls_detect = False ;
/* look for the first byte of a valid HTTP operation */
if ( strchr ( " GPHO " , web - > input . first_byte ) ) {
/* not a tls link */
web - > tls_session = NULL ;
talloc_free ( tls_session ) ;
* ( uint8_t * ) buf = web - > input . first_byte ;
return NT_STATUS_OK ;
}
web - > input . tls_first_char = True ;
}
if ( web - > tls_session = = NULL ) {
return socket_recv ( web - > conn - > socket , buf , wantlen , nread , 0 ) ;
}
status = tls_handshake ( tls_session ) ;
NT_STATUS_NOT_OK_RETURN ( status ) ;
ret = gnutls_record_recv ( tls_session - > session , buf , wantlen ) ;
if ( ret = = GNUTLS_E_INTERRUPTED | | ret = = GNUTLS_E_AGAIN ) {
return STATUS_MORE_ENTRIES ;
}
if ( ret < 0 ) {
return NT_STATUS_UNEXPECTED_NETWORK_ERROR ;
}
* nread = ret ;
return NT_STATUS_OK ;
}
/*
send data either by tls or normal socket_recv
*/
NTSTATUS tls_socket_send ( struct websrv_context * web , const DATA_BLOB * blob ,
size_t * sendlen )
{
NTSTATUS status ;
int ret ;
struct tls_session * tls_session = talloc_get_type ( web - > tls_session ,
struct tls_session ) ;
if ( web - > tls_session = = NULL ) {
return socket_send ( web - > conn - > socket , blob , sendlen , 0 ) ;
}
status = tls_handshake ( tls_session ) ;
NT_STATUS_NOT_OK_RETURN ( status ) ;
ret = gnutls_record_send ( tls_session - > session , blob - > data , blob - > length ) ;
if ( ret = = GNUTLS_E_INTERRUPTED | | ret = = GNUTLS_E_AGAIN ) {
return STATUS_MORE_ENTRIES ;
}
if ( ret < 0 ) {
return NT_STATUS_UNEXPECTED_NETWORK_ERROR ;
}
* sendlen = ret ;
return NT_STATUS_OK ;
}
# else
/* for systems without tls */
NTSTATUS tls_socket_recv ( struct websrv_context * web , void * buf , size_t wantlen ,
size_t * nread )
{
return socket_recv ( web - > conn - > socket , buf , wantlen , nread , 0 ) ;
}
NTSTATUS tls_socket_send ( struct websrv_context * web , const DATA_BLOB * blob ,
size_t * sendlen )
{
return socket_send ( web - > conn - > socket , blob , sendlen , 0 ) ;
}
NTSTATUS tls_init_connection ( struct websrv_context * web )
{
web - > tls_session = NULL ;
return NT_STATUS_OK ;
}
void tls_initialise ( struct task_server * task )
{
struct esp_data * edata = talloc_get_type ( task - > private , struct esp_data ) ;
edata - > tls_data = NULL ;
}
# endif