/* 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 "system/time.h" #include "lib/util/util_file.h" #include "../util/tevent_unix.h" #include "../lib/tsocket/tsocket.h" #include "../lib/tsocket/tsocket_internal.h" #include "../lib/util/util_net.h" #include "lib/tls/tls.h" #include "lib/param/param.h" #include #include #include "lib/crypto/gnutls_helpers.h" #define DH_BITS 2048 const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer) { switch (verify_peer) { case TLS_VERIFY_PEER_NO_CHECK: return TLS_VERIFY_PEER_NO_CHECK_STRING; case TLS_VERIFY_PEER_CA_ONLY: return TLS_VERIFY_PEER_CA_ONLY_STRING; case TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE: return TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING; case TLS_VERIFY_PEER_CA_AND_NAME: return TLS_VERIFY_PEER_CA_AND_NAME_STRING; case TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE: return TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING; } return "unknown tls_verify_peer_state"; } static const struct tstream_context_ops tstream_tls_ops; struct tstream_tls { struct tstream_context *plain_stream; int error; gnutls_session_t tls_session; bool is_server; enum tls_verify_peer_state verify_peer; const char *peer_name; DATA_BLOB channel_bindings; struct tevent_context *current_ev; struct tevent_immediate *retry_im; struct { struct tevent_req *mgmt_req; } waiting_flush; struct { uint8_t *buf; off_t ofs; struct iovec iov; struct tevent_req *subreq; } 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->push.subreq == NULL && tlss->pull.subreq == NULL) { if (tlss->waiting_flush.mgmt_req != NULL) { struct tevent_req *req = tlss->waiting_flush.mgmt_req; tlss->waiting_flush.mgmt_req = NULL; tevent_req_done(req); return; } } 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); } static void tstream_tls_push_done(struct tevent_req *subreq); static ssize_t tstream_tls_push_function(gnutls_transport_ptr_t 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); struct tevent_req *subreq = NULL; 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); tlss->push.ofs += len; 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) { errno = ENOMEM; return -1; } tevent_req_set_callback(subreq, tstream_tls_push_done, stream); tlss->push.subreq = subreq; return len; } 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_t 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); } static int tstream_tls_destructor(struct tstream_tls *tlss) { if (tlss->tls_session) { gnutls_deinit(tlss->tls_session); tlss->tls_session = NULL; } 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; } ret = gnutls_record_check_pending(tlss->tls_session); ret += tlss->read.left; 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; if (tlss->current_ev != ev) { SMB_ASSERT(tlss->push.subreq == NULL); SMB_ASSERT(tlss->pull.subreq == 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; 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); } 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; if (tlss->current_ev != ev) { SMB_ASSERT(tlss->push.subreq == NULL); SMB_ASSERT(tlss->pull.subreq == 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; 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); } 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; if (tlss->current_ev != ev) { SMB_ASSERT(tlss->push.subreq == NULL); SMB_ASSERT(tlss->pull.subreq == 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; 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; } if (tlss->push.subreq != NULL || tlss->pull.subreq != NULL) { tlss->waiting_flush.mgmt_req = req; return; } tevent_req_done(req); } 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_internal { gnutls_certificate_credentials_t x509_cred; gnutls_dh_params_t dh_params; const char *tls_priority; bool tls_enabled; enum tls_verify_peer_state verify_peer; const char *peer_name; }; struct tstream_tls_params { struct tstream_tls_params_internal *internal; }; static int tstream_tls_params_internal_destructor(struct tstream_tls_params_internal *tlsp) { 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; } return 0; } bool tstream_tls_params_enabled(struct tstream_tls_params *tls_params) { struct tstream_tls_params_internal *tlsp = tls_params->internal; return tlsp->tls_enabled; } static NTSTATUS tstream_tls_setup_channel_bindings(struct tstream_tls *tlss) { gnutls_datum_t cb = { .size = 0 }; int ret; #ifdef HAVE_GNUTLS_CB_TLS_SERVER_END_POINT ret = gnutls_session_channel_binding(tlss->tls_session, GNUTLS_CB_TLS_SERVER_END_POINT, &cb); #else /* not HAVE_GNUTLS_CB_TLS_SERVER_END_POINT */ ret = legacy_gnutls_server_end_point_cb(tlss->tls_session, tlss->is_server, &cb); #endif /* not HAVE_GNUTLS_CB_TLS_SERVER_END_POINT */ if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } if (cb.size != 0) { /* * Looking at the OpenLDAP implementation * for LDAP_OPT_X_SASL_CBINDING_TLS_ENDPOINT * revealed that we need to prefix it with * 'tls-server-end-point:' */ const char endpoint_prefix[] = "tls-server-end-point:"; size_t prefix_size = strlen(endpoint_prefix); size_t size = prefix_size + cb.size; tlss->channel_bindings = data_blob_talloc_named(tlss, NULL, size, "tls_channel_bindings"); if (tlss->channel_bindings.data == NULL) { gnutls_free(cb.data); return NT_STATUS_NO_MEMORY; } memcpy(tlss->channel_bindings.data, endpoint_prefix, prefix_size); memcpy(tlss->channel_bindings.data + prefix_size, cb.data, cb.size); gnutls_free(cb.data); } return NT_STATUS_OK; } const DATA_BLOB *tstream_tls_channel_bindings(struct tstream_context *tls_tstream) { struct tstream_tls *tlss = talloc_get_type(_tstream_context_data(tls_tstream), struct tstream_tls); if (tlss == NULL) { return NULL; } return &tlss->channel_bindings; } NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx, bool system_cas, const char * const *ca_dirs, const char *ca_file, const char *crl_file, const char *tls_priority, enum tls_verify_peer_state verify_peer, const char *peer_name, struct tstream_tls_params **_tlsp) { struct tstream_tls_params *__tlsp = NULL; struct tstream_tls_params_internal *tlsp = NULL; bool got_ca = false; size_t i; int ret; __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); if (__tlsp == NULL) { return NT_STATUS_NO_MEMORY; } tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); if (tlsp == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); __tlsp->internal = tlsp; tlsp->verify_peer = verify_peer; if (peer_name != NULL) { tlsp->peer_name = talloc_strdup(tlsp, peer_name); if (tlsp->peer_name == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { DEBUG(0,("TLS failed to missing peer_name - " "with 'tls verify peer = %s'\n", tls_verify_peer_string(tlsp->verify_peer))); TALLOC_FREE(__tlsp); return NT_STATUS_INVALID_PARAMETER_MIX; } 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 (system_cas) { ret = gnutls_certificate_set_x509_system_trust(tlsp->x509_cred); if (ret < 0) { DBG_ERR("gnutls_certificate_set_x509_system_trust() - %s\n", gnutls_strerror(ret)); TALLOC_FREE(__tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } if (ret > 0) { got_ca = true; } } for (i = 0; ca_dirs != NULL && ca_dirs[i] != NULL; i++) { const char *ca_dir = ca_dirs[i]; if (!directory_exist(ca_dir)) { continue; } ret = gnutls_certificate_set_x509_trust_dir(tlsp->x509_cred, ca_dir, GNUTLS_X509_FMT_PEM); if (ret < 0) { DBG_ERR("gnutls_certificate_set_x509_trust_dir(%s) - %s\n", ca_dir, gnutls_strerror(ret)); TALLOC_FREE(__tlsp); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } if (ret > 0) { got_ca = true; } } if (ca_file && *ca_file && file_exist(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 (ret > 0) { got_ca = true; } } if (!got_ca && tlsp->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) { D_ERR("TLS: 'tls verify peer = %s' requires " "'tls trust system cas', " "'tls ca directories' or " "'tls cafile'\n", tls_verify_peer_string(tlsp->verify_peer)); TALLOC_FREE(__tlsp); return NT_STATUS_INVALID_PARAMETER_MIX; } if (crl_file && *crl_file && file_exist(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; } } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE) { DEBUG(0,("TLS failed to missing crlfile %s - " "with 'tls verify peer = %s'\n", crl_file, tls_verify_peer_string(tlsp->verify_peer))); TALLOC_FREE(__tlsp); return NT_STATUS_INVALID_PARAMETER_MIX; } tlsp->tls_priority = talloc_strdup(tlsp, tls_priority); if (tlsp->tls_priority == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } tlsp->tls_enabled = true; *_tlsp = __tlsp; return NT_STATUS_OK; } NTSTATUS tstream_tls_params_client_lpcfg(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, const char *peer_name, struct tstream_tls_params **tlsp) { TALLOC_CTX *frame = talloc_stackframe(); bool system_cas = false; const char * const *ca_dirs = NULL; const char *ptr = NULL; char *ca_file = NULL; char *crl_file = NULL; const char *tls_priority = NULL; enum tls_verify_peer_state verify_peer = TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE; NTSTATUS status; system_cas = lpcfg_tls_trust_system_cas(lp_ctx); ca_dirs = lpcfg_tls_ca_directories(lp_ctx); ptr = lpcfg__tls_cafile(lp_ctx); if (ptr != NULL) { ca_file = lpcfg_tls_cafile(frame, lp_ctx); if (ca_file == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } } ptr = lpcfg__tls_crlfile(lp_ctx); if (ptr != NULL) { crl_file = lpcfg_tls_crlfile(frame, lp_ctx); if (crl_file == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } } tls_priority = lpcfg_tls_priority(lp_ctx); verify_peer = lpcfg_tls_verify_peer(lp_ctx); status = tstream_tls_params_client(mem_ctx, system_cas, ca_dirs, ca_file, crl_file, tls_priority, verify_peer, peer_name, tlsp); TALLOC_FREE(frame); return status; } static NTSTATUS tstream_tls_prepare_gnutls(struct tstream_tls_params *_tlsp, struct tstream_tls *tlss) { struct tstream_tls_params_internal *tlsp = NULL; int ret; unsigned int flags; const char *hostname = NULL; if (tlss->is_server) { flags = GNUTLS_SERVER; } else { flags = GNUTLS_CLIENT; /* * tls_tstream can't properly handle 'New Session Ticket' * messages sent 'after' the client sends the 'Finished' * message. GNUTLS_NO_TICKETS was introduced in GnuTLS 3.5.6. * This flag is to indicate the session Flag session should not * use resumption with session tickets. */ flags |= GNUTLS_NO_TICKETS; } /* * Note we need to make sure x509_cred and dh_params * from tstream_tls_params_internal stay alive for * the whole lifetime of this session! * * See 'man gnutls_credentials_set' and * 'man gnutls_certificate_set_dh_params'. * * Note: here we use talloc_reference() in a way * that does not expose it to the caller. */ tlsp = talloc_reference(tlss, _tlsp->internal); if (tlsp == NULL) { return NT_STATUS_NO_MEMORY; } tlss->verify_peer = tlsp->verify_peer; if (tlsp->peer_name != NULL) { bool ip = is_ipaddress(tlsp->peer_name); tlss->peer_name = talloc_strdup(tlss, tlsp->peer_name); if (tlss->peer_name == NULL) { return NT_STATUS_NO_MEMORY; } if (!ip) { hostname = tlss->peer_name; } if (tlss->verify_peer < TLS_VERIFY_PEER_CA_AND_NAME) { hostname = NULL; } } if (tlss->current_ev != NULL) { tlss->retry_im = tevent_create_immediate(tlss); if (tlss->retry_im == NULL) { return NT_STATUS_NO_MEMORY; } } ret = gnutls_init(&tlss->tls_session, flags); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } ret = gnutls_set_default_priority(tlss->tls_session); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } if (strlen(tlsp->tls_priority) > 0) { const char *error_pos = NULL; ret = gnutls_priority_set_direct(tlss->tls_session, tlsp->tls_priority, &error_pos); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } } ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE, tlsp->x509_cred); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } if (hostname != NULL) { ret = gnutls_server_name_set(tlss->tls_session, GNUTLS_NAME_DNS, hostname, strlen(hostname)); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } } if (tlss->is_server) { gnutls_certificate_server_set_request(tlss->tls_session, GNUTLS_CERT_REQUEST); gnutls_dh_set_prime_bits(tlss->tls_session, DH_BITS); } return NT_STATUS_OK; } static NTSTATUS tstream_tls_verify_peer(struct tstream_tls *tlss) { unsigned int status = UINT32_MAX; bool ip = true; const char *hostname = NULL; int ret; if (tlss->verify_peer == TLS_VERIFY_PEER_NO_CHECK) { return NT_STATUS_OK; } if (tlss->peer_name != NULL) { ip = is_ipaddress(tlss->peer_name); } if (!ip) { hostname = tlss->peer_name; } if (tlss->verify_peer == TLS_VERIFY_PEER_CA_ONLY) { hostname = NULL; } if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) { if (hostname == NULL) { DEBUG(1,("TLS %s - no hostname available for " "verify_peer[%s] and peer_name[%s]\n", __location__, tls_verify_peer_string(tlss->verify_peer), tlss->peer_name)); return NT_STATUS_IMAGE_CERT_REVOKED; } } ret = gnutls_certificate_verify_peers3(tlss->tls_session, hostname, &status); if (ret != GNUTLS_E_SUCCESS) { return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } if (status != 0) { DEBUG(1,("TLS %s - check failed for " "verify_peer[%s] and peer_name[%s] " "status 0x%x (%s%s%s%s%s%s%s%s)\n", __location__, tls_verify_peer_string(tlss->verify_peer), tlss->peer_name, status, status & GNUTLS_CERT_INVALID ? "invalid " : "", status & GNUTLS_CERT_REVOKED ? "revoked " : "", status & GNUTLS_CERT_SIGNER_NOT_FOUND ? "signer_not_found " : "", status & GNUTLS_CERT_SIGNER_NOT_CA ? "signer_not_ca " : "", status & GNUTLS_CERT_INSECURE_ALGORITHM ? "insecure_algorithm " : "", status & GNUTLS_CERT_NOT_ACTIVATED ? "not_activated " : "", status & GNUTLS_CERT_EXPIRED ? "expired " : "", status & GNUTLS_CERT_UNEXPECTED_OWNER ? "unexpected_owner " : "")); return NT_STATUS_IMAGE_CERT_REVOKED; } return NT_STATUS_OK; } 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; struct tstream_tls *tlss; NTSTATUS status; req = tevent_req_create(mem_ctx, &state, struct tstream_tls_connect_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); tlss->plain_stream = plain_stream; tlss->is_server = false; tlss->current_ev = ev; status = tstream_tls_prepare_gnutls(_tls_params, tlss); if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { tevent_req_oom(req); return tevent_req_post(req, ev); } if (!NT_STATUS_IS_OK(status)) { tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr_t)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); 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; } 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; } /* 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, const char *tls_priority, struct tstream_tls_params **_tlsp) { struct tstream_tls_params *__tlsp = NULL; struct tstream_tls_params_internal *tlsp = NULL; int ret; struct stat st; if (!enabled || key_file == NULL || *key_file == 0) { __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); if (__tlsp == NULL) { return NT_STATUS_NO_MEMORY; } tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); if (tlsp == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); __tlsp->internal = tlsp; tlsp->tls_enabled = false; *_tlsp = __tlsp; return NT_STATUS_OK; } __tlsp = talloc_zero(mem_ctx, struct tstream_tls_params); if (__tlsp == NULL) { return NT_STATUS_NO_MEMORY; } tlsp = talloc_zero(__tlsp, struct tstream_tls_params_internal); if (tlsp == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } talloc_set_destructor(tlsp, tstream_tls_params_internal_destructor); __tlsp->internal = tlsp; 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(), 0600, &st)) { DEBUG(0, ("Invalid permissions on TLS private key file '%s':\n" "owner uid %u should be %u, mode 0%o should be 0%o\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), 0600)); TALLOC_FREE(__tlsp); 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_priority = talloc_strdup(tlsp, tls_priority); if (tlsp->tls_priority == NULL) { TALLOC_FREE(__tlsp); return NT_STATUS_NO_MEMORY; } tlsp->tls_enabled = true; *_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; NTSTATUS status; 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); tlss->plain_stream = plain_stream; tlss->is_server = true; tlss->current_ev = ev; status = tstream_tls_prepare_gnutls(_tlsp, tlss); if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { tevent_req_oom(req); return tevent_req_post(req, ev); } if (!NT_STATUS_IS_OK(status)) { tevent_req_error(req, EINVAL); return tevent_req_post(req, ev); } gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr_t)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); 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; } 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; NTSTATUS status; 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; } status = tstream_tls_verify_peer(tlss); if (NT_STATUS_EQUAL(status, NT_STATUS_IMAGE_CERT_REVOKED)) { tlss->error = EINVAL; tevent_req_error(req, tlss->error); return; } if (!NT_STATUS_IS_OK(status)) { tlss->error = EIO; tevent_req_error(req, tlss->error); return; } status = tstream_tls_setup_channel_bindings(tlss); if (!NT_STATUS_IS_OK(status)) { tlss->error = EIO; tevent_req_error(req, tlss->error); return; } if (tlss->push.subreq != NULL || tlss->pull.subreq != NULL) { tlss->waiting_flush.mgmt_req = req; return; } tevent_req_done(req); } 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; } struct tstream_tls_sync { struct tstream_tls *tlss; void *io_private; ssize_t (*io_send_fn)(void *io_private, const uint8_t *buf, size_t len); ssize_t (*io_recv_fn)(void *io_private, uint8_t *buf, size_t len); }; const DATA_BLOB *tstream_tls_sync_channel_bindings(struct tstream_tls_sync *tlsss) { return &tlsss->tlss->channel_bindings; } static ssize_t tstream_tls_sync_push_function(gnutls_transport_ptr_t ptr, const void *buf, size_t size) { struct tstream_tls_sync *tlsss = talloc_get_type_abort(ptr, struct tstream_tls_sync); return tlsss->io_send_fn(tlsss->io_private, buf, size); } static ssize_t tstream_tls_sync_pull_function(gnutls_transport_ptr_t ptr, void *buf, size_t size) { struct tstream_tls_sync *tlsss = talloc_get_type_abort(ptr, struct tstream_tls_sync); return tlsss->io_recv_fn(tlsss->io_private, buf, size); } ssize_t tstream_tls_sync_read(struct tstream_tls_sync *tlsss, void *buf, size_t len) { int ret; ret = gnutls_record_recv(tlsss->tlss->tls_session, buf, len); if (ret == GNUTLS_E_INTERRUPTED) { errno = EINTR; return -1; } if (ret == GNUTLS_E_AGAIN) { errno = EAGAIN; return -1; } if (ret < 0) { DBG_WARNING("TLS gnutls_record_recv(%zu) - %s\n", (size_t)len, gnutls_strerror(ret)); errno = EIO; return -1; } return ret; } ssize_t tstream_tls_sync_write(struct tstream_tls_sync *tlsss, const void *buf, size_t len) { int ret; ret = gnutls_record_send(tlsss->tlss->tls_session, buf, len); if (ret == GNUTLS_E_INTERRUPTED) { errno = EINTR; return -1; } if (ret == GNUTLS_E_AGAIN) { errno = EAGAIN; return -1; } if (ret < 0) { DBG_WARNING("TLS gnutls_record_send(%zu) - %s\n", (size_t)len, gnutls_strerror(ret)); errno = EIO; return -1; } return ret; } size_t tstream_tls_sync_pending(struct tstream_tls_sync *tlsss) { return gnutls_record_check_pending(tlsss->tlss->tls_session); } NTSTATUS tstream_tls_sync_setup(struct tstream_tls_params *_tls_params, void *io_private, ssize_t (*io_send_fn)(void *io_private, const uint8_t *buf, size_t len), ssize_t (*io_recv_fn)(void *io_private, uint8_t *buf, size_t len), TALLOC_CTX *mem_ctx, struct tstream_tls_sync **_tlsss) { struct tstream_tls_sync *tlsss = NULL; struct tstream_tls *tlss = NULL; NTSTATUS status; int ret; tlsss = talloc_zero(mem_ctx, struct tstream_tls_sync); if (tlsss == NULL) { return NT_STATUS_NO_MEMORY; } tlsss->io_private = io_private; tlsss->io_send_fn = io_send_fn; tlsss->io_recv_fn = io_recv_fn; tlss = talloc_zero(tlsss, struct tstream_tls); if (tlss == NULL) { TALLOC_FREE(tlsss); return NT_STATUS_NO_MEMORY; } talloc_set_destructor(tlss, tstream_tls_destructor); tlss->is_server = false; tlsss->tlss = tlss; status = tstream_tls_prepare_gnutls(_tls_params, tlss); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(tlsss); return status; } gnutls_transport_set_ptr(tlss->tls_session, (gnutls_transport_ptr_t)tlsss); gnutls_transport_set_pull_function(tlss->tls_session, (gnutls_pull_func)tstream_tls_sync_pull_function); gnutls_transport_set_push_function(tlss->tls_session, (gnutls_push_func)tstream_tls_sync_push_function); do { /* * The caller should have the socket blocking * and do the timeout handling in the * io_send/recv_fn */ ret = gnutls_handshake(tlss->tls_session); } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); if (gnutls_error_is_fatal(ret) != 0) { TALLOC_FREE(tlsss); return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } if (ret != GNUTLS_E_SUCCESS) { TALLOC_FREE(tlsss); return gnutls_error_to_ntstatus(ret, NT_STATUS_CRYPTO_SYSTEM_INVALID); } status = tstream_tls_verify_peer(tlss); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(tlsss); return status; } status = tstream_tls_setup_channel_bindings(tlss); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(tlsss); return status; } *_tlsss = tlsss; return NT_STATUS_OK; }