smbd/notify: add option "honor change notify privilege"
authorBjörn Baumbach <bb@sernet.de>
Tue, 6 Nov 2018 14:21:37 +0000 (15:21 +0100)
committerStefan Metzmacher <metze@samba.org>
Thu, 17 Dec 2020 15:01:53 +0000 (15:01 +0000)
This option can be used to make use of the change notify privilege.
By default notify results are not checked against the file system
permissions.

If "honor change notify privilege" is enabled, a user will only
receive notify results, if he has change notify privilege or sufficient
file system permissions. If a user has the change notify privilege, he
will receive all requested notify results, even if the user does not
have the permissions on the file system.

Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>

Signed-off-by: Björn Baumbach <bb@sernet.de>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
Autobuild-User(master): Stefan Metzmacher <metze@samba.org>
Autobuild-Date(master): Thu Dec 17 15:01:53 UTC 2020 on sn-devel-184

docs-xml/smbdotconf/misc/honorchangenotifyprivilege.xml [new file with mode: 0644]
selftest/knownfail.d/notify_privileged [deleted file]
selftest/target/Samba3.pm
source3/param/loadparm.c
source3/smbd/notify.c

diff --git a/docs-xml/smbdotconf/misc/honorchangenotifyprivilege.xml b/docs-xml/smbdotconf/misc/honorchangenotifyprivilege.xml
new file mode 100644 (file)
index 0000000..a9c880c
--- /dev/null
@@ -0,0 +1,20 @@
+<samba:parameter name="honor change notify privilege"
+                 context="S"
+                 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+       <para>
+         This option can be used to make use of the change notify privilege.
+         By default notify results are not checked against the file system
+         permissions.
+       </para>
+       <para>
+         If "honor change notify privilege" is enabled, a user will only
+         receive notify results, if he has change notify privilege or
+         sufficient file system permissions. If a user has the change notify
+         privilege, he will receive all requested notify results, even if the
+         user does not have the permissions on the file system.
+       </para>
+</description>
+<value type="default">no</value>
+</samba:parameter>
diff --git a/selftest/knownfail.d/notify_privileged b/selftest/knownfail.d/notify_privileged
deleted file mode 100644 (file)
index 44ff8cb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-^samba.tests.smb-notify.samba.tests.smb-notify.SMBNotifyTests.test_notify_privileged
index 9c8b657e66da9c318a122d3da0fdb3a55af0ff43..ee20528a325a179690c42019139b30e81a9b926a 100755 (executable)
@@ -2892,6 +2892,7 @@ sub provision($$)
 
 [notify_priv]
        copy = tmp
+       honor change notify privilege = yes
        ";
 
        close(CONF);
index 3de22b350f11e1db0b137ca98aa18d288e341647..acb4d149f0b4c19a80f791e057c452932bfa2f59 100644 (file)
@@ -250,6 +250,7 @@ static const struct loadparm_service _sDefault =
        .smbd_search_ask_sharemode = true,
        .smbd_getinfo_ask_sharemode = true,
        .spotlight_backend = SPOTLIGHT_BACKEND_NOINDEX,
+       .honor_change_notify_privilege = false,
        .dummy = ""
 };
 
index 5f18b5cf794c6e7b8062b5777577c969d391e792..43abef0434d00f3b747ce8c8cc27ab959fbc24ca 100644 (file)
@@ -24,6 +24,8 @@
 #include "smbd/globals.h"
 #include "../librpc/gen_ndr/ndr_notify.h"
 #include "librpc/gen_ndr/ndr_file_id.h"
