/* Unix SMB/CIFS implementation. Copyright (C) Stefan Metzmacher 2010 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 3 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, see . */ #include "includes.h" #include "system/network.h" #include "system/filesys.h" #include "../util/tevent_unix.h" #include "../lib/tsocket/tsocket.h" #include "../lib/tsocket/tsocket_internal.h" #include "lib/tls/tls.h" #if ENABLE_GNUTLS #include #define DH_BITS 1024 #if defined(HAVE_GNUTLS_DATUM) && !defined(HAVE_GNUTLS_DATUM_T) typedef gnutls_datum gnutls_datum_t; #endif #endif /* ENABLE_GNUTLS */ static const struct tstream_context_ops tstream_tls_ops; struct tstream_tls { struct tstream_context *plain_stream; int error; #if ENABLE_GNUTLS gnutls_session tls_session; #endif /* ENABLE_GNUTLS */ struct tevent_context *current_ev; struct tevent_immediate *retry_im; struct { uint8_t *buf; off_t ofs; struct iovec iov; struct tevent_req *subreq; struct tevent_immediate *im; } push; struct { uint8_t *buf; struct iovec iov; struct tevent_req *subreq; } pull; struct { struct tevent_req *req; } handshake; struct { off_t ofs; size_t left; uint8_t buffer[1024]; struct tevent_req *req; } write; struct { off_t ofs; size_t left; uint8_t buffer[1024]; struct tevent_req *req; } read; struct { struct tevent_req *req; } disconnect; }; static void tstream_tls_retry_handshake(struct tstream_context *stream); static void tstream_tls_retry_read(struct tstream_context *stream); static void tstream_tls_retry_write(struct tstream_context *stream); static void tstream_tls_retry_disconnect(struct tstream_context *stream); static void tstream_tls_retry_trigger(struct tevent_context *ctx, struct tevent_immediate *im, void *private_data); static void tstream_tls_retry(struct tstream_context *stream, bool deferred) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); if (tlss->disconnect.req) { tstream_tls_retry_disconnect(stream); return; } if (tlss->handshake.req) { tstream_tls_retry_handshake(stream); return; } if (tlss->write.req && tlss->read.req && !deferred) { tevent_schedule_immediate(tlss->retry_im, tlss->current_ev, tstream_tls_retry_trigger, stream); } if (tlss->write.req) { tstream_tls_retry_write(stream); return; } if (tlss->read.req) { tstream_tls_retry_read(stream); return; } } static void tstream_tls_retry_trigger(struct tevent_context *ctx, struct tevent_immediate *im, void *private_data) { struct tstream_context *stream = talloc_get_type_abort(private_data, struct tstream_context); tstream_tls_retry(stream, true); } #if ENABLE_GNUTLS static void tstream_tls_push_trigger_write(struct tevent_context *ev, struct tevent_immediate *im, void *private_data); static ssize_t tstream_tls_push_function(gnutls_transport_ptr ptr, const void *buf, size_t size) { struct tstream_context *stream = talloc_get_type_abort(ptr, struct tstream_context); struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); uint8_t *nbuf; size_t len; if (tlss->error != 0) { errno = tlss->error; return -1; } if (tlss->push.subreq) { errno = EAGAIN; return -1; } len = MIN(size, UINT16_MAX - tlss->push.ofs); if (len == 0) { errno = EAGAIN; return -1; } nbuf = talloc_realloc(tlss, tlss->push.buf, uint8_t, tlss->push.ofs + len); if (nbuf == NULL) { if (tlss->push.buf) { errno = EAGAIN; return -1; } return -1; } tlss->push.buf = nbuf; memcpy(tlss->push.buf + tlss->push.ofs, buf, len); if (tlss->push.im == NULL) { tlss->push.im = tevent_create_immediate(tlss); if (tlss->push.im == NULL) { errno = ENOMEM; return -1; } } if (tlss->push.ofs == 0) { /* * We'll do start the tstream_writev * in the next event cycle. * * This way we can batch all push requests, * if they fit into a UINT16_MAX buffer. * * This is important as gnutls_handshake() * had a bug in some versions e.g. 2.4.1 * and others (See bug #7218) and it doesn't * handle EAGAIN. */ tevent_schedule_immediate(tlss->push.im, tlss->current_ev, tstream_tls_push_trigger_write, stream); } tlss->push.ofs += len; return len; } static void tstream_tls_push_done(struct tevent_req *subreq); static void tstream_tls_push_trigger_write(struct tevent_context *ev, struct tevent_immediate *im, void *private_data) { struct tstream_context *stream = talloc_get_type_abort(private_data, struct tstream_context); struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *subreq; if (tlss->push.subreq) { /* nothing todo */ return; } tlss->push.iov.iov_base = (char *)tlss->push.buf; tlss->push.iov.iov_len = tlss->push.ofs; subreq = tstream_writev_send(tlss, tlss->current_ev, tlss->plain_stream, &tlss->push.iov, 1); if (subreq == NULL) { tlss->error = ENOMEM; tstream_tls_retry(stream, false); return; } tevent_req_set_callback(subreq, tstream_tls_push_done, stream); tlss->push.subreq = subreq; } static void tstream_tls_push_done(struct tevent_req *subreq) { struct tstream_context *stream = tevent_req_callback_data(subreq, struct tstream_context); struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); int ret; int sys_errno; tlss->push.subreq = NULL; ZERO_STRUCT(tlss->push.iov); TALLOC_FREE(tlss->push.buf); tlss->push.ofs = 0; ret = tstream_writev_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tlss->error = sys_errno; tstream_tls_retry(stream, false); return; } tstream_tls_retry(stream, false); } static void tstream_tls_pull_done(struct tevent_req *subreq); static ssize_t tstream_tls_pull_function(gnutls_transport_ptr ptr, void *buf, size_t size) { struct tstream_context *stream = talloc_get_type_abort(ptr, struct tstream_context); struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *subreq; size_t len; if (tlss->error != 0) { errno = tlss->error; return -1; } if (tlss->pull.subreq) { errno = EAGAIN; return -1; } if (tlss->pull.iov.iov_base) { uint8_t *b; size_t n; b = (uint8_t *)tlss->pull.iov.iov_base; n = MIN(tlss->pull.iov.iov_len, size); memcpy(buf, b, n); tlss->pull.iov.iov_len -= n; b += n; tlss->pull.iov.iov_base = (char *)b; if (tlss->pull.iov.iov_len == 0) { tlss->pull.iov.iov_base = NULL; TALLOC_FREE(tlss->pull.buf); } return n; } if (size == 0) { return 0; } len = MIN(size, UINT16_MAX); tlss->pull.buf = talloc_array(tlss, uint8_t, len); if (tlss->pull.buf == NULL) { return -1; } tlss->pull.iov.iov_base = (char *)tlss->pull.buf; tlss->pull.iov.iov_len = len; subreq = tstream_readv_send(tlss, tlss->current_ev, tlss->plain_stream, &tlss->pull.iov, 1); if (subreq == NULL) { errno = ENOMEM; return -1; } tevent_req_set_callback(subreq, tstream_tls_pull_done, stream); tlss->pull.subreq = subreq; errno = EAGAIN; return -1; } static void tstream_tls_pull_done(struct tevent_req *subreq) { struct tstream_context *stream = tevent_req_callback_data(subreq, struct tstream_context); struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); int ret; int sys_errno; tlss->pull.subreq = NULL; ret = tstream_readv_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret == -1) { tlss->error = sys_errno; tstream_tls_retry(stream, false); return; } tstream_tls_retry(stream, false); } #endif /* ENABLE_GNUTLS */ static int tstream_tls_destructor(struct tstream_tls *tlss) { #if ENABLE_GNUTLS if (tlss->tls_session) { gnutls_deinit(tlss->tls_session); tlss->tls_session = NULL; } #endif /* ENABLE_GNUTLS */ return 0; } static ssize_t tstream_tls_pending_bytes(struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); size_t ret; if (tlss->error != 0) { errno = tlss->error; return -1; } #if ENABLE_GNUTLS ret = gnutls_record_check_pending(tlss->tls_session); ret += tlss->read.left; #else /* ENABLE_GNUTLS */ errno = ENOSYS; ret = -1; #endif /* ENABLE_GNUTLS */ return ret; } struct tstream_tls_readv_state { struct tstream_context *stream; struct iovec *vector; int count; int ret; }; static void tstream_tls_readv_crypt_next(struct tevent_req *req); static struct tevent_req *tstream_tls_readv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream, struct iovec *vector, size_t count) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req; struct tstream_tls_readv_state *state; tlss->read.req = NULL; tlss->current_ev = ev; req = tevent_req_create(mem_ctx, &state, struct tstream_tls_readv_state); if (req == NULL) { return NULL; } state->stream = stream; state->ret = 0; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return tevent_req_post(req, ev); } /* * we make a copy of the vector so we can change the structure */ state->vector = talloc_array(state, struct iovec, count); if (tevent_req_nomem(state->vector, req)) { return tevent_req_post(req, ev); } memcpy(state->vector, vector, sizeof(struct iovec) * count); state->count = count; tstream_tls_readv_crypt_next(req); if (!tevent_req_is_in_progress(req)) { return tevent_req_post(req, ev); } return req; } static void tstream_tls_readv_crypt_next(struct tevent_req *req) { struct tstream_tls_readv_state *state = tevent_req_data(req, struct tstream_tls_readv_state); struct tstream_tls *tlss = tstream_context_data(state->stream, struct tstream_tls); /* * copy the pending buffer first */ while (tlss->read.left > 0 && state->count > 0) { uint8_t *base = (uint8_t *)state->vector[0].iov_base; size_t len = MIN(tlss->read.left, state->vector[0].iov_len); memcpy(base, tlss->read.buffer + tlss->read.ofs, len); base += len; state->vector[0].iov_base = (char *) base; state->vector[0].iov_len -= len; tlss->read.ofs += len; tlss->read.left -= len; if (state->vector[0].iov_len == 0) { state->vector += 1; state->count -= 1; } state->ret += len; } if (state->count == 0) { tevent_req_done(req); return; } tlss->read.req = req; tstream_tls_retry_read(state->stream); } static void tstream_tls_retry_read(struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req = tlss->read.req; #if ENABLE_GNUTLS int ret; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return; } tlss->read.left = 0; tlss->read.ofs = 0; ret = gnutls_record_recv(tlss->tls_session, tlss->read.buffer, sizeof(tlss->read.buffer)); if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { return; } tlss->read.req = NULL; if (gnutls_error_is_fatal(ret) != 0) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } if (ret == 0) { tlss->error = EPIPE; tevent_req_error(req, tlss->error); return; } tlss->read.left = ret; tstream_tls_readv_crypt_next(req); #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); #endif /* ENABLE_GNUTLS */ } static int tstream_tls_readv_recv(struct tevent_req *req, int *perrno) { struct tstream_tls_readv_state *state = tevent_req_data(req, struct tstream_tls_readv_state); struct tstream_tls *tlss = tstream_context_data(state->stream, struct tstream_tls); int ret; tlss->read.req = NULL; ret = tsocket_simple_int_recv(req, perrno); if (ret == 0) { ret = state->ret; } tevent_req_received(req); return ret; } struct tstream_tls_writev_state { struct tstream_context *stream; struct iovec *vector; int count; int ret; }; static void tstream_tls_writev_crypt_next(struct tevent_req *req); static struct tevent_req *tstream_tls_writev_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream, const struct iovec *vector, size_t count) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req; struct tstream_tls_writev_state *state; tlss->write.req = NULL; tlss->current_ev = ev; req = tevent_req_create(mem_ctx, &state, struct tstream_tls_writev_state); if (req == NULL) { return NULL; } state->stream = stream; state->ret = 0; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return tevent_req_post(req, ev); } /* * we make a copy of the vector so we can change the structure */ state->vector = talloc_array(state, struct iovec, count); if (tevent_req_nomem(state->vector, req)) { return tevent_req_post(req, ev); } memcpy(state->vector, vector, sizeof(struct iovec) * count); state->count = count; tstream_tls_writev_crypt_next(req); if (!tevent_req_is_in_progress(req)) { return tevent_req_post(req, ev); } return req; } static void tstream_tls_writev_crypt_next(struct tevent_req *req) { struct tstream_tls_writev_state *state = tevent_req_data(req, struct tstream_tls_writev_state); struct tstream_tls *tlss = tstream_context_data(state->stream, struct tstream_tls); tlss->write.left = sizeof(tlss->write.buffer); tlss->write.ofs = 0; /* * first fill our buffer */ while (tlss->write.left > 0 && state->count > 0) { uint8_t *base = (uint8_t *)state->vector[0].iov_base; size_t len = MIN(tlss->write.left, state->vector[0].iov_len); memcpy(tlss->write.buffer + tlss->write.ofs, base, len); base += len; state->vector[0].iov_base = (char *) base; state->vector[0].iov_len -= len; tlss->write.ofs += len; tlss->write.left -= len; if (state->vector[0].iov_len == 0) { state->vector += 1; state->count -= 1; } state->ret += len; } if (tlss->write.ofs == 0) { tevent_req_done(req); return; } tlss->write.left = tlss->write.ofs; tlss->write.ofs = 0; tlss->write.req = req; tstream_tls_retry_write(state->stream); } static void tstream_tls_retry_write(struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req = tlss->write.req; #if ENABLE_GNUTLS int ret; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return; } ret = gnutls_record_send(tlss->tls_session, tlss->write.buffer + tlss->write.ofs, tlss->write.left); if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { return; } tlss->write.req = NULL; if (gnutls_error_is_fatal(ret) != 0) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } if (ret == 0) { tlss->error = EPIPE; tevent_req_error(req, tlss->error); return; } tlss->write.ofs += ret; tlss->write.left -= ret; if (tlss->write.left > 0) { tlss->write.req = req; tstream_tls_retry_write(stream); return; } tstream_tls_writev_crypt_next(req); #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); #endif /* ENABLE_GNUTLS */ } static int tstream_tls_writev_recv(struct tevent_req *req, int *perrno) { struct tstream_tls_writev_state *state = tevent_req_data(req, struct tstream_tls_writev_state); struct tstream_tls *tlss = tstream_context_data(state->stream, struct tstream_tls); int ret; tlss->write.req = NULL; ret = tsocket_simple_int_recv(req, perrno); if (ret == 0) { ret = state->ret; } tevent_req_received(req); return ret; } struct tstream_tls_disconnect_state { uint8_t _dummy; }; static struct tevent_req *tstream_tls_disconnect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req; struct tstream_tls_disconnect_state *state; tlss->disconnect.req = NULL; tlss->current_ev = ev; req = tevent_req_create(mem_ctx, &state, struct tstream_tls_disconnect_state); if (req == NULL) { return NULL; } if (tlss->error != 0) { tevent_req_error(req, tlss->error); return tevent_req_post(req, ev); } tlss->disconnect.req = req; tstream_tls_retry_disconnect(stream); if (!tevent_req_is_in_progress(req)) { return tevent_req_post(req, ev); } return req; } static void tstream_tls_retry_disconnect(struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req = tlss->disconnect.req; #if ENABLE_GNUTLS int ret; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return; } ret = gnutls_bye(tlss->tls_session, GNUTLS_SHUT_WR); if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { return; } tlss->disconnect.req = NULL; if (gnutls_error_is_fatal(ret) != 0) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } if (ret != GNUTLS_E_SUCCESS) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } tevent_req_done(req); #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); #endif /* ENABLE_GNUTLS */ } static int tstream_tls_disconnect_recv(struct tevent_req *req, int *perrno) { int ret; ret = tsocket_simple_int_recv(req, perrno); tevent_req_received(req); return ret; } static const struct tstream_context_ops tstream_tls_ops = { .name = "tls", .pending_bytes = tstream_tls_pending_bytes, .readv_send = tstream_tls_readv_send, .readv_recv = tstream_tls_readv_recv, .writev_send = tstream_tls_writev_send, .writev_recv = tstream_tls_writev_recv, .disconnect_send = tstream_tls_disconnect_send, .disconnect_recv = tstream_tls_disconnect_recv, }; struct tstream_tls_params { #if ENABLE_GNUTLS gnutls_certificate_credentials x509_cred; gnutls_dh_params dh_params; #endif /* ENABLE_GNUTLS */ bool tls_enabled; }; static int tstream_tls_params_destructor(struct tstream_tls_params *tlsp) { #if ENABLE_GNUTLS if (tlsp->x509_cred) { gnutls_certificate_free_credentials(tlsp->x509_cred); tlsp->x509_cred = NULL; } if (tlsp->dh_params) { gnutls_dh_params_deinit(tlsp->dh_params); tlsp->dh_params = NULL; } #endif /* ENABLE_GNUTLS */ return 0; } bool tstream_tls_params_enabled(struct tstream_tls_params *tlsp) { return tlsp->tls_enabled; } NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx, const char *ca_file, const char *crl_file, struct tstream_tls_params **_tlsp) { #if ENABLE_GNUTLS struct tstream_tls_params *tlsp; int ret; ret = gnutls_global_init(); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); return NT_STATUS_NOT_SUPPORTED; } tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); NT_STATUS_HAVE_NO_MEMORY(tlsp); talloc_set_destructor(tlsp, tstream_tls_params_destructor); ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_NO_MEMORY; } if (ca_file && *ca_file) { ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, ca_file, GNUTLS_X509_FMT_PEM); if (ret < 0) { DEBUG(0,("TLS failed to initialise cafile %s - %s\n", ca_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } } if (crl_file && *crl_file) { ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, crl_file, GNUTLS_X509_FMT_PEM); if (ret < 0) { DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", crl_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } } tlsp->tls_enabled = true; *_tlsp = tlsp; return NT_STATUS_OK; #else /* ENABLE_GNUTLS */ return NT_STATUS_NOT_IMPLEMENTED; #endif /* ENABLE_GNUTLS */ } struct tstream_tls_connect_state { struct tstream_context *tls_stream; }; struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *plain_stream, struct tstream_tls_params *tls_params, const char *location) { struct tevent_req *req; struct tstream_tls_connect_state *state; #if ENABLE_GNUTLS struct tstream_tls *tlss; int ret; static const int cert_type_priority[] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 }; #endif /* ENABLE_GNUTLS */ req = tevent_req_create(mem_ctx, &state, struct tstream_tls_connect_state); if (req == NULL) { return NULL; } #if ENABLE_GNUTLS state->tls_stream = tstream_context_create(state, &tstream_tls_ops, &tlss, struct tstream_tls, location); if (tevent_req_nomem(state->tls_stream, req)) { return tevent_req_post(req, ev); } ZERO_STRUCTP(tlss); talloc_set_destructor(tlss, tstream_tls_destructor); tlss->plain_stream = plain_stream; tlss->current_ev = ev; tlss->retry_im = tevent_create_immediate(tlss); if (tevent_req_nomem(tlss->retry_im, req)) { return tevent_req_post(req, ev); } ret = gnutls_init(&tlss->tls_session, GNUTLS_CLIENT); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } ret = gnutls_set_default_priority(tlss->tls_session); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } gnutls_certificate_type_set_priority(tlss->tls_session, cert_type_priority); ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, tls_params->x509_cred); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr)state->tls_stream); gnutls_transport_set_pull_function(tlss->tls_session, (gnutls_pull_func)tstream_tls_pull_function); gnutls_transport_set_push_function(tlss->tls_session, (gnutls_push_func)tstream_tls_push_function); #if GNUTLS_VERSION_MAJOR < 3 gnutls_transport_set_lowat(tlss->tls_session, 0); #endif tlss->handshake.req = req; tstream_tls_retry_handshake(state->tls_stream); if (!tevent_req_is_in_progress(req)) { return tevent_req_post(req, ev); } return req; #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); return tevent_req_post(req, ev); #endif /* ENABLE_GNUTLS */ } int tstream_tls_connect_recv(struct tevent_req *req, int *perrno, TALLOC_CTX *mem_ctx, struct tstream_context **tls_stream) { struct tstream_tls_connect_state *state = tevent_req_data(req, struct tstream_tls_connect_state); if (tevent_req_is_unix_error(req, perrno)) { tevent_req_received(req); return -1; } *tls_stream = talloc_move(mem_ctx, &state->tls_stream); tevent_req_received(req); return 0; } extern void tls_cert_generate(TALLOC_CTX *, const char *, const char *, const char *, const char *); /* initialise global tls state */ NTSTATUS tstream_tls_params_server(TALLOC_CTX *mem_ctx, const char *dns_host_name, bool enabled, const char *key_file, const char *cert_file, const char *ca_file, const char *crl_file, const char *dhp_file, struct tstream_tls_params **_tlsp) { struct tstream_tls_params *tlsp; #if ENABLE_GNUTLS int ret; struct stat st; if (!enabled || key_file == NULL || *key_file == 0) { tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); NT_STATUS_HAVE_NO_MEMORY(tlsp); talloc_set_destructor(tlsp, tstream_tls_params_destructor); tlsp->tls_enabled = false; *_tlsp = tlsp; return NT_STATUS_OK; } ret = gnutls_global_init(); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); return NT_STATUS_NOT_SUPPORTED; } tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); NT_STATUS_HAVE_NO_MEMORY(tlsp); talloc_set_destructor(tlsp, tstream_tls_params_destructor); if (!file_exist(ca_file)) { tls_cert_generate(tlsp, dns_host_name, key_file, cert_file, ca_file); } if (file_exist(key_file) && !file_check_permissions(key_file, geteuid(), 0400, &st) && !file_check_permissions(key_file, geteuid(), 0600, &st)) { DEBUG(0, ("Invalid permissions on TLS private key file '%s':\n" "owner uid %u should be %u, mode %04o should be %04o or %04o\n" "This is known as CVE-2013-4476.\n" "Removing all tls .pem files will cause an " "auto-regeneration with the correct permissions.\n", key_file, (unsigned int)st.st_uid, geteuid(), (unsigned int)(st.st_mode & 0777), 0400, 0600)); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_NO_MEMORY; } if (ca_file && *ca_file) { ret = gnutls_certificate_set_x509_trust_file(tlsp->x509_cred, ca_file, GNUTLS_X509_FMT_PEM); if (ret < 0) { DEBUG(0,("TLS failed to initialise cafile %s - %s\n", ca_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } } if (crl_file && *crl_file) { ret = gnutls_certificate_set_x509_crl_file(tlsp->x509_cred, crl_file, GNUTLS_X509_FMT_PEM); if (ret < 0) { DEBUG(0,("TLS failed to initialise crlfile %s - %s\n", crl_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } } ret = gnutls_certificate_set_x509_key_file(tlsp->x509_cred, cert_file, key_file, GNUTLS_X509_FMT_PEM); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS failed to initialise certfile %s and keyfile %s - %s\n", cert_file, key_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } ret = gnutls_dh_params_init(&tlsp->dh_params); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_NO_MEMORY; } if (dhp_file && *dhp_file) { gnutls_datum_t dhparms; size_t size; dhparms.data = (uint8_t *)file_load(dhp_file, &size, 0, tlsp); if (!dhparms.data) { DEBUG(0,("TLS failed to read DH Parms from %s - %d:%s\n", dhp_file, errno, strerror(errno))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } dhparms.size = size; ret = gnutls_dh_params_import_pkcs3(tlsp->dh_params, &dhparms, GNUTLS_X509_FMT_PEM); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS failed to import pkcs3 %s - %s\n", dhp_file, gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } } else { ret = gnutls_dh_params_generate2(tlsp->dh_params, DH_BITS); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS failed to generate dh_params - %s\n", gnutls_strerror(ret))); talloc_free(tlsp); return NT_STATUS_INTERNAL_ERROR; } } gnutls_certificate_set_dh_params(tlsp->x509_cred, tlsp->dh_params); tlsp->tls_enabled = true; #else /* ENABLE_GNUTLS */ tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); NT_STATUS_HAVE_NO_MEMORY(tlsp); talloc_set_destructor(tlsp, tstream_tls_params_destructor); tlsp->tls_enabled = false; #endif /* ENABLE_GNUTLS */ *_tlsp = tlsp; return NT_STATUS_OK; } struct tstream_tls_accept_state { struct tstream_context *tls_stream; }; struct tevent_req *_tstream_tls_accept_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct tstream_context *plain_stream, struct tstream_tls_params *tlsp, const char *location) { struct tevent_req *req; struct tstream_tls_accept_state *state; struct tstream_tls *tlss; #if ENABLE_GNUTLS int ret; #endif /* ENABLE_GNUTLS */ req = tevent_req_create(mem_ctx, &state, struct tstream_tls_accept_state); if (req == NULL) { return NULL; } state->tls_stream = tstream_context_create(state, &tstream_tls_ops, &tlss, struct tstream_tls, location); if (tevent_req_nomem(state->tls_stream, req)) { return tevent_req_post(req, ev); } ZERO_STRUCTP(tlss); talloc_set_destructor(tlss, tstream_tls_destructor); #if ENABLE_GNUTLS tlss->plain_stream = plain_stream; tlss->current_ev = ev; tlss->retry_im = tevent_create_immediate(tlss); if (tevent_req_nomem(tlss->retry_im, req)) { return tevent_req_post(req, ev); } ret = gnutls_init(&tlss->tls_session, GNUTLS_SERVER); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } ret = gnutls_set_default_priority(tlss->tls_session); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, tlsp->x509_cred); if (ret != GNUTLS_E_SUCCESS) { DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } gnutls_certificate_server_set_request(tlss->tls_session, GNUTLS_CERT_REQUEST); gnutls_dh_set_prime_bits(tlss->tls_session, DH_BITS); gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr)state->tls_stream); gnutls_transport_set_pull_function(tlss->tls_session, (gnutls_pull_func)tstream_tls_pull_function); gnutls_transport_set_push_function(tlss->tls_session, (gnutls_push_func)tstream_tls_push_function); #if GNUTLS_VERSION_MAJOR < 3 gnutls_transport_set_lowat(tlss->tls_session, 0); #endif tlss->handshake.req = req; tstream_tls_retry_handshake(state->tls_stream); if (!tevent_req_is_in_progress(req)) { return tevent_req_post(req, ev); } return req; #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); return tevent_req_post(req, ev); #endif /* ENABLE_GNUTLS */ } static void tstream_tls_retry_handshake(struct tstream_context *stream) { struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls); struct tevent_req *req = tlss->handshake.req; #if ENABLE_GNUTLS int ret; if (tlss->error != 0) { tevent_req_error(req, tlss->error); return; } ret = gnutls_handshake(tlss->tls_session); if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { return; } tlss->handshake.req = NULL; if (gnutls_error_is_fatal(ret) != 0) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } if (ret != GNUTLS_E_SUCCESS) { DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret))); tlss->error = EIO; tevent_req_error(req, tlss->error); return; } tevent_req_done(req); #else /* ENABLE_GNUTLS */ tevent_req_error(req, ENOSYS); #endif /* ENABLE_GNUTLS */ } int tstream_tls_accept_recv(struct tevent_req *req, int *perrno, TALLOC_CTX *mem_ctx, struct tstream_context **tls_stream) { struct tstream_tls_accept_state *state = tevent_req_data(req, struct tstream_tls_accept_state); if (tevent_req_is_unix_error(req, perrno)) { tevent_req_received(req); return -1; } *tls_stream = talloc_move(mem_ctx, &state->tls_stream); tevent_req_received(req); return 0; }