--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/network.h"
+#include "../util/tevent_unix.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/tsocket/tsocket_internal.h"
+#include "lib/tls/tls.h"
+#include "gnutls/gnutls.h"
+
+#define DH_BITS 1024
+
+static const struct tstream_context_ops tstream_tls_ops;
+
+struct tstream_tls {
+ struct tstream_context *plain_stream;
+ int plain_errno;
+
+ gnutls_session tls_session;
+ int tls_error;
+
+ gnutls_certificate_credentials xcred;
+
+ struct tevent_context *current_ev;
+
+ struct {
+ uint8_t buffer[1024];
+ struct iovec iov;
+ } push, pull;
+};
+
+static void tstream_tls_schedule_retry(struct tstream_context *tls,
+ struct tevent_context *ev)
+{
+ /* TODO */
+}
+static void tstream_tls_retry(struct tstream_context *tls)
+{
+ /* TODO */
+}
+
+static void tstream_tls_push_done(struct tevent_req *subreq);
+
+static ssize_t tstream_tls_push_function(gnutls_transport_ptr ptr,
+ const void *buf, size_t size)
+{
+ struct tstream_context *tls = talloc_get_type_abort(ptr,
+ struct tstream_context);
+ struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls);
+ struct tevent_req *subreq;
+
+ if (tlss->push.iov.iov_base) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ tlss->push.iov.iov_base = tlss->push.buffer;
+ tlss->push.iov.iov_len = MIN(size, sizeof(tlss->push.buffer));
+
+ memcpy(tlss->push.buffer, buf, tlss->push.iov.iov_len);
+
+ 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, tls);
+
+ return tlss->push.iov.iov_len;
+}
+
+static void tstream_tls_push_done(struct tevent_req *subreq)
+{
+ struct tstream_context *tls = tevent_req_callback_data(subreq,
+ struct tstream_context);
+ struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls);
+ int ret;
+ int perrno;
+
+ ZERO_STRUCT(tlss->push.iov);
+
+ ret = tstream_writev_recv(subreq, &perrno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tlss->plain_errno = perrno;
+ tstream_tls_retry(tls);
+ return;
+ }
+
+ tstream_tls_retry(tls);
+}
+
+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 *tls = talloc_get_type_abort(ptr,
+ struct tstream_context);
+ struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls);
+ struct tevent_req *subreq;
+
+ if (tlss->pull.iov.iov_base) {
+ size_t n;
+
+ n = MIN(tlss->pull.iov.iov_len, size);
+ memcpy(buf, tlss->pull.iov.iov_base, n);
+
+ tlss->pull.iov.iov_len -= n;
+ if (tlss->pull.iov.iov_len == 0) {
+ tlss->pull.iov.iov_base = NULL;
+ }
+
+ return n;
+ }
+
+ if (size == 0) {
+ return 0;
+ }
+
+ tlss->pull.iov.iov_base = tlss->pull.buffer;
+ tlss->pull.iov.iov_len = MIN(size, sizeof(tlss->pull.buffer));
+
+ 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, tls);
+
+ errno = EAGAIN;
+ return -1;
+}
+
+static void tstream_tls_pull_done(struct tevent_req *subreq)
+{
+ struct tstream_context *tls = tevent_req_callback_data(subreq,
+ struct tstream_context);
+ struct tstream_tls *tlss = tstream_context_data(tls, struct tstream_tls);
+ int ret;
+ int perrno;
+
+ ret = tstream_readv_recv(subreq, &perrno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tlss->plain_errno = perrno;
+ tstream_tls_retry(tls);
+ return;
+ }
+
+ tstream_tls_retry(tls);
+}
+
+static int tstream_tls_destructor(struct tstream_tls *tlss)
+{
+ if (tlss->xcred) {
+ gnutls_certificate_free_credentials(tlss->xcred);
+ tlss->xcred = NULL;
+ }
+ 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);
+ ssize_t ret;
+
+ if (!tlss->plain_stream) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ if (!tlss->tls_session) {
+ ret = tstream_pending_bytes(tlss->plain_stream);
+ return ret;
+ }
+
+ ret = gnutls_record_check_pending(tlss->tls_session);
+ if (ret < 0) {
+ /* TODO: better mapping */
+ errno = EIO;
+ return -1;
+ }
+
+ return ret;
+}
+
+struct tstream_tls_readv_state {
+ int ret;
+};
+
+static void tstream_tls_readv_plain_handler(struct tevent_req *subreq);
+
+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 tevent_req *req;
+ struct tstream_tls_readv_state *state;
+ struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls);
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_tls_readv_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ret = 0;
+
+ if (!tlss->plain_stream) {
+ tevent_req_error(req, ENOTCONN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!tlss->tls_session) {
+ subreq = tstream_readv_send(state,
+ ev,
+ tlss->plain_stream,
+ vector,
+ count);
+ if (tevent_req_nomem(subreq,req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ tstream_tls_readv_plain_handler,
+ req);
+
+ return req;
+ }
+
+ /* TODO */
+ tevent_req_error(req, ENOSYS);
+ return tevent_req_post(req, ev);
+}
+
+static void tstream_tls_readv_plain_handler(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct tstream_tls_readv_state *state = tevent_req_data(req,
+ struct tstream_tls_readv_state);
+ int ret;
+ int sys_errno;
+
+ ret = tstream_readv_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, sys_errno);
+ return;
+ }
+
+ state->ret = ret;
+
+ tevent_req_done(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);
+ int ret;
+
+ ret = tsocket_simple_int_recv(req, perrno);
+ if (ret == 0) {
+ ret = state->ret;
+ }
+
+ tevent_req_received(req);
+ return ret;
+}
+
+struct tstream_tls_writev_state {
+ int ret;
+};
+
+static void tstream_tls_writev_plain_handler(struct tevent_req *subreq);
+
+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 tevent_req *req;
+ struct tstream_tls_writev_state *state;
+ struct tstream_tls *tlss = tstream_context_data(stream, struct tstream_tls);
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_tls_writev_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ret = 0;
+
+ if (!tlss->plain_stream) {
+ tevent_req_error(req, ENOTCONN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!tlss->tls_session) {
+ subreq = tstream_writev_send(state,
+ ev,
+ tlss->plain_stream,
+ vector,
+ count);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tstream_tls_writev_plain_handler, req);
+
+ return req;
+ }
+
+ /* TODO */
+ tevent_req_error(req, ENOSYS);
+ return tevent_req_post(req, ev);
+}
+
+static void tstream_tls_writev_plain_handler(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct tstream_tls_writev_state *state = tevent_req_data(req,
+ struct tstream_tls_writev_state);
+ int ret;
+ int sys_errno;
+
+ ret = tstream_writev_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, sys_errno);
+ return;
+ }
+
+ state->ret = ret;
+
+ tevent_req_done(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);
+ int ret;
+
+ 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;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_tls_disconnect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!tlss->plain_stream) {
+ tevent_req_error(req, ENOTCONN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!tlss->tls_session) {
+ /*
+ * The caller is responsible to do the real disconnect
+ * on the plain stream!
+ */
+ tlss->plain_stream = NULL;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /* TODO */
+ tevent_req_error(req, ENOSYS);
+ return tevent_req_post(req, ev);
+}
+
+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 {
+ const char *ca_path;
+ gnutls_certificate_credentials xcred;
+};
+
+struct tstream_tls_connect_state {
+ struct {
+ struct tevent_context *ev;
+ } caller;
+ 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;
+ int ret;
+ static const int cert_type_priority[] = {
+ GNUTLS_CRT_X509,
+ GNUTLS_CRT_OPENPGP,
+ 0
+ };
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_tls_connect_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->caller.ev = ev;
+
+ 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;
+
+ gnutls_global_init();
+
+ ret = gnutls_certificate_allocate_credentials(&tlss->xcred);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ gnutls_certificate_set_x509_trust_file(tlss->xcred,
+ tls_params->ca_path,
+ GNUTLS_X509_FMT_PEM);
+
+ ret = gnutls_init(&tlss->tls_session, GNUTLS_CLIENT);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ ret = gnutls_set_default_priority(tlss->tls_session);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ 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, tlss->xcred);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ 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);
+ gnutls_transport_set_lowat(tlss->tls_session, 0);
+
+ //tstream_tls_prepare_operation(tls, state->caller.ev);
+ ret = gnutls_handshake(tlss->tls_session);
+ if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) {
+ tstream_tls_schedule_retry(state->tls_stream, state->caller.ev);
+ return req;
+ }
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+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;
+}
+
+struct tstream_tls_accept_state {
+ struct {
+ struct tevent_context *ev;
+ } caller;
+ 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 *tls_params,
+ const char *location)
+{
+ struct tevent_req *req;
+ struct tstream_tls_accept_state *state;
+ struct tstream_tls *tlss;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_tls_accept_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->caller.ev = ev;
+
+ 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;
+
+ gnutls_global_init();
+
+ ret = gnutls_init(&tlss->tls_session, GNUTLS_SERVER);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ ret = gnutls_set_default_priority(tlss->tls_session);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ ret = gnutls_credentials_set(tlss->tls_session, GNUTLS_CRD_CERTIFICATE,
+ tls_params->xcred);
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ 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);
+ gnutls_transport_set_lowat(tlss->tls_session, 0);
+
+ //tstream_tls_prepare_operation(tls, state->caller.ev);
+ ret = gnutls_handshake(tlss->tls_session);
+ if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) {
+ tstream_tls_schedule_retry(state->tls_stream, state->caller.ev);
+ return req;
+ }
+ if (tevent_req_error(req, ret)) {
+ DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+ return tevent_req_post(req, ev);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+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;
+}
+