--- /dev/null
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Stefan Metzmacher 2016
+ * Copyright (C) Ralph Boehme 2016
+ *
+ * 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 "replace.h"
+#include "system/filesys.h"
+#include "system/network.h"
+#include <tevent.h>
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/blocking.h"
+#include "lib/tsocket/tsocket.h"
+#include "lib/util/debug.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/smb_direct_daemon.h"
+#include "librpc/gen_ndr/ndr_smb_direct_daemon.h"
+#include "librpc/ndr/libndr.h"
+#include "libcli/util/tstream.h"
+#include "lib/util/byteorder.h"
+#include "lib/util/samba_util.h"
+#include "smb_direct.h"
+#include "smb_direct_util.h"
+
+struct smb_direct_daemon_state {
+ /*
+ * Global state of the daemon
+ */
+
+ /* Our tevent context */
+ struct tevent_context *ev;
+
+ /* Lockfile fd */
+ int socket_lock_fd;
+
+ /*
+ * Path, fd and fde for the listening UNIX domain socket
+ */
+ char *listen_path;
+ int listen_fd;
+ struct tevent_fd *listen_fde;
+
+ struct sdd_listen_params params;
+};
+
+struct smb_direct_daemon_conn {
+ /*
+ * Connection object, one for each peer.
+ */
+
+ /* Backpointer to the global daemon state */
+ struct smb_direct_daemon_state *daemon_state;
+
+ /* Our tevent context */
+ struct tevent_context *ev;
+
+ /*
+ * We use the tstream_context for general protocol data
+ * exchange and use the fd for fd-passing with
+ * sendmsg()/recvmsg().
+ */
+ int conn_fd;
+ struct tstream_context *stream;
+
+ /* true if our peer is root */
+ bool privileged;
+
+ /*
+ * A connection in the listening state can only be used with
+ * smb_direct_accept() to accept a connection fd.
+ */
+ bool listening;
+};
+
+static struct tevent_req *smb_direct_daemon_conn_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_direct_daemon_state *daemon_state,
+ bool privileged,
+ int conn_fd,
+ struct tstream_context **stream);
+
+static int smb_direct_daemon_conn_recv(struct tevent_req *req);
+
+struct smbd_direct_daemon_write_state {
+ struct smb_direct_daemon_conn *conn;
+ DATA_BLOB out_data;
+ struct iovec iov;
+};
+
+static void smbd_direct_daemon_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_direct_daemon_write_send(
+ struct smb_direct_daemon_conn *conn,
+ DATA_BLOB out_data)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_direct_daemon_write_state *write_state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(conn, &write_state,
+ struct smbd_direct_daemon_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *write_state = (struct smbd_direct_daemon_write_state) {
+ .conn = conn,
+ .out_data = out_data,
+ };
+ talloc_steal(write_state, write_state->out_data.data);
+
+ write_state->iov = (struct iovec) {
+ .iov_base = write_state->out_data.data,
+ .iov_len = write_state->out_data.length
+ };
+
+ subreq = tstream_writev_send(write_state, conn->ev, conn->stream,
+ &write_state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, conn->ev);
+ }
+ tevent_req_set_callback(subreq, smbd_direct_daemon_write_done, req);
+
+ return req;
+}
+
+static void smbd_direct_daemon_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int result;
+ int err;
+
+ result = tstream_writev_recv(subreq, &err);
+ if (result == -1) {
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_direct_daemon_write_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct smbd_direct_daemon_read_state {
+ struct smb_direct_daemon_conn *conn;
+ DATA_BLOB in_data;
+};
+
+static void smbd_direct_daemon_read_done(struct tevent_req *subreq);
+
+static NTSTATUS smbd_direct_daemon_packet_full(void *private_data,
+ DATA_BLOB blob,
+ size_t *size)
+{
+ size_t packet_len;
+
+ if (blob.length < 4) {
+ return STATUS_MORE_ENTRIES;
+ }
+
+ packet_len = IVAL(&blob.data[0], 0);
+ if (packet_len < 5 || packet_len > 0x1000) {
+ return NT_STATUS_DATA_ERROR;
+ }
+
+ *size = packet_len;
+
+ if (*size > blob.length) {
+ return STATUS_MORE_ENTRIES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct tevent_req *smbd_direct_daemon_read_send(
+ struct smb_direct_daemon_conn *conn)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_direct_daemon_read_state *read_state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(conn, &read_state,
+ struct smbd_direct_daemon_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *read_state = (struct smbd_direct_daemon_read_state) {
+ .conn = conn,
+ };
+
+ subreq = tstream_read_pdu_blob_send(read_state,
+ conn->ev,
+ conn->stream,
+ 4, /* initial_read_size */
+ smbd_direct_daemon_packet_full,
+ read_state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, conn->ev);
+ }
+ tevent_req_set_callback(subreq, smbd_direct_daemon_read_done, req);
+
+ return req;
+}
+
+static void smbd_direct_daemon_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_direct_daemon_read_state *read_state = tevent_req_data(
+ req, struct smbd_direct_daemon_read_state);
+ NTSTATUS status;
+
+ status = tstream_read_pdu_blob_recv(subreq, read_state,
+ &read_state->in_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_ERR("tstream_read_pdu_blob_recv [%s]",
+ nt_errstr(status));
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_direct_daemon_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *in_data)
+{
+ struct smbd_direct_daemon_read_state *read_state = tevent_req_data(
+ req, struct smbd_direct_daemon_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DBG_ERR("tstream_read_pdu_blob_recv [%s]",
+ nt_errstr(status));
+ tevent_req_received(req);
+ return status;
+ }
+
+ *in_data = read_state->in_data;
+ talloc_steal(mem_ctx, in_data->data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static void smb_direct_daemon_req_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smb_direct_daemon_state *state = tevent_req_data(
+ req, struct smb_direct_daemon_state);
+
+ TALLOC_FREE(state->listen_fde);
+
+ if (state->listen_path != NULL) {
+ unlink(state->listen_path);
+ TALLOC_FREE(state->listen_path);
+ }
+
+ if (state->listen_fd != -1) {
+ close(state->listen_fd);
+ state->listen_fd = -1;
+ }
+
+ if (state->socket_lock_fd != -1) {
+ close(state->socket_lock_fd);
+ state->socket_lock_fd = -1;
+ }
+}
+
+static void smb_direct_daemon_listen_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+struct tevent_req *smb_direct_daemon_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req = NULL;
+ struct smb_direct_daemon_state *state = NULL;
+ char *lock_path = NULL;
+ char *listen_path = NULL;
+ union {
+ struct sockaddr_un su;
+ struct sockaddr sa;
+ } addr;
+ int ret;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct smb_direct_daemon_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct smb_direct_daemon_state) {
+ .ev = ev,
+ .listen_fd = -1,
+ .socket_lock_fd = -1,
+ };
+
+ tevent_req_set_cleanup_fn(req, smb_direct_daemon_req_cleanup);
+
+ lock_path = smb_direct_lock_path(state);
+ if (tevent_req_nomem(lock_path, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->socket_lock_fd = open(lock_path, O_CREAT|O_RDWR, 0700);
+ if (state->socket_lock_fd == -1) {
+ DBG_ERR("Can't open socket lockfile %s [%s]\n",
+ lock_path, strerror(errno));
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = fcntl_lock(state->socket_lock_fd, F_SETLK, 0, 0, F_WRLCK);
+ if (!ok) {
+ DBG_ERR("Can't lock socket lockfile %s [%s]\n",
+ lock_path, strerror(errno));
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ listen_path = smb_direct_socket_path(state);
+ if (tevent_req_nomem(listen_path, req)) {
+ DBG_ERR("smb_direct_socket_path failed\n");
+ return tevent_req_post(req, ev);
+ }
+
+ if (strlen(listen_path) > sizeof(addr.su.sun_path) - 1) {
+ DBG_ERR("listen_path too long [%s]\n", listen_path);
+ tevent_req_error(req, ENAMETOOLONG);
+ return tevent_req_post(req, ev);
+ }
+
+ unlink(listen_path);
+
+ ZERO_STRUCT(addr);
+ addr.su.sun_family = AF_UNIX;
+ strncpy(addr.su.sun_path, listen_path,
+ sizeof(addr.su.sun_path) - 1);
+
+ state->listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (state->listen_fd == -1) {
+ DBG_ERR("socket failed [%s]\n", strerror(errno));
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ set_close_on_exec(state->listen_fd);
+
+ ret = bind(state->listen_fd, &addr.sa, sizeof(addr.su));
+ if (ret != 0) {
+ DBG_ERR("bind failed [%s]\n", strerror(errno));
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Once the bind has created the socket file set the path in
+ * the state for the cleanup function.
+ */
+ state->listen_path = listen_path;
+
+ ret = listen(state->listen_fd, SMB_DIRECT_LISTEN_BACKLOG);
+ if (ret != 0) {
+ DBG_ERR("listen failed [%s]\n", strerror(errno));
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ set_blocking(state->listen_fd, false);
+
+ state->listen_fde = tevent_add_fd(ev, state,
+ state->listen_fd,
+ TEVENT_FD_READ,
+ smb_direct_daemon_listen_handler,
+ req);
+ if (tevent_req_nomem(state->listen_fde, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_NOTICE("smb_direct_daemon started\n");
+
+ return req;
+}
+
+static void smb_direct_daemon_conn_disconnect(struct tevent_req *subreq);
+
+static void smb_direct_daemon_listen_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct smb_direct_daemon_state *daemon_state = tevent_req_data(
+ req, struct smb_direct_daemon_state);
+ int conn_fd;
+ int ret;
+ uid_t peer_uid = -1;
+ gid_t peer_gid = -1;
+ bool privileged = false;
+ struct tstream_context *stream = NULL;
+ struct tevent_req *subreq = NULL;
+
+ conn_fd = accept(daemon_state->listen_fd, NULL, NULL);
+ if (conn_fd == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ DBG_WARNING("accept failed [%s]\n", strerror(errno));
+ }
+ return;
+ }
+
+ set_close_on_exec(conn_fd);
+
+ ret = getpeereid(conn_fd, &peer_uid, &peer_gid);
+ if (ret != 0) {
+ DBG_WARNING("getpeereid failed [%s]\n", strerror(errno));
+ close(conn_fd);
+ return;
+ }
+
+ if (peer_uid == geteuid()) {
+ privileged = true;
+ }
+
+ set_blocking(conn_fd, false);
+
+ ret = tstream_bsd_existing_socket(daemon_state, conn_fd, &stream);
+ if (ret != 0) {
+ DBG_WARNING("tstream_bsd_existing_socket failed [%s]\n",
+ strerror(errno));
+ close(conn_fd);
+ return;
+ }
+
+ subreq = smb_direct_daemon_conn_send(daemon_state,
+ daemon_state->ev,
+ daemon_state,
+ privileged,
+ conn_fd, &stream);
+ if (subreq == NULL) {
+ DBG_WARNING("smb_direct_daemon_conn_send failed [%s]\n",
+ strerror(errno));
+ TALLOC_FREE(stream);
+ return;
+ }
+ tevent_req_set_callback(subreq, smb_direct_daemon_conn_disconnect, req);
+}
+
+static void smb_direct_daemon_conn_disconnect(struct tevent_req *subreq)
+{
+ int ret;
+
+ ret = smb_direct_daemon_conn_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_DEBUG("smb_direct_daemon_conn_send failed\n");
+ }
+}
+
+int smb_direct_daemon_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static NTSTATUS smbd_direct_daemon_ping(
+ struct sdd_packet_ping_request *ping_request,
+ struct sdd_packet_ping_response *ping_response)
+{
+ ping_response->data = ping_request->data;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_direct_daemon_listen(
+ struct smb_direct_daemon_conn *conn,
+ struct sdd_packet_listen_request *listen_request,
+ struct sdd_packet_listen_response *listen_response)
+{
+ conn->daemon_state->params = listen_request->params;
+ conn->listening = true;
+ listen_response->status = 0;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_direct_daemon_dispatch(struct smb_direct_daemon_conn *conn,
+ DATA_BLOB in_data,
+ DATA_BLOB *out_data)
+{
+ struct sdd_packetB *request_packet = NULL;
+ struct sdd_packetB *response_packet = NULL;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ if (conn->listening) {
+ return NT_STATUS_INVALID_MESSAGE;
+ }
+
+ request_packet = talloc_zero(conn, struct sdd_packetB);
+ if (request_packet == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ response_packet = talloc_zero(conn, struct sdd_packetB);
+ if (response_packet == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &in_data, request_packet, request_packet,
+ (ndr_pull_flags_fn_t)ndr_pull_sdd_packetB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_DATA_ERROR;
+ }
+
+ switch (request_packet->command) {
+
+ case SDD_PING_REQUEST: {
+ struct sdd_packet_ping_response ping_response;
+
+ ZERO_STRUCT(ping_response);
+
+ status = smbd_direct_daemon_ping(
+ &request_packet->packet.ping_request,
+ &ping_response);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ response_packet->command = SDD_PING_RESPONSE;
+ response_packet->packet.ping_response = ping_response;
+ break;
+ }
+
+ case SDD_LISTEN_REQUEST: {
+ struct sdd_packet_listen_response listen_response;
+
+ ZERO_STRUCT(listen_response);
+
+ status = smbd_direct_daemon_listen(
+ conn,
+ &request_packet->packet.listen_request,
+ &listen_response);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ response_packet->command = SDD_LISTEN_RESPONSE;
+ response_packet->packet.listen_response = listen_response;
+ break;
+ }
+
+ default:
+ status = NT_STATUS_DATA_ERROR;
+ break;
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ out_data, conn, response_packet,
+ (ndr_push_flags_fn_t)ndr_push_sdd_packetB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = NT_STATUS_DATA_ERROR;
+ goto fail;
+ }
+
+ SIVAL(&out_data->data[0], 0, out_data->length);
+
+fail:
+ TALLOC_FREE(request_packet);
+ TALLOC_FREE(response_packet);
+ return status;
+}
+
+static void smbd_direct_daemon_conn_handler(struct tevent_req *read_req);
+static void smbd_direct_daemon_conn_handler_next(struct tevent_req *write_req);
+
+/**
+ * @brief Handle an already accepted connection
+ *
+ * Connection handler, called just after the connection was accepted.
+ **/
+static struct tevent_req *smb_direct_daemon_conn_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_direct_daemon_state *daemon_state,
+ bool privileged,
+ int conn_fd,
+ struct tstream_context **stream)
+{
+ struct tevent_req *req = NULL;
+ struct smb_direct_daemon_conn *conn = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &conn, struct smb_direct_daemon_conn);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *conn = (struct smb_direct_daemon_conn) {
+ .ev = ev,
+ .daemon_state = daemon_state,
+ .conn_fd = conn_fd,
+ .stream = talloc_move(conn, stream),
+ .privileged = privileged,
+ };
+
+ subreq = smbd_direct_daemon_read_send(conn);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_direct_daemon_conn_handler, req);
+
+ return req;
+}
+
+static void smbd_direct_daemon_conn_handler(struct tevent_req *read_req)
+{
+ struct tevent_req *conn_req = tevent_req_callback_data(
+ read_req, struct tevent_req);
+ struct smb_direct_daemon_conn *conn = tevent_req_data(
+ conn_req, struct smb_direct_daemon_conn);
+ struct tevent_req *write_req = NULL;
+ NTSTATUS status;
+ DATA_BLOB in_data;
+ DATA_BLOB out_data;
+
+ status = smbd_direct_daemon_read_recv(read_req, conn, &in_data);
+ TALLOC_FREE(read_req);
+ if (tevent_req_nterror(conn_req, status)) {
+ DBG_ERR("smbd_direct_daemon_read_recv [%s]",
+ nt_errstr(status));
+ return;
+ }
+
+ status = smbd_direct_daemon_dispatch(conn, in_data, &out_data);
+ if (tevent_req_nterror(conn_req, status)) {
+ DBG_ERR("smbd_direct_daemon_read_recv [%s]",
+ nt_errstr(status));
+ return;
+ }
+
+ write_req = smbd_direct_daemon_write_send(conn, out_data);
+ if (tevent_req_nomem(write_req, conn_req)) {
+ return;
+ }
+ tevent_req_set_callback(write_req,
+ smbd_direct_daemon_conn_handler_next,
+ conn_req);
+}
+
+static void smbd_direct_daemon_conn_handler_next(struct tevent_req *write_req)
+{
+ NTSTATUS status;
+ struct tevent_req *conn_req = tevent_req_callback_data(
+ write_req, struct tevent_req);
+ struct smb_direct_daemon_conn *conn = tevent_req_data(
+ conn_req, struct smb_direct_daemon_conn);
+ struct tevent_req *read_req = NULL;
+
+ status = smbd_direct_daemon_write_recv(write_req);
+ TALLOC_FREE(write_req);
+ if (tevent_req_nterror(conn_req, status)) {
+ DBG_ERR("smbd_direct_daemon_write_recv [%s]",
+ nt_errstr(status));
+ return;
+ }
+
+ read_req = smbd_direct_daemon_read_send(conn);
+ if (tevent_req_nomem(read_req, conn_req)) {
+ return;
+ }
+ tevent_req_set_callback(read_req,
+ smbd_direct_daemon_conn_handler,
+ conn_req);
+}
+
+static int smb_direct_daemon_conn_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}