r25446: Merge some changes I made on the way home from SFO:
[jelmer/samba4-debian.git] / source / lib / tls / tls.c
index 49f7b758c0dbc811e147b23f89ada29a6c9c0955..d2f6d6f740606c052bca205aa8ec9bfcdd1b6aad 100644 (file)
@@ -3,11 +3,13 @@
 
    transport layer security handling code
 
-   Copyright (C) Andrew Tridgell 2005
-   
+   Copyright (C) Andrew Tridgell 2004-2005
+   Copyright (C) Stefan Metzmacher 2004
+   Copyright (C) Andrew Bartlett 2006
    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
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    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.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 #include "lib/events/events.h"
 #include "lib/socket/socket.h"
 #include "lib/tls/tls.h"
+#include "param/param.h"
 
-#if HAVE_LIBGNUTLS
+#if ENABLE_GNUTLS
 #include "gnutls/gnutls.h"
 
 #define DH_BITS 1024
 
+#if defined(HAVE_GNUTLS_DATUM) && !defined(HAVE_GNUTLS_DATUM_T)
+typedef gnutls_datum gnutls_datum_t;
+#endif
+
 /* hold persistent tls data */
 struct tls_params {
        gnutls_certificate_credentials x509_cred;
        gnutls_dh_params dh_params;
        BOOL tls_enabled;
 };
+#endif
 
 /* hold per connection tls data */
 struct tls_context {
        struct socket_context *socket;
        struct fd_event *fde;
+       BOOL tls_enabled;
+#if ENABLE_GNUTLS
        gnutls_session session;
        BOOL done_handshake;
        BOOL have_first_byte;
        uint8_t first_byte;
-       BOOL tls_enabled;
        BOOL tls_detect;
        const char *plain_chars;
        BOOL output_pending;
        gnutls_certificate_credentials xcred;
        BOOL interrupted;
+#endif
 };
 
+BOOL tls_enabled(struct socket_context *sock)
+{
+       struct tls_context *tls;
+       if (!sock) {
+               return False;
+       }
+       if (strcmp(sock->backend_name, "tls") != 0) {
+               return False;
+       }
+       tls = talloc_get_type(sock->private_data, struct tls_context);
+       if (!tls) {
+               return False;
+       }
+       return tls->tls_enabled;
+}
+
+
+#if ENABLE_GNUTLS
+
+static const struct socket_ops tls_socket_ops;
+
+static NTSTATUS tls_socket_init(struct socket_context *sock)
+{
+       switch (sock->type) {
+       case SOCKET_TYPE_STREAM:
+               break;
+       default:
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       sock->backend_name = "tls";
+
+       return NT_STATUS_OK;
+}
+
 #define TLSCHECK(call) do { \
        ret = call; \
        if (ret < 0) { \
@@ -62,7 +106,6 @@ struct tls_context {
 } while (0)
 
 
-
 /*
   callback for reading from a socket
 */
@@ -78,7 +121,7 @@ static ssize_t tls_pull(gnutls_transport_ptr ptr, void *buf, size_t size)
                return 1;
        }
 
-       status = socket_recv(tls->socket, buf, size, &nread, 0);
+       status = socket_recv(tls->socket, buf, size, &nread);
        if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
                return 0;
        }
@@ -119,7 +162,7 @@ static ssize_t tls_push(gnutls_transport_ptr ptr, const void *buf, size_t size)
        b.data = discard_const(buf);
        b.length = size;
 
-       status = socket_send(tls->socket, &b, &nwritten, 0);
+       status = socket_send(tls->socket, &b, &nwritten);
        if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
                errno = EAGAIN;
                return -1;
@@ -137,9 +180,8 @@ static ssize_t tls_push(gnutls_transport_ptr ptr, const void *buf, size_t size)
 /*
   destroy a tls session
  */
