File 0001-Add-swnd-daemon.patch of Package cifs-utils
From 7bbc2e1a941a6f5515510a1236dca6478d985320 Mon Sep 17 00:00:00 2001
From: Samuel Cabrero <scabrero@samba.org>
Date: Thu, 2 Jul 2020 17:52:32 +0200
Subject: [PATCH] Add swnd daemon
The swnd daemon talks to the cifs kernel module to handle witness
registrations and notifications.
---
Makefile.am | 10 +
configure.ac | 12 +
swnd.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
swnd.h | 23 ++
swnd.rst | 21 +
5 files changed, 1109 insertions(+)
create mode 100644 swnd.c
create mode 100644 swnd.h
create mode 100644 swnd.rst
diff --git a/Makefile.am b/Makefile.am
index 7877823..131954d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -118,6 +118,16 @@ man_MANS += $(rst_man_pages)
CLEANFILES += $(rst_man_pages)
endif
+if CONFIG_SWND
+bin_PROGRAMS += swnd
+swnd_SOURCES = swnd.c
+swnd_CFLAGS = @LIBNL3_CFLAGS@ @LIBNL_GENL3_CFLAGS@ @LIBTALLOC_CFLAGS@ \
+ @LIBTEVENT_CFLAGS@ @LIBSWNCLIENT_CFLAGS@
+swnd_LDADD = @LIBNL3_LIBS@ @LIBNL_GENL3_LIBS@ @LIBTALLOC_LIBS@ \
+ @LIBTEVENT_LIBS@ @LIBSWNCLIENT_LIBS@ -lsystemd -lndr-krb5pac
+rst_man_pages += swnd.1
+endif
+
SUBDIRS = contrib
install-exec-hook: install-sbinPROGRAMS
diff --git a/configure.ac b/configure.ac
index e871b9f..3d8167c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -65,6 +65,11 @@ AC_ARG_ENABLE(man,
enable_man=$enableval,
enable_man="maybe")
+AC_ARG_ENABLE(swnd,
+ [AS_HELP_STRING([--enable-swnd],[Create swnd binary @<:@default=yes@:>@])],
+ enable_swnd=$enableval,
+ enable_swnd="maybe")
+
# "with" options
AC_ARG_WITH(idmap-plugin,
[AS_HELP_STRING([--with-idmap-plugin=/path/to/plugin],[Define the path to the plugin that the idmapping infrastructure should use @<:@default=/etc/cifs-utils/idmap-plugin@:>@])],
@@ -241,6 +246,12 @@ if test $enable_pam != "no"; then
])
fi
+PKG_CHECK_MODULES(LIBNL3, libnl-3.0 >= 3.1)
+PKG_CHECK_MODULES(LIBNL_GENL3, libnl-genl-3.0 >= 3.1)
+PKG_CHECK_MODULES(LIBTALLOC, talloc >= 2.4.3)
+PKG_CHECK_MODULES(LIBTEVENT, tevent >= 0.16.2)
+PKG_CHECK_MODULES(LIBSWNCLIENT, swnclient >= 0.1.0)
+
# ugly, but I'm not sure how to check for functions in a library that's not in $LIBS
cu_saved_libs=$LIBS
LIBS="$LIBS $KRB5_LDADD"
@@ -294,6 +305,7 @@ AM_CONDITIONAL(CONFIG_SMBINFO, [test "$enable_smbinfo" != "no"])
AM_CONDITIONAL(CONFIG_PYTHON_TOOLS, [test "$enable_pythontools" != "no"])
AM_CONDITIONAL(CONFIG_PAM, [test "$enable_pam" != "no"])
AM_CONDITIONAL(CONFIG_PLUGIN, [test "$enable_cifsidmap" != "no" -o "$enable_cifsacl" != "no"])
+AM_CONDITIONAL(CONFIG_SWND, [test "$enable_swnd" != "no"])
LIBCAP_NG_PATH
diff --git a/swnd.c b/swnd.c
new file mode 100644
index 0000000..ecef32d
--- /dev/null
+++ b/swnd.c
@@ -0,0 +1,1043 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <talloc.h>
+
+#define TEVENT_DEPRECATED 1
+#include <tevent.h>
+
+#include <netdb.h>
+
+#include <linux/cifs/cifs_netlink.h>
+
+#include <systemd/sd-journal.h>
+#include <systemd/sd-daemon.h>
+
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#include <netlink/genl/genl.h>
+#include <netlink/handlers.h>
+#include <netlink/genl/mngt.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+
+#include <witness/swnclient.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+static struct nla_policy cifs_genl_policy[CIFS_GENL_ATTR_MAX + 1] = {
+ [CIFS_GENL_ATTR_SWN_REGISTRATION_ID] = { .type = NLA_U32 },
+ [CIFS_GENL_ATTR_SWN_NET_NAME] = { .type = NLA_STRING },
+ [CIFS_GENL_ATTR_SWN_SHARE_NAME] = { .type = NLA_STRING },
+ [CIFS_GENL_ATTR_SWN_IP] = { .minlen = sizeof(struct sockaddr_storage) },
+ [CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY] = { .type = NLA_FLAG },
+ [CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY] = { .type = NLA_FLAG },
+ [CIFS_GENL_ATTR_SWN_IP_NOTIFY] = { .type = NLA_FLAG },
+ [CIFS_GENL_ATTR_SWN_USER_NAME] = { .type = NLA_STRING },
+ [CIFS_GENL_ATTR_SWN_PASSWORD] = { .type = NLA_STRING },
+ [CIFS_GENL_ATTR_SWN_DOMAIN_NAME] = { .type = NLA_STRING },
+ [CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE] = { .type = NLA_U32 },
+ [CIFS_GENL_ATTR_SWN_RESOURCE_STATE] = { .type = NLA_U32 },
+ [CIFS_GENL_ATTR_SWN_RESOURCE_NAME] = { .type = NLA_STRING },
+};
+
+static int swn_register_handler(struct nl_cache_ops *cache_ops,
+ struct genl_cmd *cmd,
+ struct genl_info *info,
+ void *arg);
+
+static int swn_unregister_handler(struct nl_cache_ops *cache_ops,
+ struct genl_cmd *cmd,
+ struct genl_info *info,
+ void *arg);
+
+static struct genl_cmd swnd_genl_family_cmds[] = {
+ {
+ .c_id = CIFS_GENL_CMD_SWN_REGISTER,
+ .c_name = "swn_register",
+ .c_attr_policy = cifs_genl_policy,
+ .c_msg_parser = &swn_register_handler,
+ .c_maxattr = CIFS_GENL_ATTR_MAX,
+ },
+ {
+ .c_id = CIFS_GENL_CMD_SWN_UNREGISTER,
+ .c_name = "swn_unregister",
+ .c_attr_policy = cifs_genl_policy,
+ .c_msg_parser = &swn_unregister_handler,
+ .c_maxattr = CIFS_GENL_ATTR_MAX,
+ },
+};
+
+static struct genl_ops swnd_genl_family_ops = {
+ .o_name = CIFS_GENL_NAME,
+ .o_cmds = swnd_genl_family_cmds,
+ .o_ncmds = ARRAY_SIZE(swnd_genl_family_cmds),
+};
+
+struct swnd_context {
+ struct tevent_context *ev_ctx;
+ struct nl_sock *nlsk;
+ int cifs_id;
+ struct tevent_fd *fde;
+ struct swnc_state *swn;
+};
+
+/**
+ * @brief Extracts the attributes from a netlink message
+ *
+ * @param[in] mem_ctx The memory context to allocate the resulting swnc_registration_info
+ * @param[in] info The information received from the kernel
+ * @param[out] rinfo The resulting registration info allocated under mem_ctx context
+ * @param[out] registration_id This parameter is optional. If it is not NULL then the
+ * registration id will be retrieved from the kernel message,
+ * resulting in an error if it is not present.
+ * @return Zero if success, error code otherwise
+ */
+static int get_swnc_registration_info_from_genl_info(TALLOC_CTX *mem_ctx,
+ struct genl_info *info,
+ struct swnc_registration_info **out,
+ int *registration_id)
+{
+ struct swnc_registration_info *tmp = NULL;
+ int ret;
+
+ if (registration_id != NULL) {
+ if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID] != NULL) {
+ *registration_id =
+ nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]);
+ } else {
+ sd_journal_print(LOG_NOTICE, "Missing registration ID attribute");
+ return EINVAL;
+ }
+ }
+
+ tmp = talloc_zero(mem_ctx, struct swnc_registration_info);
+ if (tmp == NULL) {
+ return ENOMEM;
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_NET_NAME] != NULL) {
+ tmp->net_name = talloc_strdup(tmp,
+ nla_get_string(info->attrs[CIFS_GENL_ATTR_SWN_NET_NAME]));
+ if (tmp->net_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ } else {
+ sd_journal_print(LOG_NOTICE, "Missing network name attribute");
+ ret = EINVAL;
+ goto fail;
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_IP] != NULL) {
+ char ip_address[INET6_ADDRSTRLEN];
+ struct sockaddr_storage *addr = nla_data(info->attrs[CIFS_GENL_ATTR_SWN_IP]);
+ ret = getnameinfo((struct sockaddr *)addr,
+ sizeof(struct sockaddr_storage),
+ ip_address,
+ sizeof(ip_address),
+ NULL,
+ 0,
+ NI_NUMERICHOST);
+ if (ret != 0) {
+ sd_journal_print(LOG_NOTICE, "Failed to parse ip address: %s",
+ gai_strerror(ret));
+ goto fail;
+ }
+
+ tmp->ip_address = talloc_strdup(tmp, ip_address);
+ if (tmp->ip_address == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_SHARE_NAME] != NULL) {
+ tmp->share_name = talloc_strdup(tmp,
+ nla_get_string(info->attrs[CIFS_GENL_ATTR_SWN_SHARE_NAME]));
+ if (tmp->share_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY] != NULL) {
+ tmp->net_name_req = nla_get_flag(info->attrs[CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY]);
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_IP_NOTIFY] != NULL) {
+ tmp->ip_address_req = nla_get_flag(info->attrs[CIFS_GENL_ATTR_SWN_IP_NOTIFY]);
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY] != NULL) {
+ tmp->share_name_req =
+ nla_get_flag(info->attrs[CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY]);
+ }
+
+ *out = tmp;
+ return 0;
+
+fail:
+ TALLOC_FREE(tmp);
+ return ret;
+}
+
+static int get_swnc_credentials_from_genl_info(TALLOC_CTX *mem_ctx,
+ struct genl_info *info,
+ struct swnc_credentials **out)
+{
+ struct swnc_credentials *creds = NULL;
+ int ret;
+
+ creds = talloc_zero(mem_ctx, struct swnc_credentials);
+ if (creds == NULL) {
+ return ENOMEM;
+ }
+
+ creds->use_kerberos = info->attrs[CIFS_GENL_ATTR_SWN_KRB_AUTH] != NULL;
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_USER_NAME] != NULL) {
+ creds->username = talloc_strdup(
+ creds, nla_get_string(info->attrs[CIFS_GENL_ATTR_SWN_USER_NAME]));
+ if (creds->username == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_PASSWORD] != NULL) {
+ creds->password = talloc_strdup(
+ creds, nla_get_string(info->attrs[CIFS_GENL_ATTR_SWN_PASSWORD]));
+ if (creds->password == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_DOMAIN_NAME] != NULL) {
+ creds->domain = talloc_strdup(creds, nla_get_string(info->attrs[CIFS_GENL_ATTR_SWN_DOMAIN_NAME]));
+ if (creds->domain == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if ((creds->username == NULL || creds->password == NULL) && !creds->use_kerberos) {
+ sd_journal_print(LOG_WARNING,
+ "Username and password attributes are required to register");
+ ret = EINVAL;
+ goto fail;
+ }
+
+ *out = creds;
+
+ return 0;
+fail:
+ TALLOC_FREE(creds);
+ return ret;
+}
+
+/**
+ * @brief Sends a notification to cifs kernel module
+ *
+ * @param nlsk The netlink socket
+ * @param cifs_genl_family_id The netlink family id
+ * @param n The notification to send
+ */
+static int swnc_notification_to_cifs(struct nl_sock *nlsk,
+ int cifs_genl_family_id,
+ uint32_t registration_id,
+ struct swnc_notification *n)
+{
+ struct nl_msg *msg = NULL;
+ void *hdr = NULL;
+ int ret;
+
+ sd_journal_print(LOG_DEBUG,
+ "Sending notification of type 0x%x",
+ n->type);
+
+ msg = nlmsg_alloc();
+ if (msg == NULL) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to allocate netlink message: %d (%s)",
+ ret, strerror(ret));
+ return -ENOMEM;
+ }
+
+ genlmsg_put(msg,
+ NL_AUTO_PORT,
+ NL_AUTO_SEQ,
+ cifs_genl_family_id,
+ 0,
+ 0,
+ CIFS_GENL_CMD_SWN_NOTIFY,
+ 0);
+
+ /*
+ * This id lets the kernel find the matching registration and its
+ * internal state
+ */
+ NLA_PUT_U32(msg, CIFS_GENL_ATTR_SWN_REGISTRATION_ID, registration_id);
+
+ switch (n->type) {
+ case SWNC_NOTIFICATION_RESOURCE_CHANGE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE,
+ CIFS_SWN_NOTIFICATION_RESOURCE_CHANGE);
+ NLA_PUT_STRING(msg,
+ CIFS_GENL_ATTR_SWN_RESOURCE_NAME,
+ n->resource_change.name);
+ switch (n->resource_change.state) {
+ case SWNC_RESOURCE_STATE_UNKNOWN:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_RESOURCE_STATE,
+ CIFS_SWN_RESOURCE_STATE_UNKNOWN);
+ break;
+ case SWNC_RESOURCE_STATE_AVAILABLE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_RESOURCE_STATE,
+ CIFS_SWN_RESOURCE_STATE_AVAILABLE);
+ break;
+ case SWNC_RESOURCE_STATE_UNAVAILABLE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_RESOURCE_STATE,
+ CIFS_SWN_RESOURCE_STATE_UNAVAILABLE);
+ break;
+ }
+ break;
+ case SWNC_NOTIFICATION_CLIENT_MOVE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE,
+ CIFS_SWN_NOTIFICATION_CLIENT_MOVE);
+ NLA_PUT(msg,
+ CIFS_GENL_ATTR_SWN_IP,
+ sizeof(struct sockaddr_storage),
+ n->client_move.addr);
+ break;
+ case SWNC_NOTIFICATION_SHARE_MOVE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE,
+ CIFS_SWN_NOTIFICATION_SHARE_MOVE);
+ NLA_PUT(msg,
+ CIFS_GENL_ATTR_SWN_IP,
+ sizeof(struct sockaddr_storage),
+ n->share_move.addr);
+ break;
+ case SWNC_NOTIFICATION_IP_CHANGE:
+ NLA_PUT_U32(msg,
+ CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE,
+ CIFS_SWN_NOTIFICATION_IP_CHANGE);
+ NLA_PUT(msg,
+ CIFS_GENL_ATTR_SWN_IP,
+ sizeof(struct sockaddr_storage),
+ n->ip_change.addr);
+ break;
+ default:
+ sd_journal_print(LOG_INFO,
+ "Unknown notification type '%d'",
+ n->type);
+ goto fail;
+ }
+
+ ret = nl_send_auto_complete(nlsk, msg);
+ if (ret < 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to send netlink message: %d (%s)",
+ ret, strerror(ret));
+ goto fail;
+ }
+
+ ret = 0;
+
+nla_put_failure:
+fail:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/*
+ * After register, the wait_notification_loop_send starts the wait notification loop.
+ * This loop never ends, when a notification is received a new request is created to
+ * wait for the next one. The loop continues until unregister.
+ */
+struct wait_notif_state {
+ struct swnd_context *swnd_ctx;
+ uint32_t registration_id;
+ struct swnc_registration_info info;
+};
+
+static void wait_notification_done(struct tevent_req *req);
+
+static struct tevent_req *wait_notification_loop_send(
+ struct swnd_context *swnd_ctx,
+ uint32_t registration_id,
+ struct swnc_registration_info *info)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct wait_notif_state *state = NULL;
+
+ req = tevent_req_create(swnd_ctx, &state, struct wait_notif_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->swnd_ctx = swnd_ctx;
+ state->registration_id = registration_id;
+ state->info.net_name = talloc_strdup(state, info->net_name);
+ if (tevent_req_nomem(state->info.net_name, req)) {
+ return tevent_req_post(req, swnd_ctx->ev_ctx);
+ }
+
+ state->info.ip_address = talloc_strdup(state, info->ip_address);
+ if (tevent_req_nomem(state->info.ip_address, req)) {
+ return tevent_req_post(req, swnd_ctx->ev_ctx);
+ }
+
+ state->info.share_name = talloc_strdup(state, info->share_name);
+ if (tevent_req_nomem(state->info.share_name, req)) {
+ return tevent_req_post(req, swnd_ctx->ev_ctx);
+ }
+
+ state->info.net_name_req = info->net_name_req;
+ state->info.ip_address_req = info->ip_address_req;
+ state->info.share_name_req = info->share_name_req;
+
+ /* Wait for notification */
+ subreq = swnc_wait_notification_send(state,
+ state->swnd_ctx->ev_ctx,
+ state->swnd_ctx->swn,
+ &state->info);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, state->swnd_ctx->ev_ctx);
+ }
+ tevent_req_set_callback(subreq, wait_notification_done, req);
+
+ return req;
+}
+
+static void wait_notification_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wait_notif_state *state = tevent_req_data(
+ req, struct wait_notif_state);
+ struct swnc_notification *notifications = NULL;
+ ssize_t i;
+ int ret;
+
+ ret = swnc_wait_notification_recv(subreq,
+ state,
+ ¬ifications);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ if (ret != ECANCELED) {
+ /* Avoid logging errors when the request is canceled */
+ sd_journal_print(LOG_WARNING,
+ "AsyncNotify request failed: %d (%s)",
+ ret, strerror(ret));
+ }
+ return;
+ }
+
+ /* Process the notifications */
+ for (i = 0; i < talloc_array_length(notifications); i++) {
+ struct swnc_notification *n = ¬ifications[i];
+ ret = swnc_notification_to_cifs(state->swnd_ctx->nlsk,
+ state->swnd_ctx->cifs_id,
+ state->registration_id,
+ n);
+ if (ret != 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to send notification to "
+ "cifs: %d (%s)", ret, strerror(ret));
+ continue;
+ }
+ }
+
+ TALLOC_FREE(notifications);
+
+ /* Wait the next notification */
+ subreq = swnc_wait_notification_send(state,
+ state->swnd_ctx->ev_ctx,
+ state->swnd_ctx->swn,
+ &state->info);
+ if (tevent_req_nomem(subreq, req)) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to send notification wait request\n");
+ return;
+ }
+ tevent_req_set_callback(subreq, wait_notification_done, req);
+}
+
+static int wait_notification_loop_recv(struct tevent_req *req)
+{
+ enum tevent_req_state state;
+ uint64_t err;
+ int rc;
+
+ if (!tevent_req_is_error(req, &state, &err)) {
+ tevent_req_received(req);
+ return 0;
+ }
+
+ switch (state) {
+ case TEVENT_REQ_TIMED_OUT:
+ rc = ETIMEDOUT;
+ break;
+ case TEVENT_REQ_NO_MEMORY:
+ rc = ENOMEM;
+ break;
+ case TEVENT_REQ_USER_ERROR:
+ rc = err;
+ break;
+ default:
+ rc = EINVAL;
+ break;
+ }
+
+ tevent_req_received(req);
+ return rc;
+}
+
+/*
+ * Register for notifications. It starts the wait notification loop, and if it fails tries to
+ * unregister. The kernel will retry the registration at regular intervals.
+ */
+
+struct swnd_register_state {
+ struct swnd_context *swnd_ctx;
+ struct swnc_credentials *creds;
+ struct swnc_registration_info *info;
+ uint32_t registration_id;
+};
+
+static void swnc_register_done(struct tevent_req *req);
+
+static int swn_register_handler(struct nl_cache_ops *cache_ops,
+ struct genl_cmd *cmd,
+ struct genl_info *info,
+ void *arg)
+{
+ struct swnd_context *swnd_ctx = talloc_get_type_abort(arg, struct swnd_context);
+ struct swnd_register_state *state = NULL;
+ struct tevent_req *req = NULL;
+ int ret;
+
+ if (!info->attrs[cmd->c_id]) {
+ return NL_SKIP;
+ }
+
+ state = talloc_zero(swnd_ctx, struct swnd_register_state);
+ if (state == NULL) {
+ sd_journal_print(LOG_WARNING, "Failed to allocate memory");
+ return NL_SKIP;
+ }
+ state->swnd_ctx = swnd_ctx;
+
+ ret = get_swnc_registration_info_from_genl_info(state,
+ info,
+ &state->info,
+ &state->registration_id);
+ if (ret != 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to get registration info from kernel message: %s",
+ ret);
+ goto fail;
+ }
+
+ ret = get_swnc_credentials_from_genl_info(state, info, &state->creds);
+ if (ret != 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to get credentials from kernel message: %s",
+ ret);
+ goto fail;
+ }
+
+ sd_journal_print(LOG_NOTICE,
+ "Register: id='%u', net_name='%s' (%s), ip='%s' (%s), share_name='%s' (%s)",
+ state->registration_id,
+ state->info->net_name, state->info->net_name_req ? "y" : "n",
+ state->info->ip_address, state->info->ip_address_req ? "y" : "n",
+ state->info->share_name, state->info->share_name_req ? "y" : "n");
+
+ req = swnc_register_send(state,
+ state->swnd_ctx->ev_ctx,
+ state->swnd_ctx->swn,
+ state->creds,
+ state->info);
+ if (req == NULL) {
+ sd_journal_print(LOG_WARNING, "Failed to create register request");
+ goto fail;
+ }
+ tevent_req_set_callback(req, swnc_register_done, state);
+
+ return NL_OK;
+
+fail:
+ TALLOC_FREE(state);
+ return NL_SKIP;
+}
+
+static void wait_notification_loop_done(struct tevent_req *req);
+static void swnc_unregister_done(struct tevent_req *req);
+
+static void swnc_register_done(struct tevent_req *req)
+{
+ struct swnd_register_state *state =
+ tevent_req_callback_data(req, struct swnd_register_state);
+ int ret;
+
+ ret = swnc_register_recv(req);
+ TALLOC_FREE(req);
+
+ if (ret != 0) {
+ if (ret == EEXIST) {
+ sd_journal_print(LOG_INFO,
+ "Already registered: id='%u', net_name='%s' (%s), ip='%s' (%s), "
+ "share_name='%s' (%s)",
+ state->registration_id,
+ state->info->net_name, state->info->net_name_req ? "y" : "n",
+ state->info->ip_address, state->info->ip_address_req ? "y" : "n",
+ state->info->share_name, state->info->share_name_req ? "y" : "n");
+ } else {
+ sd_journal_print(LOG_WARNING, "Failed to register: %s", strerror(ret));
+ }
+ return;
+ }
+
+ sd_journal_print(LOG_INFO, "Registered, starting notification wait loop");
+
+ /* Use swnd_ctx as mem_ctx, we have to free the state now. */
+ req = wait_notification_loop_send(state->swnd_ctx,
+ state->registration_id,
+ state->info);
+ if (req == NULL) {
+ sd_journal_print(LOG_WARNING, "Failed to allocate wait loop request");
+ goto unregister;
+ }
+ tevent_req_set_callback(req, wait_notification_loop_done, state->swnd_ctx);
+
+ TALLOC_FREE(state);
+
+ return;
+
+unregister:
+ req = swnc_unregister_send(state,
+ state->swnd_ctx->ev_ctx,
+ state->swnd_ctx->swn,
+ state->info);
+ if (req == NULL) {
+ sd_journal_print(LOG_WARNING, "Failed to allocate unregister request");
+ return;
+ }
+ tevent_req_set_callback(req, swnc_unregister_done, state);
+}
+
+static void wait_notification_loop_done(struct tevent_req *req)
+{
+ struct swnd_context *swnd_ctx = tevent_req_callback_data(req,
+ struct swnd_context);
+ int ret;
+
+ ret = wait_notification_loop_recv(req);
+ TALLOC_FREE(req);
+ if (ret != 0 && ret != ECANCELED) {
+ sd_journal_print(LOG_WARNING,
+ "Wait notification loop finished: %d (%s)",
+ ret, strerror(ret));
+ }
+}
+
+/*
+ * Unregister for notifications. It tries to cancel the wait notification loop.
+ */
+static int swn_unregister_handler(struct nl_cache_ops *cache_ops,
+ struct genl_cmd *cmd,
+ struct genl_info *nlinfo,
+ void *arg)
+{
+ struct swnd_context *swnd_ctx = talloc_get_type_abort(arg, struct swnd_context);
+ struct swnd_register_state *state = NULL;
+ struct tevent_req *req = NULL;
+ int ret;
+
+ if (!nlinfo->attrs[cmd->c_id]) {
+ return NL_SKIP;
+ }
+
+ state = talloc_zero(swnd_ctx, struct swnd_register_state);
+ if (state == NULL) {
+ sd_journal_print(LOG_WARNING, "Failed to allocate memory");
+ return NL_SKIP;
+ }
+ state->swnd_ctx = swnd_ctx;
+
+ ret = get_swnc_registration_info_from_genl_info(state,
+ nlinfo,
+ &state->info,
+ &state->registration_id);
+ if (ret != 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to get registration info from kernel message: %s",
+ ret);
+ goto fail;
+ }
+
+ sd_journal_print(LOG_NOTICE,
+ "Unregister: id='%u', net_name='%s' (%s), ip='%s' (%s), share_name='%s' (%s)",
+ state->registration_id,
+ state->info->net_name, state->info->net_name_req ? "y" : "n",
+ state->info->ip_address, state->info->ip_address_req ? "y" : "n",
+ state->info->share_name, state->info->share_name_req ? "y" : "n");
+
+ req = swnc_unregister_send(state->swnd_ctx,
+ state->swnd_ctx->ev_ctx,
+ state->swnd_ctx->swn,
+ state->info);
+ if (req == NULL) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to create unregister request.\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, swnc_unregister_done, state);
+
+ return NL_OK;
+
+fail:
+ TALLOC_FREE(state);
+ return NL_SKIP;
+}
+
+static void swnc_unregister_done(struct tevent_req *req)
+{
+ struct swnd_register_state *state =
+ tevent_req_callback_data(req, struct swnd_register_state);
+ int ret;
+
+ ret = swnc_unregister_recv(req);
+ TALLOC_FREE(req);
+ if (ret != 0) {
+ sd_journal_print(LOG_WARNING, "Failed to unregister: %s\n",
+ strerror(ret));
+ }
+
+ TALLOC_FREE(state);
+}
+
+static int parse_cb(struct nl_msg *msg, void *arg)
+{
+ return genl_handle_msg(msg, arg);
+}
+
+static void netlink_socket_read_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct swnd_context *swnd_ctx = talloc_get_type_abort(
+ private_data, struct swnd_context);
+ int ret;
+
+ sd_journal_print(LOG_DEBUG, "Netlink socket read ready\n");
+
+ ret = nl_recvmsgs_default(swnd_ctx->nlsk);
+ if (ret != 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to receive message: %s\n",
+ nl_geterror(ret));
+ return;
+ }
+}
+
+static void netlink_socket_close_handler(struct tevent_context *ev_ctx,
+ struct tevent_fd *fde,
+ int fd,
+ void *private_data)
+{
+ struct swnd_context *swnd_ctx = talloc_get_type_abort(
+ private_data, struct swnd_context);
+ int ret;
+
+ sd_journal_print(LOG_DEBUG, "Closing netlink socket\n");
+
+ nl_socket_free(swnd_ctx->nlsk);
+}
+
+static int setup_netlink_socket(struct swnd_context *swnd_ctx)
+{
+ int ret;
+ int fd;
+
+ swnd_ctx->nlsk = nl_socket_alloc();
+ if (swnd_ctx->nlsk == NULL) {
+ sd_journal_print(LOG_ERR, "Failed to allocate netlink socket");
+ return ENOMEM;
+ }
+
+ nl_socket_disable_seq_check(swnd_ctx->nlsk);
+
+ ret = genl_connect(swnd_ctx->nlsk);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to connect netlink socket: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ /* Check if kernel has already registered the family, as we may have
+ * started before the cifs kernel module is loaded. */
+ ret = genl_ctrl_resolve(swnd_ctx->nlsk, CIFS_GENL_NAME);
+ if (ret < 0) {
+ sd_journal_print(LOG_WARNING,
+ "Failed to resolve netlink family: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+ swnd_ctx->cifs_id = ret;
+
+ ret = genl_ctrl_resolve_grp(swnd_ctx->nlsk,
+ CIFS_GENL_NAME,
+ CIFS_GENL_MCGRP_SWN_NAME);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to resolve multicast group: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ ret = nl_socket_add_membership(swnd_ctx->nlsk, ret);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to join multicast group: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ ret = genl_register_family(&swnd_genl_family_ops);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to register swnd netlink ops: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ ret = genl_ops_resolve(swnd_ctx->nlsk, &swnd_genl_family_ops);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to resolve swnd netlink ops: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ ret = nl_socket_modify_cb(swnd_ctx->nlsk,
+ NL_CB_VALID,
+ NL_CB_CUSTOM,
+ parse_cb,
+ swnd_ctx);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to modify valid message callback: %s",
+ nl_geterror(ret));
+ goto fail;
+ }
+
+ ret = nl_socket_set_nonblocking(swnd_ctx->nlsk);
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to set netlink socket "
+ "non-blocking: %s", nl_geterror(ret));
+ goto fail;
+ }
+
+ fd = nl_socket_get_fd(swnd_ctx->nlsk);
+ swnd_ctx->fde = tevent_add_fd(swnd_ctx->ev_ctx,
+ swnd_ctx,
+ fd,
+ TEVENT_FD_READ,
+ netlink_socket_read_handler,
+ swnd_ctx);
+ if (swnd_ctx->fde == NULL) {
+ ret = EIO;
+ sd_journal_print(LOG_ERR,
+ "Failed to set netlink socket handler");
+ goto fail;
+ }
+
+ tevent_fd_set_close_fn(swnd_ctx->fde, netlink_socket_close_handler);
+
+ sd_journal_print(LOG_DEBUG, "Netlink socket created");
+
+ return 0;
+fail:
+ nl_socket_free(swnd_ctx->nlsk);
+ swnd_ctx->nlsk = NULL;
+ return ret;
+}
+
+static void sigterm_handler(struct tevent_context *ev_ctx,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ TALLOC_CTX *mem_ctx = (TALLOC_CTX *)private_data;
+
+ sd_journal_print(LOG_INFO, "Termination signal received, exit now\n");
+
+ TALLOC_FREE(mem_ctx);
+
+ exit(EXIT_SUCCESS);
+}
+
+static int setup_sigterm_handler(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev_ctx)
+{
+ struct tevent_signal *se = NULL;
+
+ se = tevent_add_signal(ev_ctx,
+ mem_ctx,
+ SIGTERM,
+ 0,
+ sigterm_handler,
+ mem_ctx);
+ if (se == NULL) {
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+void samba_debug_cb(void *private_data, int level, const char *msg)
+{
+ int sd_lvl;
+
+ switch (level) {
+ case 0:
+ sd_lvl = LOG_ERR;
+ break;
+ case 1:
+ sd_lvl = LOG_WARNING;
+ break;
+ case 2:
+ case 3:
+ sd_lvl = LOG_NOTICE;
+ break;
+ case 4:
+ case 5:
+ sd_lvl = LOG_INFO;
+ break;
+ default:
+ sd_lvl = LOG_DEBUG;
+ break;
+ }
+ sd_journal_print(sd_lvl, msg);
+}
+
+static const struct option long_options[] = {
+ {"debuglevel", 1, NULL, 'd'},
+ {NULL, 0, NULL, 0}
+};
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int debug_level = 1;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct swnd_context *swnd_ctx = NULL;
+ int ret;
+
+ while ((c = getopt_long(argc, argv, "d:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ debug_level = strtoul(optarg, NULL, 10);
+ break;
+ default:
+ sd_journal_print(LOG_ERR, "unknown option: %c", c);
+ return EXIT_FAILURE;
+ }
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ sd_journal_print(LOG_ERR, "Failed to allocate talloc context");
+ return EXIT_FAILURE;
+ }
+
+ swnd_ctx = talloc_zero(mem_ctx, struct swnd_context);
+ if (swnd_ctx == NULL) {
+ TALLOC_FREE(mem_ctx);
+ sd_journal_print(LOG_ERR, "Failed to allocate swnd context");
+ return EXIT_FAILURE;
+ }
+
+ swnd_ctx->ev_ctx = tevent_context_init(swnd_ctx);
+ if (swnd_ctx->ev_ctx == NULL) {
+ sd_journal_print(LOG_ERR, "Failed to allocate tevent context");
+ TALLOC_FREE(mem_ctx);
+ return EXIT_FAILURE;
+ }
+
+ /* Setup SIGTERM handler */
+ ret = setup_sigterm_handler(mem_ctx, swnd_ctx->ev_ctx);
+ if (ret != 0) {
+ sd_journal_print(LOG_ERR, "Failed to setup sigterm handler");
+ TALLOC_FREE(mem_ctx);
+ return EXIT_FAILURE;
+ }
+
+ /* Setup socket handler */
+ ret = setup_netlink_socket(swnd_ctx);
+ if (ret != 0) {
+ sd_journal_print(LOG_ERR, "Failed to setup netlink socket");
+ TALLOC_FREE(mem_ctx);
+ return EXIT_FAILURE;
+ }
+
+ /* Init witness client library */
+ ret = swnc_init_state(swnd_ctx, &swnd_ctx->swn);
+ if (ret != 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to initialize witness library: %s\n",
+ strerror(ret));
+ TALLOC_FREE(mem_ctx);
+ return EXIT_FAILURE;
+ }
+
+ /* Setup samba logging */
+ ret = swnc_set_debug_callback(swnd_ctx, debug_level, samba_debug_cb);
+ if (ret != 0) {
+ sd_journal_print(LOG_ERR,
+ "Failed to set samba debug callback: %s\n",
+ strerror(ret));
+ TALLOC_FREE(mem_ctx);
+ return EXIT_FAILURE;
+ }
+
+ /* Notify systemd we are ready */
+ ret = sd_notify(0, "READY=1");
+ if (ret < 0) {
+ sd_journal_print(LOG_ERR, "sd_notify failed [%d]", ret);
+ }
+
+ /* Loop forever */
+ ret = tevent_loop_wait(swnd_ctx->ev_ctx);
+
+ /* Should not be reached */
+ sd_journal_print(LOG_ERR,
+ "tevent_loop_wait() exited with %d: %s",
+ ret, (ret == 0) ? "out of events" : strerror(errno));
+
+ TALLOC_FREE(mem_ctx);
+
+ return EXIT_FAILURE;
+}
+
+/* vim: set ts=8 sw=8 tw=0 noet : */
diff --git a/swnd.h b/swnd.h
new file mode 100644
index 0000000..194f4a6
--- /dev/null
+++ b/swnd.h
@@ -0,0 +1,23 @@
+/*
+ * Service Witness Daemon for CIFS
+ *
+ * Copyright (C) 2020 Samuel Cabrero (scabrero@samba.org)
+ *
+ * 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/>.
+ */
+
+#ifndef _LINUX_CIFS_SWND_H
+#define _LINUX_CIFS_SWND_H
+
+#endif /* _LINUX_CIFS_SWND_H */
diff --git a/swnd.rst b/swnd.rst
new file mode 100644
index 0000000..305540c
--- /dev/null
+++ b/swnd.rst
@@ -0,0 +1,21 @@
+=========
+swnd
+=========
+
+-----------------------------------------
+Service Witness Protocol Daemon
+-----------------------------------------
+:Manual section: 1
+
+********
+SYNOPSIS
+********
+
+TODO
+
+*******
+AUTHORS
+*******
+
+The swnd program was originally developed by Samuel Cabrero
+<scabrero@samba.org>.
--
2.48.1