+#include "libcli/security/privileges.h"
+#include "libcli/security/security.h"
 
 struct notify_change_event {
        struct timespec when;
@@ -597,6 +599,104 @@ void notify_fname(connection_struct *conn, uint32_t action, uint32_t filter,
        notify_trigger(notify_ctx, action, filter, conn->connectpath, path);
 }
 
+static bool user_can_stat_name_under_fsp(files_struct *fsp, const char *name)
+{
+       uint32_t rights;
+       struct smb_filename fname;
+       char *filepath = NULL;
+       NTSTATUS status;
+       char *p = NULL;
+
+       /*
+        * Assume we get filepath (relative to the share)
+        * like this:
+        *
+        *  'dir1/dir2/dir3/file'
+        *
+        * We start with LIST and TRAVERSE on the
+        * direct parent ('dir1/dir2/dir3')
+        *
+        * Then we switch to just TRAVERSE for
+        * the rest: 'dir1/dir2', 'dir1', '.'
+        *
+        * For a file in the share root, we'll have
+        *  'file'
+        * and would just check '.' with LIST and TRAVERSE.
+        *
+        * It's important to always check '.' as the last step,
+        * which means we check the permissions of the share root
+        * directory.
+        */
+
+       if (ISDOT(fsp->fsp_name->base_name)) {
+               filepath = talloc_strdup(talloc_tos(), name);
+       } else {
+               filepath = talloc_asprintf(talloc_tos(),
+                       "%s/%s",
+                       fsp->fsp_name->base_name,
+                       name);
+       }
+       if (filepath == NULL) {
+               DBG_ERR("Memory allocation failed\n");
+               return false;
+       }
+
+       fname = (struct smb_filename) { .base_name = filepath };
+
+       rights = SEC_DIR_LIST|SEC_DIR_TRAVERSE;
+       p = strrchr_m(filepath, '/');
+       /*
+        * Check each path component, exluding the share root.
+        *
+        * We could check all components including root using
+        * a do { .. } while() loop, but IMHO the logic is clearer
+        * having the share root check separately afterwards.
+        */
+       while (p != NULL) {
+               *p = '\0';
+               status = smbd_check_access_rights(fsp->conn,
+                                                 fsp->conn->cwd_fsp,
+                                                 &fname,
+                                                 false,
+                                                 rights);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DBG_DEBUG("Access rights for %s/%s: %s\n",
+                                 fsp->conn->connectpath,
+                                 filepath,
+                                 nt_errstr(status));
+                       TALLOC_FREE(filepath);
+                       return false;
+               }
+
+               rights = SEC_DIR_TRAVERSE;
+               p = strrchr_m(filepath, '/');
+       }
+
+       TALLOC_FREE(filepath);
+
+       /* Finally check share root. */
+       filepath = talloc_strdup(talloc_tos(), ".");
+       if (filepath == NULL) {
+               DBG_ERR("Memory allocation failed\n");
+               return false;
+       }
+       fname = (struct smb_filename) { .base_name = filepath };
+       status = smbd_check_access_rights(fsp->conn,
+                                         fsp->conn->cwd_fsp,
+                                         &fname,
+                                         false,
+                                         rights);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("Access rights for %s/.: %s\n",
+                         fsp->conn->connectpath,
+                         nt_errstr(status));
+               TALLOC_FREE(filepath);
+               return false;
+       }
+       TALLOC_FREE(filepath);
+       return true;
+}
+
 static void notify_fsp(files_struct *fsp, struct timespec when,
                       uint32_t action, const char *name)
 {
@@ -610,6 +710,35 @@ static void notify_fsp(files_struct *fsp, struct timespec when,
                return;
        }
 
+       if (lp_honor_change_notify_privilege(SNUM(fsp->conn))) {
+               bool has_sec_change_notify_privilege;
+               bool expose = false;
+
+               has_sec_change_notify_privilege = security_token_has_privilege(
+                       fsp->conn->session_info->security_token,
+                       SEC_PRIV_CHANGE_NOTIFY);
+
+               if (has_sec_change_notify_privilege) {
+                       expose = true;
+               } else {
+                       bool ok;
+
+                       ok = become_user_without_service_by_fsp(fsp);
+                       if (ok) {
+                               expose = user_can_stat_name_under_fsp(fsp, name);
+                               unbecome_user_without_service();
+                       }
+               }
+               DBG_DEBUG("has_sec_change_notify_privilege=%s "
+                         "expose=%s for %s notify %s\n",
+                         has_sec_change_notify_privilege ? "true" : "false",
+                         expose ? "true" : "false",
+                         fsp->fsp_name->base_name, name);
+               if (!expose) {
+                       return;
+               }
+       }
+
        /*
         * Someone has triggered a notify previously, queue the change for
         * later.