-static int tls_destructor(void *ptr)
+static int tls_destructor(struct tls_context *tls)
 {
-       struct tls_context *tls = talloc_get_type(ptr, struct tls_context);
        int ret;
        ret = gnutls_bye(tls->session, GNUTLS_SHUT_WR);
        if (ret < 0) {
@@ -200,8 +242,9 @@ static NTSTATUS tls_interrupted(struct tls_context *tls)
 /*
   see how many bytes are pending on the connection
 */
-NTSTATUS tls_socket_pending(struct tls_context *tls, size_t *npending)
+static NTSTATUS tls_socket_pending(struct socket_context *sock, size_t *npending)
 {
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
        if (!tls->tls_enabled || tls->tls_detect) {
                return socket_pending(tls->socket, npending);
        }
@@ -220,13 +263,15 @@ NTSTATUS tls_socket_pending(struct tls_context *tls, size_t *npending)
 /*
   receive data either by tls or normal socket_recv
 */
-NTSTATUS tls_socket_recv(struct tls_context *tls, void *buf, size_t wantlen
-                        size_t *nread)
+static NTSTATUS tls_socket_recv(struct socket_context *sock, void *buf
+                               size_t wantlen, size_t *nread)
 {
        int ret;
        NTSTATUS status;
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
+
        if (tls->tls_enabled && tls->tls_detect) {
-               status = socket_recv(tls->socket, &tls->first_byte, 1, nread, 0);
+               status = socket_recv(tls->socket, &tls->first_byte, 1, nread);
                NT_STATUS_NOT_OK_RETURN(status);
                if (*nread == 0) return NT_STATUS_OK;
                tls->tls_detect = False;
@@ -241,7 +286,7 @@ NTSTATUS tls_socket_recv(struct tls_context *tls, void *buf, size_t wantlen,
        }
 
        if (!tls->tls_enabled) {
-               return socket_recv(tls->socket, buf, wantlen, nread, 0);
+               return socket_recv(tls->socket, buf, wantlen, nread);
        }
 
        status = tls_handshake(tls);
@@ -269,13 +314,15 @@ NTSTATUS tls_socket_recv(struct tls_context *tls, void *buf, size_t wantlen,
 /*
   send data either by tls or normal socket_recv
 */
-NTSTATUS tls_socket_send(struct tls_context *tls, const DATA_BLOB *blob, size_t *sendlen)
+static NTSTATUS tls_socket_send(struct socket_context *sock, 
+                               const DATA_BLOB *blob, size_t *sendlen)
 {
        NTSTATUS status;
        int ret;
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
 
        if (!tls->tls_enabled) {
-               return socket_send(tls->socket, blob, sendlen, 0);
+               return socket_send(tls->socket, blob, sendlen);
        }
 
        status = tls_handshake(tls);
@@ -293,7 +340,7 @@ NTSTATUS tls_socket_send(struct tls_context *tls, const DATA_BLOB *blob, size_t
                return STATUS_MORE_ENTRIES;
        }
        if (ret < 0) {
-               DEBUG(0,("gnutls_record_send of %d failed - %s\n", blob->length, gnutls_strerror(ret)));
+               DEBUG(0,("gnutls_record_send of %d failed - %s\n", (int)blob->length, gnutls_strerror(ret)));
                return NT_STATUS_UNEXPECTED_NETWORK_ERROR;
        }
        *sendlen = ret;
@@ -309,17 +356,23 @@ struct tls_params *tls_initialise(TALLOC_CTX *mem_ctx)
 {
        struct tls_params *params;
        int ret;
-       const char *keyfile = lp_tls_keyfile();
-       const char *certfile = lp_tls_certfile();
-       const char *cafile = lp_tls_cafile();
-       const char *crlfile = lp_tls_crlfile();
+       TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+       const char *keyfile = private_path(tmp_ctx, global_loadparm, lp_tls_keyfile(global_loadparm));
+       const char *certfile = private_path(tmp_ctx, global_loadparm, lp_tls_certfile(global_loadparm));
+       const char *cafile = private_path(tmp_ctx, global_loadparm, lp_tls_cafile(global_loadparm));
+       const char *crlfile = private_path(tmp_ctx, global_loadparm, lp_tls_crlfile(global_loadparm));
+       const char *dhpfile = private_path(tmp_ctx, global_loadparm, lp_tls_dhpfile(global_loadparm));
        void tls_cert_generate(TALLOC_CTX *, const char *, const char *, const char *);
 
        params = talloc(mem_ctx, struct tls_params);
-       if (params == NULL) return NULL;
+       if (params == NULL) {
+               talloc_free(tmp_ctx);
+               return NULL;
+       }
 
-       if (!lp_tls_enabled() || keyfile == NULL || *keyfile == 0) {
+       if (!lp_tls_enabled(global_loadparm) || keyfile == NULL || *keyfile == 0) {
                params->tls_enabled = False;
+               talloc_free(tmp_ctx);
                return params;
        }
 
@@ -361,21 +414,39 @@ struct tls_params *tls_initialise(TALLOC_CTX *mem_ctx)
                goto init_failed;
        }
        
+       
        ret = gnutls_dh_params_init(&params->dh_params);
        if (ret < 0) goto init_failed;
 
-       ret = gnutls_dh_params_generate2(params->dh_params, DH_BITS);
-       if (ret < 0) goto init_failed;
+       if (dhpfile && *dhpfile) {
+               gnutls_datum_t dhparms;
+               size_t size;
+               dhparms.data = (uint8_t *)file_load(dhpfile, &size, mem_ctx);
 
+               if (!dhparms.data) {
+                       DEBUG(0,("Failed to read DH Parms from %s\n", dhpfile));
+                       goto init_failed;
+               }
+               dhparms.size = size;
+                       
+               ret = gnutls_dh_params_import_pkcs3(params->dh_params, &dhparms, GNUTLS_X509_FMT_PEM);
+               if (ret < 0) goto init_failed;
+       } else {
+               ret = gnutls_dh_params_generate2(params->dh_params, DH_BITS);
+               if (ret < 0) goto init_failed;
+       }
+               
        gnutls_certificate_set_dh_params(params->x509_cred, params->dh_params);
 
        params->tls_enabled = True;
 
+       talloc_free(tmp_ctx);
        return params;
 
 init_failed:
        DEBUG(0,("GNUTLS failed to initialise - %s\n", gnutls_strerror(ret)));
        params->tls_enabled = False;
+       talloc_free(tmp_ctx);
        return params;
 }
 
@@ -383,24 +454,44 @@ init_failed:
 /*
   setup for a new connection
 */
-struct tls_context *tls_init_server(struct tls_params *params, 
-                                   struct socket_context *socket,
-                                   struct fd_event *fde, 
-                                   const char *plain_chars,
-                                   BOOL tls_enable)
+struct socket_context *tls_init_server(struct tls_params *params, 
+                                      struct socket_context *socket,
+                                      struct fd_event *fde, 
+                                      const char *plain_chars)
 {
        struct tls_context *tls;
        int ret;
+       struct socket_context *new_sock;
+       NTSTATUS nt_status;
+       
+       nt_status = socket_create_with_ops(socket, &tls_socket_ops, &new_sock, 
+                                          SOCKET_TYPE_STREAM, 
+                                          socket->flags | SOCKET_FLAG_ENCRYPT);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               return NULL;
+       }
 
-       tls = talloc(socket, struct tls_context);
-       if (tls == NULL) return NULL;
+       tls = talloc(new_sock, struct tls_context);
+       if (tls == NULL) {
+               return NULL;
+       }
 
        tls->socket          = socket;
        tls->fde             = fde;
+       if (talloc_reference(tls, fde) == NULL) {
+               talloc_free(new_sock);
+               return NULL;
+       }
+       if (talloc_reference(tls, socket) == NULL) {
+               talloc_free(new_sock);
+               return NULL;
+       }
 
-       if (!params->tls_enabled || !tls_enable) {
-               tls->tls_enabled = False;
-               return tls;
+       new_sock->private_data    = tls;
+
+       if (!params->tls_enabled) {
+               talloc_free(new_sock);
+               return NULL;
        }
 
        TLSCHECK(gnutls_init(&tls->session, GNUTLS_SERVER));
@@ -430,42 +521,60 @@ struct tls_context *tls_init_server(struct tls_params *params,
        tls->tls_enabled     = True;
        tls->interrupted     = False;
        
-       return tls;
+       new_sock->state = SOCKET_STATE_SERVER_CONNECTED;
+
+       return new_sock;
 
 failed:
        DEBUG(0,("TLS init connection failed - %s\n", gnutls_strerror(ret)));
-       tls->tls_enabled = False;
-       params->tls_enabled = False;
-       return tls;
+       talloc_free(new_sock);
+       return NULL;
 }
 
 
 /*
   setup for a new client connection
 */
-struct tls_context *tls_init_client(struct socket_context *socket,
-                                   struct fd_event *fde, 
-                                   BOOL tls_enable)
+struct socket_context *tls_init_client(struct socket_context *socket,
+                                      struct fd_event *fde)
 {
        struct tls_context *tls;
-       int ret;
+       int ret = 0;
        const int cert_type_priority[] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };
-       tls = talloc(socket, struct tls_context);
+       char *cafile;
+       struct socket_context *new_sock;
+       NTSTATUS nt_status;
+       
+       nt_status = socket_create_with_ops(socket, &tls_socket_ops, &new_sock, 
+                                          SOCKET_TYPE_STREAM, 
+                                          socket->flags | SOCKET_FLAG_ENCRYPT);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               return NULL;
+       }
+
+       tls = talloc(new_sock, struct tls_context);
        if (tls == NULL) return NULL;
 
        tls->socket          = socket;
        tls->fde             = fde;
-       tls->tls_enabled     = tls_enable;
+       if (talloc_reference(tls, fde) == NULL) {
+               return NULL;
+       }
+       if (talloc_reference(tls, socket) == NULL) {
+               return NULL;
+       }
+       new_sock->private_data    = tls;
 
-       if (!tls->tls_enabled) {
-               return tls;
+       cafile = private_path(tls, global_loadparm, lp_tls_cafile(global_loadparm));
+       if (!cafile || !*cafile) {
+               goto failed;
        }
 
        gnutls_global_init();
 
        gnutls_certificate_allocate_credentials(&tls->xcred);
-       gnutls_certificate_set_x509_trust_file(tls->xcred, lp_tls_cafile(),
-                                              GNUTLS_X509_FMT_PEM);
+       gnutls_certificate_set_x509_trust_file(tls->xcred, cafile, GNUTLS_X509_FMT_PEM);
+       talloc_free(cafile);
        TLSCHECK(gnutls_init(&tls->session, GNUTLS_CLIENT));
        TLSCHECK(gnutls_set_default_priority(tls->session));
        gnutls_certificate_type_set_priority(tls->session, cert_type_priority);
@@ -485,76 +594,101 @@ struct tls_context *tls_init_client(struct socket_context *socket,
        tls->tls_enabled     = True;
        tls->interrupted     = False;
        
-       return tls;
+       new_sock->state = SOCKET_STATE_CLIENT_CONNECTED;
+
+       return new_sock;
 
 failed:
        DEBUG(0,("TLS init connection failed - %s\n", gnutls_strerror(ret)));
        tls->tls_enabled = False;
-       return tls;
+       return new_sock;
 }
 
-BOOL tls_enabled(struct tls_context *tls)
+static NTSTATUS tls_socket_set_option(struct socket_context *sock, const char *option, const char *val)
 {
-       return tls->tls_enabled;
+       set_socket_options(socket_get_fd(sock), option);
+       return NT_STATUS_OK;
 }
 
-BOOL tls_support(struct tls_params *params)
+static char *tls_socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx)
 {
-       return params->tls_enabled;
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
+       return socket_get_peer_name(tls->socket, mem_ctx);
 }
 
-#else
-
-/* for systems without tls we just map the tls socket calls to the
-   normal socket calls */
-
-struct tls_params *tls_initialise(TALLOC_CTX *mem_ctx)
+static struct socket_address *tls_socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
 {
-       return talloc_new(mem_ctx);
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
+       return socket_get_peer_addr(tls->socket, mem_ctx);
 }
 
-struct tls_context *tls_init_server(struct tls_params *params, 
-                                   struct socket_context *sock, 
-                                   struct fd_event *fde,
-                                   const char *plain_chars,
-                                   BOOL tls_enable)
+static struct socket_address *tls_socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
 {
-       if (plain_chars == NULL) return NULL;
-       return (struct tls_context *)sock;
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
+       return socket_get_my_addr(tls->socket, mem_ctx);
 }
 
-struct tls_context *tls_init_client(struct socket_context *sock, 
-                                   struct fd_event *fde,
-                                   BOOL tls_enable)
+static int tls_socket_get_fd(struct socket_context *sock)
 {
-       return (struct tls_context *)sock;
+       struct tls_context *tls = talloc_get_type(sock->private_data, struct tls_context);
+       return socket_get_fd(tls->socket);
 }
 
+static const struct socket_ops tls_socket_ops = {
+       .name                   = "tls",
+       .fn_init                = tls_socket_init,
+       .fn_recv                = tls_socket_recv,
+       .fn_send                = tls_socket_send,
+       .fn_pending             = tls_socket_pending,
+
+       .fn_set_option          = tls_socket_set_option,
+
+       .fn_get_peer_name       = tls_socket_get_peer_name,
+       .fn_get_peer_addr       = tls_socket_get_peer_addr,
+       .fn_get_my_addr         = tls_socket_get_my_addr,
+       .fn_get_fd              = tls_socket_get_fd
+};
 
-NTSTATUS tls_socket_recv(struct tls_context *tls, void *buf, size_t wantlen, 
-                        size_t *nread)
+BOOL tls_support(struct tls_params *params)
 {
-       return socket_recv((struct socket_context *)tls, buf, wantlen, nread, 0);
+       return params->tls_enabled;
 }
 
-NTSTATUS tls_socket_send(struct tls_context *tls, const DATA_BLOB *blob, size_t *sendlen)
+#else
+
+/* for systems without tls we just fail the operations, and the caller
+ * will retain the original socket */
+
+struct tls_params *tls_initialise(TALLOC_CTX *mem_ctx)
 {
-       return socket_send((struct socket_context *)tls, blob, sendlen, 0);
+       return talloc_new(mem_ctx);
 }
 
-BOOL tls_enabled(struct tls_context *tls)
+/*
+  setup for a new connection
+*/
+struct socket_context *tls_init_server(struct tls_params *params, 
+                                   struct socket_context *socket,
+                                   struct fd_event *fde, 
+                                   const char *plain_chars)
 {
-       return False;
+       return NULL;
 }
 
-BOOL tls_support(struct tls_params *params)
+
+/*
+  setup for a new client connection
+*/
+struct socket_context *tls_init_client(struct socket_context *socket,
+                                      struct fd_event *fde)
 {
-       return False;
+       return NULL;
 }
 
-NTSTATUS tls_socket_pending(struct tls_context *tls, size_t *npending)
+BOOL tls_support(struct tls_params *params)
 {
-       return socket_pending((struct socket_context *)tls, npending);
+       return False;
 }
 
 #endif
+