mirror of
https://github.com/samba-team/samba.git
synced 2024-12-27 03:21:53 +03:00
c22d20edb4
This allows smbd to be setup from scratch over ssl without having to
know how to create valid certificates (which can be quite tricky
unless you've done it before)
Of course, a good admin will provide real certificates, and smbd will
use them if specified
(This used to be commit 00b67efc99
)
377 lines
9.5 KiB
C
377 lines
9.5 KiB
C
/*
|
|
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 02139, 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;
|
|
};
|
|
|
|
/*
|
|
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);
|
|
int ret;
|
|
ret = gnutls_bye(tls_session->session, GNUTLS_SHUT_WR);
|
|
if (ret < 0) {
|
|
DEBUG(0,("TLS gnutls_bye failed - %s\n", gnutls_strerror(ret)));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
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) {
|
|
DEBUG(0,("TLS gnutls_handshake failed - %s\n", gnutls_strerror(ret)));
|
|
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) {
|
|
DEBUG(0,("gnutls_record_recv failed - %s\n", gnutls_strerror(ret)));
|
|
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) {
|
|
DEBUG(0,("gnutls_record_send failed - %s\n", gnutls_strerror(ret)));
|
|
return NT_STATUS_UNEXPECTED_NETWORK_ERROR;
|
|
}
|
|
*sendlen = ret;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
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();
|
|
|
|
if (!lp_web_tls() || keyfile == NULL || *keyfile == 0) {
|
|
return;
|
|
}
|
|
|
|
tls = talloc_zero(edata, struct tls_data);
|
|
edata->tls_data = tls;
|
|
|
|
if (!file_exist(cafile)) {
|
|
tls_cert_generate(tls, keyfile, certfile, cafile);
|
|
}
|
|
|
|
ret = gnutls_global_init();
|
|
if (ret < 0) goto init_failed;
|
|
|
|
gnutls_certificate_allocate_credentials(&tls->x509_cred);
|
|
if (ret < 0) goto init_failed;
|
|
|
|
if (cafile && *cafile) {
|
|
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", crlfile));
|
|
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",
|
|
certfile, 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 - %s\n", gnutls_strerror(ret)));
|
|
talloc_free(tls);
|
|
edata->tls_data = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
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 %s - %s\n", #call, gnutls_strerror(ret))); \
|
|
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:
|
|
DEBUG(0,("TLS init connection failed - %s\n", gnutls_strerror(ret)));
|
|
web->tls_session = NULL;
|
|
talloc_free(tls_session);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
/* for systems without tls we just map the tls socket calls to the
|
|
normal socket calls */
|
|
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
|