r7013: added tls support to the builtin web server. It auto-detects if the client
authorAndrew Tridgell <tridge@samba.org>
Fri, 27 May 2005 11:57:14 +0000 (11:57 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:17:06 +0000 (13:17 -0500)
is using tls by looking at the first byte on the connection. This allows
both https and http services to be on the same port
(This used to be commit 6369dfb6585ce4d4e3028c557395f2d73c290c92)

source4/web_server/config.m4
source4/web_server/config.mk
source4/web_server/http.c
source4/web_server/tls.c [new file with mode: 0644]
source4/web_server/web_server.c
source4/web_server/web_server.h

index 4d8952b775444e421db509f9360388695515a0b8..057f193179de59623fe67002376744c782797eb0 100644 (file)
@@ -1 +1,13 @@
 AC_CHECK_HEADERS(setjmp.h)
+
+###############################
+# start SMB_EXT_LIB_GNUTLS
+# check for gnutls/gnutls.h and -lgnutls
+AC_CHECK_HEADERS(gnutls/gnutls.h)
+AC_CHECK_LIB_EXT(gnutls, GNUTLS_LIBS, gnutls_global_init)
+if test x"$ac_cv_header_gnutls_gnutls_h" = x"yes" -a x"$ac_cv_lib_ext_gnutls_gnutls_global_init" = x"yes";then
+       SMB_EXT_LIB_ENABLE(GNUTLS,YES)
+fi
+SMB_EXT_LIB(GNUTLS, $GNUTLS_LIBS)
+# end SMB_EXT_LIB_GNUTLS
+###############################
index b287b4f153f2e626e03565b25652aeda77e6eb34..00ae2ea8a028ac889f1d625ac5ba1c4221567a61 100644 (file)
@@ -34,7 +34,8 @@ INIT_OBJ_FILES = \
                web_server/web_server.o
 ADD_OBJ_FILES = \
                web_server/http.o \
-               web_server/calls.o
+               web_server/calls.o \
+               web_server/tls.o
 REQUIRED_SUBSYSTEMS = ESP
 # End SUBSYSTEM WEB
 #######################
index 6687ab7d16af2461aa238e1c8f5015567c942157..11ddec552d2f6ae25d98f612124d0a9303a7d656 100644 (file)
 
 #define SWAT_SESSION_KEY "_swat_session_"
 
-/*
-  context for long term storage in the web server, to support session[]
-  and application[] data. Stored in task->private.
-*/
-struct esp_data {
-       struct session_data {
-               struct session_data *next, *prev;
-               struct esp_data *edata;
-               const char *id;
-               struct MprVar *data;
-               struct timed_event *te;
-               int lifetime;
-       } *sessions;
-       struct MprVar *application_data;
-};
-
 /* state of the esp subsystem for a specific request */
 struct esp_state {
        struct websrv_context *web;
@@ -250,10 +234,13 @@ static void http_redirect(EspHandle handle, int code, char *url)
                if (url[0] != '/') {
                        char *p = strrchr(web->input.url, '/');
                        if (p == web->input.url) {
-                               url = talloc_asprintf(web, "http://%s/%s", host, url);
+                               url = talloc_asprintf(web, "http%s://%s/%s", 
+                                                     web->tls_session?"s":"",
+                                                     host, url);
                        } else {
                                int dirlen = p - web->input.url;
-                               url = talloc_asprintf(web, "http://%s%*.*s/%s",
+                               url = talloc_asprintf(web, "http%s://%s%*.*s/%s",
+                                                     web->tls_session?"s":"",
                                                      host, 
                                                      dirlen, dirlen, web->input.url,
                                                      url);
@@ -351,6 +338,7 @@ void http_error(struct websrv_context *web, int code, const char *info)
        http_output_headers(web);
        EVENT_FD_NOT_READABLE(web->conn->event.fde);
        EVENT_FD_WRITEABLE(web->conn->event.fde);
+       web->output.output_pending = True;
 }
 
 /*
@@ -399,6 +387,7 @@ static void http_simple_request(struct websrv_context *web)
 
        http_output_headers(web);
        EVENT_FD_WRITEABLE(web->conn->event.fde);
+       web->output.output_pending = True;
        return;
 
 invalid:
@@ -449,7 +438,7 @@ static void http_setup_arrays(struct esp_state *esp)
        SETVAR(ESP_SERVER_OBJ, "DOCUMENT_ROOT", lp_swat_directory());
        SETVAR(ESP_SERVER_OBJ, "SERVER_PORT", 
               talloc_asprintf(esp, "%u", socket_get_my_port(web->conn->socket)));
-       SETVAR(ESP_SERVER_OBJ, "SERVER_PROTOCOL", "http");
+       SETVAR(ESP_SERVER_OBJ, "SERVER_PROTOCOL", web->tls_session?"https":"http");
        SETVAR(ESP_SERVER_OBJ, "SERVER_SOFTWARE", "SWAT");
        SETVAR(ESP_SERVER_OBJ, "GATEWAY_INTERFACE", "CGI/1.1");
        SETVAR(ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url);
@@ -509,6 +498,7 @@ static void esp_request(struct esp_state *esp)
        talloc_free(buf);
        http_output_headers(web);
        EVENT_FD_WRITEABLE(web->conn->event.fde);
+       web->output.output_pending = True;
 }
 
 
@@ -663,7 +653,7 @@ static void http_setup_session(struct esp_state *esp)
                s->data = NULL;
                s->te = NULL;
                s->edata = edata;
-               s->lifetime = lp_parm_int(-1, "http", "sessiontimeout", 300);
+               s->lifetime = lp_parm_int(-1, "web", "sessiontimeout", 300);
                DLIST_ADD(edata->sessions, s);
                talloc_set_destructor(s, session_destructor);
        }
@@ -775,6 +765,9 @@ void http_process_input(struct websrv_context *web)
 
        /* work out the mime type */
        p = strrchr(web->input.url, '.');
+       if (p == NULL) {
+               esp_enable = True;
+       }
        for (i=0;p && i<ARRAY_SIZE(mime_types);i++) {
                if (strcmp(mime_types[i].extension, p+1) == 0) {
                        file_type = mime_types[i].mime_type;
@@ -880,12 +873,10 @@ NTSTATUS http_setup_esp(struct task_server *task)
 {
        struct esp_data *edata;
 
-       edata = talloc(task, struct esp_data);
+       edata = talloc_zero(task, struct esp_data);
        NT_STATUS_HAVE_NO_MEMORY(edata);
 
        task->private = edata;
-       edata->sessions = NULL;
-       edata->application_data = NULL;
 
        return NT_STATUS_OK;
 }
diff --git a/source4/web_server/tls.c b/source4/web_server/tls.c
new file mode 100644 (file)
index 0000000..4129d59
--- /dev/null
@@ -0,0 +1,358 @@
+/* 
+   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;
+};
+
+/*
+  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_parm_bool(-1, "web", "tls", False)) {
+               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
index 9bbf423d29fcf802e72d5f5a7def6b14aa6e64b1..faa30fc55f1f4e7bbdf581158b394f5f2b27fc0d 100644 (file)
@@ -68,7 +68,7 @@ static void websrv_recv(struct stream_connection *conn, uint16_t flags)
        DATA_BLOB b;
 
        /* not the most efficient http parser ever, but good enough for us */
-       status = socket_recv(conn->socket, buf, sizeof(buf), &nread, 0);
+       status = tls_socket_recv(web, buf, sizeof(buf), &nread);
        if (NT_STATUS_IS_ERR(status)) goto failed;
        if (!NT_STATUS_IS_OK(status)) return;
 
@@ -128,7 +128,7 @@ static void websrv_send(struct stream_connection *conn, uint16_t flags)
        b.data += web->output.nsent;
        b.length -= web->output.nsent;
 
-       status = socket_send(conn->socket, &b, &nsent, 0);
+       status = tls_socket_send(web, &b, &nsent);
        if (NT_STATUS_IS_ERR(status)) {
                stream_terminate_connection(web->conn, "socket_send: failed");
                return;
@@ -159,7 +159,8 @@ static void websrv_send(struct stream_connection *conn, uint16_t flags)
                web->output.content = data_blob_talloc(web, buf, nread);
        }
 
-       if (web->output.content.length == web->output.nsent) {
+       if (web->output.content.length == web->output.nsent && 
+           web->output.fd == -1) {
                stream_terminate_connection(web->conn, NULL);
        }
 }
@@ -171,6 +172,7 @@ static void websrv_accept(struct stream_connection *conn)
 {
        struct task_server *task = talloc_get_type(conn->private, struct task_server);
        struct websrv_context *web;
+       NTSTATUS status;
 
        web = talloc_zero(conn, struct websrv_context);
        if (web == NULL) goto failed;
@@ -184,6 +186,10 @@ static void websrv_accept(struct stream_connection *conn)
        event_add_timed(conn->event.ctx, web, 
                        timeval_current_ofs(HTTP_TIMEOUT, 0),
                        websrv_timeout, web);
+
+       status = tls_init_connection(web);
+       if (!NT_STATUS_IS_OK(status)) goto failed;
+
        return;
 
 failed:
@@ -235,6 +241,8 @@ static void websrv_task_init(struct task_server *task)
        status = http_setup_esp(task);
        if (!NT_STATUS_IS_OK(status)) goto failed;
 
+       tls_initialise(task);
+
        return;
 
 failed:
index f23ea90d551a5fd3ba1ea187af05bc408ac436d0..53f97964f12538245b8eed990c9a62749f975444 100644 (file)
@@ -28,6 +28,9 @@ struct websrv_context {
        struct task_server *task;
        struct stream_connection *conn;
        struct {
+               BOOL tls_detect;
+               BOOL tls_first_char;
+               uint8_t first_byte;
                DATA_BLOB partial;
                BOOL end_of_headers;
                char *url;
@@ -45,13 +48,32 @@ struct websrv_context {
                const char *session_key;
        } input;
        struct {
+               BOOL output_pending;
                DATA_BLOB content;
                int fd;
                unsigned nsent;
                int response_code;
                const char **headers;
        } output;
+       void *tls_session;
        struct session_data *session;
 };
 
 
+/*
+  context for long term storage in the web server, to support session[]
+  and application[] data. Stored in task->private.
+*/
+struct esp_data {
+       struct session_data {
+               struct session_data *next, *prev;
+               struct esp_data *edata;
+               const char *id;
+               struct MprVar *data;
+               struct timed_event *te;
+               int lifetime;
+       } *sessions;
+       struct MprVar *application_data;
+       void *tls_data;
+};
+