From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/modules/README-gpfs-acl.txt | 82 + source3/modules/README.nfs4acls.txt | 89 + source3/modules/The_New_VFS.org | 469 +++ source3/modules/The_New_VFS.txt | 607 ++++ source3/modules/getdate.c | 2742 +++++++++++++++ source3/modules/getdate.h | 45 + source3/modules/getdate.y | 1118 ++++++ source3/modules/hash_inode.c | 87 + source3/modules/hash_inode.h | 25 + source3/modules/lib_vxfs.c | 279 ++ source3/modules/nfs41acl.x | 111 + source3/modules/nfs4_acls.c | 1223 +++++++ source3/modules/nfs4_acls.h | 184 + source3/modules/nfs4acl_xattr.h | 40 + source3/modules/nfs4acl_xattr_ndr.c | 300 ++ source3/modules/nfs4acl_xattr_ndr.h | 44 + source3/modules/nfs4acl_xattr_nfs.c | 894 +++++ source3/modules/nfs4acl_xattr_nfs.h | 36 + source3/modules/nfs4acl_xattr_util.c | 73 + source3/modules/nfs4acl_xattr_util.h | 25 + source3/modules/nfs4acl_xattr_xdr.c | 401 +++ source3/modules/nfs4acl_xattr_xdr.h | 34 + source3/modules/non_posix_acls.c | 63 + source3/modules/non_posix_acls.h | 24 + source3/modules/offload_token.c | 348 ++ source3/modules/offload_token.h | 44 + source3/modules/posixacl_xattr.c | 423 +++ source3/modules/posixacl_xattr.h | 36 + source3/modules/test_nfs4_acls.c | 1898 +++++++++++ source3/modules/test_vfs_full_audit.c | 49 + source3/modules/test_vfs_gpfs.c | 101 + source3/modules/test_vfs_posixacl.c | 171 + source3/modules/util_reparse.c | 84 + source3/modules/util_reparse.h | 40 + source3/modules/vfs_acl_common.c | 1181 +++++++ source3/modules/vfs_acl_common.h | 91 + source3/modules/vfs_acl_tdb.c | 387 +++ source3/modules/vfs_acl_xattr.c | 552 +++ source3/modules/vfs_afsacl.c | 1085 ++++++ source3/modules/vfs_aio_fork.c | 936 +++++ source3/modules/vfs_aio_pthread.c | 538 +++ source3/modules/vfs_aixacl.c | 131 + source3/modules/vfs_aixacl.h | 34 + source3/modules/vfs_aixacl2.c | 480 +++ source3/modules/vfs_aixacl_util.c | 288 ++ source3/modules/vfs_aixacl_util.h | 22 + source3/modules/vfs_audit.c | 355 ++ source3/modules/vfs_btrfs.c | 887 +++++ source3/modules/vfs_cacheprime.c | 181 + source3/modules/vfs_cap.c | 1004 ++++++ source3/modules/vfs_catia.c | 1952 +++++++++++ source3/modules/vfs_ceph.c | 1960 +++++++++++ source3/modules/vfs_ceph_snapshots.c | 1478 ++++++++ source3/modules/vfs_commit.c | 400 +++ source3/modules/vfs_crossrename.c | 194 ++ source3/modules/vfs_default.c | 4098 ++++++++++++++++++++++ source3/modules/vfs_default_quota.c | 236 ++ source3/modules/vfs_delay_inject.c | 440 +++ source3/modules/vfs_dfs_samba4.c | 162 + source3/modules/vfs_dirsort.c | 267 ++ source3/modules/vfs_error_inject.c | 219 ++ source3/modules/vfs_expand_msdfs.c | 270 ++ source3/modules/vfs_extd_audit.c | 418 +++ source3/modules/vfs_fake_acls.c | 704 ++++ source3/modules/vfs_fake_dfq.c | 281 ++ source3/modules/vfs_fake_perms.c | 108 + source3/modules/vfs_fileid.c | 723 ++++ source3/modules/vfs_fruit.c | 5478 ++++++++++++++++++++++++++++++ source3/modules/vfs_full_audit.c | 3035 +++++++++++++++++ source3/modules/vfs_glusterfs.c | 2673 +++++++++++++++ source3/modules/vfs_glusterfs_fuse.c | 273 ++ source3/modules/vfs_gpfs.c | 2546 ++++++++++++++ source3/modules/vfs_hpuxacl.c | 1174 +++++++ source3/modules/vfs_hpuxacl.h | 56 + source3/modules/vfs_io_uring.c | 822 +++++ source3/modules/vfs_linux_xfs_sgid.c | 128 + source3/modules/vfs_media_harmony.c | 1873 ++++++++++ source3/modules/vfs_nfs4acl_xattr.c | 580 ++++ source3/modules/vfs_not_implemented.c | 1193 +++++++ source3/modules/vfs_offline.c | 50 + source3/modules/vfs_posix_eadb.c | 450 +++ source3/modules/vfs_posixacl.c | 389 +++ source3/modules/vfs_posixacl.h | 38 + source3/modules/vfs_prealloc.c | 222 ++ source3/modules/vfs_preopen.c | 757 +++++ source3/modules/vfs_readahead.c | 186 + source3/modules/vfs_readonly.c | 113 + source3/modules/vfs_recycle.c | 737 ++++ source3/modules/vfs_shadow_copy.c | 274 ++ source3/modules/vfs_shadow_copy2.c | 3303 ++++++++++++++++++ source3/modules/vfs_shell_snap.c | 201 ++ source3/modules/vfs_snapper.c | 2647 +++++++++++++++ source3/modules/vfs_solarisacl.c | 699 ++++ source3/modules/vfs_solarisacl.h | 44 + source3/modules/vfs_streams_depot.c | 1218 +++++++ source3/modules/vfs_streams_xattr.c | 1614 +++++++++ source3/modules/vfs_syncops.c | 418 +++ source3/modules/vfs_time_audit.c | 2812 +++++++++++++++ source3/modules/vfs_tsmsm.c | 573 ++++ source3/modules/vfs_unityed_media.c | 1544 +++++++++ source3/modules/vfs_virusfilter.c | 1677 +++++++++ source3/modules/vfs_virusfilter_clamav.c | 195 ++ source3/modules/vfs_virusfilter_common.h | 154 + source3/modules/vfs_virusfilter_dummy.c | 58 + source3/modules/vfs_virusfilter_fsav.c | 451 +++ source3/modules/vfs_virusfilter_sophos.c | 391 +++ source3/modules/vfs_virusfilter_utils.c | 1036 ++++++ source3/modules/vfs_virusfilter_utils.h | 177 + source3/modules/vfs_vxfs.c | 736 ++++ source3/modules/vfs_vxfs.h | 36 + source3/modules/vfs_widelinks.c | 418 +++ source3/modules/vfs_worm.c | 359 ++ source3/modules/vfs_xattr_tdb.c | 702 ++++ source3/modules/vfs_zfsacl.c | 507 +++ source3/modules/wscript_build | 639 ++++ 115 files changed, 79907 insertions(+) create mode 100644 source3/modules/README-gpfs-acl.txt create mode 100644 source3/modules/README.nfs4acls.txt create mode 100644 source3/modules/The_New_VFS.org create mode 100644 source3/modules/The_New_VFS.txt create mode 100644 source3/modules/getdate.c create mode 100644 source3/modules/getdate.h create mode 100644 source3/modules/getdate.y create mode 100644 source3/modules/hash_inode.c create mode 100644 source3/modules/hash_inode.h create mode 100644 source3/modules/lib_vxfs.c create mode 100644 source3/modules/nfs41acl.x create mode 100644 source3/modules/nfs4_acls.c create mode 100644 source3/modules/nfs4_acls.h create mode 100644 source3/modules/nfs4acl_xattr.h create mode 100644 source3/modules/nfs4acl_xattr_ndr.c create mode 100644 source3/modules/nfs4acl_xattr_ndr.h create mode 100644 source3/modules/nfs4acl_xattr_nfs.c create mode 100644 source3/modules/nfs4acl_xattr_nfs.h create mode 100644 source3/modules/nfs4acl_xattr_util.c create mode 100644 source3/modules/nfs4acl_xattr_util.h create mode 100644 source3/modules/nfs4acl_xattr_xdr.c create mode 100644 source3/modules/nfs4acl_xattr_xdr.h create mode 100644 source3/modules/non_posix_acls.c create mode 100644 source3/modules/non_posix_acls.h create mode 100644 source3/modules/offload_token.c create mode 100644 source3/modules/offload_token.h create mode 100644 source3/modules/posixacl_xattr.c create mode 100644 source3/modules/posixacl_xattr.h create mode 100644 source3/modules/test_nfs4_acls.c create mode 100644 source3/modules/test_vfs_full_audit.c create mode 100644 source3/modules/test_vfs_gpfs.c create mode 100644 source3/modules/test_vfs_posixacl.c create mode 100644 source3/modules/util_reparse.c create mode 100644 source3/modules/util_reparse.h create mode 100644 source3/modules/vfs_acl_common.c create mode 100644 source3/modules/vfs_acl_common.h create mode 100644 source3/modules/vfs_acl_tdb.c create mode 100644 source3/modules/vfs_acl_xattr.c create mode 100644 source3/modules/vfs_afsacl.c create mode 100644 source3/modules/vfs_aio_fork.c create mode 100644 source3/modules/vfs_aio_pthread.c create mode 100644 source3/modules/vfs_aixacl.c create mode 100644 source3/modules/vfs_aixacl.h create mode 100644 source3/modules/vfs_aixacl2.c create mode 100644 source3/modules/vfs_aixacl_util.c create mode 100644 source3/modules/vfs_aixacl_util.h create mode 100644 source3/modules/vfs_audit.c create mode 100644 source3/modules/vfs_btrfs.c create mode 100644 source3/modules/vfs_cacheprime.c create mode 100644 source3/modules/vfs_cap.c create mode 100644 source3/modules/vfs_catia.c create mode 100644 source3/modules/vfs_ceph.c create mode 100644 source3/modules/vfs_ceph_snapshots.c create mode 100644 source3/modules/vfs_commit.c create mode 100644 source3/modules/vfs_crossrename.c create mode 100644 source3/modules/vfs_default.c create mode 100644 source3/modules/vfs_default_quota.c create mode 100644 source3/modules/vfs_delay_inject.c create mode 100644 source3/modules/vfs_dfs_samba4.c create mode 100644 source3/modules/vfs_dirsort.c create mode 100644 source3/modules/vfs_error_inject.c create mode 100644 source3/modules/vfs_expand_msdfs.c create mode 100644 source3/modules/vfs_extd_audit.c create mode 100644 source3/modules/vfs_fake_acls.c create mode 100644 source3/modules/vfs_fake_dfq.c create mode 100644 source3/modules/vfs_fake_perms.c create mode 100644 source3/modules/vfs_fileid.c create mode 100644 source3/modules/vfs_fruit.c create mode 100644 source3/modules/vfs_full_audit.c create mode 100644 source3/modules/vfs_glusterfs.c create mode 100644 source3/modules/vfs_glusterfs_fuse.c create mode 100644 source3/modules/vfs_gpfs.c create mode 100644 source3/modules/vfs_hpuxacl.c create mode 100644 source3/modules/vfs_hpuxacl.h create mode 100644 source3/modules/vfs_io_uring.c create mode 100644 source3/modules/vfs_linux_xfs_sgid.c create mode 100644 source3/modules/vfs_media_harmony.c create mode 100644 source3/modules/vfs_nfs4acl_xattr.c create mode 100644 source3/modules/vfs_not_implemented.c create mode 100644 source3/modules/vfs_offline.c create mode 100644 source3/modules/vfs_posix_eadb.c create mode 100644 source3/modules/vfs_posixacl.c create mode 100644 source3/modules/vfs_posixacl.h create mode 100644 source3/modules/vfs_prealloc.c create mode 100644 source3/modules/vfs_preopen.c create mode 100644 source3/modules/vfs_readahead.c create mode 100644 source3/modules/vfs_readonly.c create mode 100644 source3/modules/vfs_recycle.c create mode 100644 source3/modules/vfs_shadow_copy.c create mode 100644 source3/modules/vfs_shadow_copy2.c create mode 100644 source3/modules/vfs_shell_snap.c create mode 100644 source3/modules/vfs_snapper.c create mode 100644 source3/modules/vfs_solarisacl.c create mode 100644 source3/modules/vfs_solarisacl.h create mode 100644 source3/modules/vfs_streams_depot.c create mode 100644 source3/modules/vfs_streams_xattr.c create mode 100644 source3/modules/vfs_syncops.c create mode 100644 source3/modules/vfs_time_audit.c create mode 100644 source3/modules/vfs_tsmsm.c create mode 100644 source3/modules/vfs_unityed_media.c create mode 100644 source3/modules/vfs_virusfilter.c create mode 100644 source3/modules/vfs_virusfilter_clamav.c create mode 100644 source3/modules/vfs_virusfilter_common.h create mode 100644 source3/modules/vfs_virusfilter_dummy.c create mode 100644 source3/modules/vfs_virusfilter_fsav.c create mode 100644 source3/modules/vfs_virusfilter_sophos.c create mode 100644 source3/modules/vfs_virusfilter_utils.c create mode 100644 source3/modules/vfs_virusfilter_utils.h create mode 100644 source3/modules/vfs_vxfs.c create mode 100644 source3/modules/vfs_vxfs.h create mode 100644 source3/modules/vfs_widelinks.c create mode 100644 source3/modules/vfs_worm.c create mode 100644 source3/modules/vfs_xattr_tdb.c create mode 100644 source3/modules/vfs_zfsacl.c create mode 100644 source3/modules/wscript_build (limited to 'source3/modules') diff --git a/source3/modules/README-gpfs-acl.txt b/source3/modules/README-gpfs-acl.txt new file mode 100644 index 0000000..49f3259 --- /dev/null +++ b/source3/modules/README-gpfs-acl.txt @@ -0,0 +1,82 @@ +This patch has been taken against SAMBA_3_0 Release 20028. + +The patch basically moves the GPFS-ACL functionalities into the new GPFS VFS module( vfs_gpfs ). + +Please read SAMBA_3_0/source/modules/README.nfs4acls.txt - generalised README file on Samba support for NFS4-ACLS. +This README file is specific for GPFS only. + +Configuring GPFS ACL support +=============================== + +Binary: (default install path is [samba]/lib/vfs/) +- gpfs.so + +Its compiled by default, no additional configure option needed. + +To enable/use/load this module, include "vfs objects = gpfs" in the smb.conf file under concerned share-name. + +Example of smb.conf: + +[smbtest] +path = /gpfs-test +vfs objects = gpfs +nfs4: mode = special +nfs4: chown = yes +nfs4: acedup = merge + +Adding "vfs objects = gpfs" to a share should be done only in case when NFS4 is really supported by the filesystem. +(Otherwise you may get performance loss.) + +================================================== +Below are some limitations listed for this module: +================================================== +1. When a child file or child directory is created, the results are a bit different from windows as specified below: + +Eg: Prent directory is set to have 2 ACES - full access for owner and everyone + +Default ACL for Windows: 2 aces: allow ACE for owner and everyone +Default ACL for GPFS: 6 aces: allow and deny ACEs for owner, group and everyone + +The below mentioned inheritance flags and its combinations are applied only to the owner ACE and not to everyone ACE + +"fi"------>File Inherit +"di"------>Directory Inherit +"oi"------>Inherit Only + + +Parent dir: no inheritance flag set + Windows: index=0: GPFS(special mode): index=0: GPFS(simple mode): index=0: +child File: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces +child dir: default acl: 2 aces child dir: default acl: 6 aces child dir: default acl: 6 aces + + +Parent dir: "fi" flag set + Windows: index=1: GPFS(special mode): index=1: GPFS(simple mode): index=1: +child file: no flag set child file: "fi" flag set child file: default acl: 6 aces +child dir: "fioi" flag set child dir: "fi" flag set child dir: "fi" flag set + + +Parent dir: "di" flag set + Windows: index=2: GPFS(special mode): index=2: GPFS(simple mode): index=2: +child file: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces + + +Parent dir: "fidi" flag set + Windows: index=3: GPFS(special mode): index=3: GPFS(simple mode): index=3: +child file: no flag set child file: "fidi" flag set child file: default acl: 6 aces + + +Parent dir: "fioi" flag set + Windows: index=4: GPFS(special mode): index=4: GPFS(simple mode): index=4: +child file: no flag set child file: "fi" flag set child file: default acl: 6 aces +child dir: "fioi" flag set child dir: "fi" flag set child dir: "fi" flag set + + +Parent dir: "dioi" flag set + Windows: index=5: GPFS(special mode): index=5: GPFS(simple mode): index=5: +child file: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces + + +Parent dir: "fidioi" flag set + Windows: index=6: GPFS(special mode): index=6: GPFS(simple mode): index=6: +child file: no flag set child file: "fidi" flag set child file: default acl: 6 aces diff --git a/source3/modules/README.nfs4acls.txt b/source3/modules/README.nfs4acls.txt new file mode 100644 index 0000000..6de87a1 --- /dev/null +++ b/source3/modules/README.nfs4acls.txt @@ -0,0 +1,89 @@ +Configuring NFS4 ACLs in Samba3 +=============================== +Created: Peter Somogyi, 2006-JUN-06 +Last modified: Alexander Werth, 2013-MAY-02 +Revision no.: 4 +------------------------------- + + +Parameters in smb.conf: +======================= + +Each parameter must have a prefix "nfs4:". +Each one affects the behaviour only when _setting_ an acl on a file/dir: + +mode = [simple|special] +- simple: Use OWNER@ and GROUP@ special IDs for non inheriting ACEs only. + This mode is the default. +- special: use OWNER@ and GROUP@ special IDs in ACEs instead of simple + user&group ids. This mode is deprecated. + +Note1: EVERYONE@ is always processed (if found such an ACE). +Note2: There is a side effect when _only_ chown is performed. + Later this may be worked out. +Note3: Mode special inherits incorrect ACL entries when the user creating + a file is different from the owner of the caurrent folder. +Note4: Mode simple uses inheriting OWNER@ and GROUP@ special IDs to + support Creator Owner and Creator Group. + +It's strongly advised to set "store dos attributes = yes" in smb.conf. + +chown = [true|false] +- true => enable changing owner and group - default. +- false => disable support for changing owner or group + +acedup = [dontcare|reject|ignore|merge] +- dontcare: copy ACEs as they come, don't care with "duplicate" records. +- reject: stop operation, exit acl setter operation with an error. (deprecated) +- ignore: don't include the second matching ACE. (deprecated) +- merge: OR 2 ace.flag fields and 2 ace.mask fields of the 2 duplicate ACEs into 1 ACE (default) + +Two ACEs are considered here "duplicate" when their type and id fields are matching. + +Example: + +[smbtest] +path = /tests/psomogyi/smbtest +writable = yes +vfs objects = aixacl2 +nfs4: mode = special +nfs4: chown = yes +nfs4: acedup = merge + +Configuring AIX ACL support +============================== + +Binaries: (default install path is [samba]/lib/vfs/) +- aixacl.so: provides AIXC ACL support only, can be compiled and works on all AIX platforms +- aixacl2.so: provides AIXC and JFS2-NFS4 ACL support, can be compiled and works only under AIX 5.3 and newer. +NFS4 acl currently has support only under JFS2 (ext. attr. format must be set to v2). +aixacl2.so always detects support for NFS4 acls and redirects to POSIX ACL handling automatically when NFS4 is not supported for a path. + +Adding "vfs objects = aixacl2" to a share should be done only in case when NFS4 is really supported by the filesystem. +(Otherwise you may get performance loss.) + +For configuration see also the example above. + +General notes +============= + +NFS4 handling logic is separated from AIX/jfs2 ACL parsing. + +Samba and its VFS modules don't reorder ACEs. Windows clients do that (and the smbcacl tool). MSDN also says deny ACEs must come first. +NFS4 ACL's validity is checked by the system API, not by Samba. +NFS4 ACL rights are enforced by the OS or filesystem, not by Samba. + +The flag INHERITED_ACE is never set (not required, as doesn't do WinNT/98/me, only since Win2k). +Win2k GUI behaves strangely when detecting inheritance (sometimes it doesn't detect, +but after adding an ace it shows that - it's some GUI error). + +Unknown (unmappable) SIDs are not accepted. + +TODOs +===== +- Creator Owner & Group SID handling (same way as posix) +- the 4 generic rights bits support (GENERIC_RIGHT_READ_ACCESS, WRITE, EXEC, ALL) +- chown & no ACL, but we have OWNER@ and GROUP@ +- DIALUP, ANONYMOUS, ... builtin SIDs +- audit & alarm support - in theory it's forwarded so it should work, but currently there's no platform which supports them to test +- support for a real NFS4 client (we don't have an accepted API yet) diff --git a/source3/modules/The_New_VFS.org b/source3/modules/The_New_VFS.org new file mode 100644 index 0000000..2f6a84c --- /dev/null +++ b/source3/modules/The_New_VFS.org @@ -0,0 +1,469 @@ +#+TITLE: The New Samba VFS +#+AUTHOR: Ralph Böhme, SerNet, Samba Team +#+DATE: {{{modification-time(%Y-%m-%d)}}} +* The new VFS +** Summary +The effort to modernize Samba's VFS interface has reached a major milestone with +the next release Samba 4.14. + +Starting with version 4.14 Samba provides core infrastructure code that allows +basing all access to the server's filesystem on file handles and not on +paths. An example of this is using =fstat()= instead of =stat()=, or +=SMB_VFS_FSTAT()= instead of =SMB_VFS_STAT()= in Samba parlance. + +Historically Samba's fileserver code had to deal a lot with processing path +based SMB requests. While the SMB protocol itself has been streamlined to be +purely handle based starting with SMB2, large parts of infrastructure code +remains in place that will "degrade" handle based SMB2 requests to path based +filesystem access. + +In order to fully leverage the handle based nature of the SMB2 protocol we came +up with a straight forward way to convert this infrastructure code. + +At the core, we introduced a helper function that opens a file handle that only +serves as a path reference and hence can not be used for any sort of access to +file data. + +Samba's internal file handle structure is of type =struct files_struct= and all +variable pointing to objects of such type are typically called =fsp=. Until very +recently the only function that would open such a file handle and return an fsp +was =SMB_VFS_CREATE_FILE()=. + +Internally =SMB_VFS_CREATE_FILE()= consisted of processing through Samba's VFS +open function to open the low level file and then going through Samba's Windows +NTFS emulation code. + +The key point of the new helper function which is called =openat_pathref_fsp()= +is that it skips the NTFS emulation logic. Additionally, the handle is +restricted internally to be only usable as a path reference but not for any sort +of IO. On Linux this is achieved by using the =O_PATH= =open()= flag, on systems +without =O_PATH= support other mechanisms are used described in more detail +below. + +Path processing in Samba typically means processing client supplied paths by +Samba's core path processing function =filename_convert()= which returns a +pointer to an object of type =struct smb_filename=. Pointers to such objects are +then passed around, often passing many layers of code. + +By attaching an =fsp= file handle returned from =openat_pathref_fsp()= to all +=struct smb_filename= objects returned from =filename_convert()=, the whole +infrastructure code has immediate access to a file handle and so the large +infrastructure codebase can be converted to use handle based VFS functions +whenever VFS access is done in a piecemeal fashion. +** Samba and O_PATH +*** Background + On Linux the =O_PATH= flag to =open()= can be used to open a filehandle on a + file or directory with interesting properties: [fn:manpage] + + - the file-handle indicates a location in the filesystem tree, + + - no permission checks are done by the kernel on the filesystem object and + + - only operations that act purely at the file descriptor level are allowed. + + The file itself is not opened, and other file operations (e.g., ~read(2)~, + ~write(2)~, ~fchmod(2)~, ~fchown(2)~, ~fgetxattr(2)~, ~ioctl(2)~, ~mmap(2)~) fail + with the error ~EBADF~. + + The following subset of operations that is relevant to Samba is allowed: + + - ~close(2)~, + + - ~fchdir(2)~, if the file descriptor refers to a directory, + + - ~fstat(2)~, + + - ~fstatfs(2)~ and + + - passing the file descriptor as the dirfd argument of ~openat()~ and the other + "*at()" system calls. This includes ~linkat(2)~ with AT_EMPTY_PATH (or via + procfs using AT_SYMLINK_FOLLOW) even if the file is not a directory. + + Opening a file or directory with the ~O_PATH~ flag requires no permissions + on the object itself (but does require execute permission on the + directories in the path prefix). By contrast, obtaining a reference to a + filesystem object by opening it with the ~O_RDONLY~ flag requires that the + caller have read permission on the object, even when the subsequent + operation (e.g., ~fchdir(2)~, ~fstat(2)~) does not require read permis‐ + sion on the object. + + If for example Samba receives an SMB request to open a file requesting + ~SEC_FILE_READ_ATTRIBUTE~ access rights because the client wants to read the + file's metadata from the handle, Samba will have to call ~open()~ with at least + ~O_RDONLY~ access rights. +*** Usecases for O_PATH in Samba + The ~O_PATH~ flag is currently not used in Samba. By leveraging this Linux + specific flags we can avoid permission mismatches as described above. + + Additionally ~O_PATH~ allows basing all filesystem accesses done by the + fileserver on handle based syscalls by opening all client pathnames with + ~O_PATH~ and consistently using for example ~fstat()~ instead of ~stat()~ + throughout the codebase. + + Subsequent parts of this document will call such file-handles opened with O_PATH + *path referencing file-handles* or *pathref*s for short. + +*** When to open with O_PATH + In Samba the decision whether to call POSIX ~open()~ on a client pathname or + whether to leave the low-level handle at -1 (what we call a stat-open) is based + on the client requested SMB access mask. + + The set of access rights that trigger an ~open()~ includes + ~READ_CONTROL_ACCESS~. As a result, the open() will be done with at least + ~O_RDONLY~. If the filesystem supports NT style ACLs natively (like GPFS or ZFS), + the filesystem may grant the user requested right ~READ_CONTROL_ACCESS~, but it + may not grant ~READ_DATA~ (~O_RDONLY~). + + Currently the full set of access rights that trigger opening a file is: + + - FILE_READ_DATA + - FILE_WRITE_DATA + - FILE_APPEND_DATA + - FILE_EXECUTE + - WRITE_DAC_ACCESS + - WRITE_OWNER_ACCESS + - SEC_FLAG_SYSTEM_SECURITY + - READ_CONTROL_ACCESS + + In the future we can remove the following rights from the list on systems that + support O_PATH: + + - WRITE_DAC_ACCESS + - WRITE_OWNER_ACCESS + - SEC_FLAG_SYSTEM_SECURITY + - READ_CONTROL_ACCESS +*** Fallback on systems without O_PATH support + The code of higher level file-handle consumers must be kept simple and + streamlined, avoiding special casing the handling of the file-handles opened + with or without ~O_PATH~. To achieve this, a fallback that allows opening a + file-handle with the same higher level semantics even if the system doesn't + support ~O_PATH~ is needed. + + The way this is implemented on such systems is impersonating the root user for + the ~open()~ syscall. In order to avoid privilege escalations security issues, + we must carefully control the use these file-handles. + + The low level filehandle is stored in a public struct ~struct file_handle~ that + is part of the widely used ~struct files_struct~. Consumers used to simply + access the fd directly by dereferencing pointers to ~struct files_struct~. + + In order to guard access to such file-handles we do two things: + + - tag the pathref file-handles and + + - control access to the file-handle by making the structure ~struct + file_handle~ private, only allowing access with accessor functions that + implement a security boundary. + + In order to avoid bypassing restrictive permissions on intermediate directories + of a client path, the root user is only impersonated after changing directory + to the parent directory of the client requested pathname. + + Two functions can then be used to fetch the low-level system file-handle from a + ~struct files_struct~: + + - ~fsp_get_io_fd(fsp)~: enforces fsp is NOT a pathref file-handle and + + - ~fsp_get_pathref_fd(fsp)~: allows fsp to be either a pathref file-handle or a + traditional POSIX file-handle opened with O_RDONLY or any other POSIX open + flag. + + Note that the name ~fsp_get_pathref_fd()~ may sound confusing at first given + that the fsp can be either a pathref fsp or a "normal/full" fsp, but as any + full file-handle can be used for IO and as path reference, the name + correctly reflects the intended usage of the caller. +*** When to use fsp_get_io_fd() or fsp_get_pathref_fd() + + The general guideline is: + + - if you do something like ~fstat(fd)~, use ~fsp_get_pathref_fd()~, + + - if you do something like ~*at(dirfd, ...)~, use ~fsp_get_pathref_fd()~, + + - if you want to print the fd for example in =DEBUG= messages, use ~fsp_get_pathref_fd()~, + + - if you want to call ~close(fd)~, use ~fsp_get_pathref_fd()~, + + - if you're doing a logical comparison of fd values, use ~fsp_get_pathref_fd()~. + + In any other case use ~fsp_get_io_fd()~. + +[fn:manpage] parts of the following sections copied from man open(2) +[fn:gitlab] https://gitlab.com/samba-team/devel/samba/-/commits/slow-pathref-wip + +* VFS status quo and remaining work +** VFS Functions Tables [fn:VFS_API] +*** Existing VFS Functions +#+ATTR_HTML: :border 1 :rules all :frame border +| VFS Function | Group | Status | +|-----------------------------------+----------+--------| +| SMB_VFS_AIO_FORCE() | [[fsp][fsp]] | - | +| SMB_VFS_AUDIT_FILE() | [[Special][Special]] | - | +| SMB_VFS_BRL_LOCK_WINDOWS() | [[fsp][fsp]] | - | +| SMB_VFS_BRL_UNLOCK_WINDOWS() | [[fsp][fsp]] | - | +| SMB_VFS_CHDIR() | [[Path][Path]] | Todo | +| SMB_VFS_CHFLAGS() | [[Path][Path]] | - | +| SMB_VFS_CHMOD() | [[Path][Path]] | - | +| SMB_VFS_CLOSE() | [[fsp][fsp]] | - | +| SMB_VFS_CLOSEDIR() | [[fsp][fsp]] | - | +| SMB_VFS_CONNECT() | [[Disk][Disk]] | - | +| SMB_VFS_CONNECTPATH() | [[P2px][P2px]] | - | +| SMB_VFS_CREATE_DFS_PATHAT() | [[NsC][NsC]] | - | +| SMB_VFS_CREATE_FILE() | [[NsC][NsC]] | - | +| SMB_VFS_DISCONNECT() | [[Disk][Disk]] | - | +| SMB_VFS_DISK_FREE() | [[Disk][Disk]] | - | +| SMB_VFS_DURABLE_COOKIE() | [[fsp][fsp]] | - | +| SMB_VFS_DURABLE_DISCONNECT() | [[fsp][fsp]] | - | +| SMB_VFS_DURABLE_RECONNECT() | [[fsp][fsp]] | - | +| SMB_VFS_FALLOCATE() | [[fsp][fsp]] | - | +| SMB_VFS_FCHMOD() | [[fsp][fsp]] | - | +| SMB_VFS_FCHOWN() | [[fsp][fsp]] | - | +| SMB_VFS_FCNTL() | [[fsp][fsp]] | - | +| SMB_VFS_FDOPENDIR() | [[fsp][fsp]] | - | +| SMB_VFS_FGET_COMPRESSION() | [[fsp][fsp]] | - | +| SMB_VFS_FGET_DOS_ATTRIBUTES() | [[fsp][fsp]] | - | +| SMB_VFS_FGET_NT_ACL() | [[fsp][fsp]] | - | +| SMB_VFS_FGETXATTR() | [[xpathref][xpathref]] | - | +| SMB_VFS_FILE_ID_CREATE() | [[Special][Special]] | - | +| SMB_VFS_FLISTXATTR() | [[xpathref][xpathref]] | - | +| SMB_VFS_FREMOVEXATTR() | [[xpathref][xpathref]] | - | +| SMB_VFS_FS_CAPABILITIES() | [[Disk][Disk]] | - | +| SMB_VFS_FSCTL() | [[fsp][fsp]] | - | +| SMB_VFS_FSET_DOS_ATTRIBUTES() | [[fsp][fsp]] | - | +| SMB_VFS_FSET_NT_ACL() | [[fsp][fsp]] | - | +| SMB_VFS_FSETXATTR() | [[xpathref][xpathref]] | - | +| SMB_VFS_FS_FILE_ID() | [[Special][Special]] | - | +| SMB_VFS_FSTAT() | [[fsp][fsp]] | - | +| SMB_VFS_FSYNC() | [[fsp][fsp]] | - | +| SMB_VFS_FSYNC_SEND() | [[fsp][fsp]] | - | +| SMB_VFS_FTRUNCATE() | [[fsp][fsp]] | - | +| SMB_VFS_GET_ALLOC_SIZE() | [[fsp][fsp]] | - | +| SMB_VFS_GET_DFS_REFERRALS() | [[Disk][Disk]] | - | +| SMB_VFS_GET_DOS_ATTRIBUTES_RECV() | [[Enum][Enum]] | - | +| SMB_VFS_GET_DOS_ATTRIBUTES_SEND() | [[Enum][Enum]] | - | +| SMB_VFS_GETLOCK() | [[fsp][fsp]] | - | +| SMB_VFS_GET_NT_ACL_AT() | [[Path][Path]] | - | +| SMB_VFS_GET_QUOTA() | [[Special][Special]] | - | +| SMB_VFS_GET_REAL_FILENAME() | [[P2px][P2px]] | - | +| SMB_VFS_GET_SHADOW_COPY_DATA() | [[fsp][fsp]] | - | +| SMB_VFS_GETWD() | [[Special][Special]] | - | +| SMB_VFS_GETXATTR() | [[Path][Path]] | - | +| SMB_VFS_GETXATTRAT_RECV() | [[Enum][Enum]] | - | +| SMB_VFS_GETXATTRAT_SEND() | [[Enum][Enum]] | - | +| SMB_VFS_FILESYSTEM_SHAREMODE() | [[fsp][fsp]] | - | +| SMB_VFS_LCHOWN() | [[Path][Path]] | Todo | +| SMB_VFS_LINKAT() | [[NsC][NsC]] | - | +| SMB_VFS_LINUX_SETLEASE() | [[fsp][fsp]] | - | +| SMB_VFS_LISTXATTR() | [[Path][Path]] | - | +| SMB_VFS_LOCK() | [[fsp][fsp]] | - | +| SMB_VFS_LSEEK() | [[fsp][fsp]] | - | +| SMB_VFS_LSTAT() | [[Path][Path]] | Todo | +| SMB_VFS_MKDIRAT() | [[NsC][NsC]] | - | +| SMB_VFS_MKNODAT() | [[NsC][NsC]] | - | +| SMB_VFS_NTIMES() | [[Path][Path]] | - | +| SMB_VFS_OFFLOAD_READ_RECV() | [[fsp][fsp]] | - | +| SMB_VFS_OFFLOAD_READ_SEND() | [[fsp][fsp]] | - | +| SMB_VFS_OFFLOAD_WRITE_RECV() | [[fsp][fsp]] | - | +| SMB_VFS_OFFLOAD_WRITE_SEND() | [[fsp][fsp]] | - | +| SMB_VFS_OPENAT() | [[NsC][NsC]] | - | +| SMB_VFS_PREAD() | [[fsp][fsp]] | - | +| SMB_VFS_PREAD_SEND() | [[fsp][fsp]] | - | +| SMB_VFS_PWRITE() | [[fsp][fsp]] | - | +| SMB_VFS_PWRITE_SEND() | [[fsp][fsp]] | - | +| SMB_VFS_READ_DFS_PATHAT() | [[Symlink][Symlink]] | - | +| SMB_VFS_READDIR() | [[fsp][fsp]] | - | +| SMB_VFS_READDIR_ATTR() | [[Path][Path]] | - | +| SMB_VFS_READLINKAT() | [[Symlink][Symlink]] | - | +| SMB_VFS_REALPATH() | [[P2px][P2px]] | - | +| SMB_VFS_RECVFILE() | [[fsp][fsp]] | - | +| SMB_VFS_REMOVEXATTR() | [[Path][Path]] | - | +| SMB_VFS_RENAMEAT() | [[Path][Path]] | ---- | +| SMB_VFS_REWINDDIR() | [[fsp][fsp]] | - | +| SMB_VFS_SENDFILE() | [[fsp][fsp]] | - | +| SMB_VFS_SET_COMPRESSION() | [[fsp][fsp]] | - | +| SMB_VFS_SET_DOS_ATTRIBUTES() | [[Path][Path]] | - | +| SMB_VFS_SET_QUOTA() | [[Special][Special]] | - | +| SMB_VFS_SETXATTR() | [[Path][Path]] | - | +| SMB_VFS_SNAP_CHECK_PATH() | [[Disk][Disk]] | - | +| SMB_VFS_SNAP_CREATE() | [[Disk][Disk]] | - | +| SMB_VFS_SNAP_DELETE() | [[Disk][Disk]] | - | +| SMB_VFS_STAT() | [[Path][Path]] | Todo | +| SMB_VFS_STATVFS() | [[Disk][Disk]] | - | +| SMB_VFS_STREAMINFO() | [[Path][Path]] | - | +| SMB_VFS_STRICT_LOCK_CHECK() | [[fsp][fsp]] | - | +| SMB_VFS_SYMLINKAT() | [[NsC][NsC]] | - | +| SMB_VFS_SYS_ACL_BLOB_GET_FD() | [[xpathref][xpathref]] | - | +| SMB_VFS_SYS_ACL_BLOB_GET_FILE() | [[Path][Path]] | - | +| SMB_VFS_SYS_ACL_DELETE_DEF_FILE() | [[Path][Path]] | - | +| SMB_VFS_SYS_ACL_GET_FD() | [[xpathref][xpathref]] | - | +| SMB_VFS_SYS_ACL_GET_FILE() | [[Path][Path]] | - | +| SMB_VFS_SYS_ACL_SET_FD() | [[xpathref][xpathref]] | - | +| SMB_VFS_TRANSLATE_NAME() | [[P2px][P2px]] | - | +| SMB_VFS_UNLINKAT() | [[NsC][NsC]] | - | +|-----------------------------------+----------+--------| + +*** New VFS Functions +#+ATTR_HTML: :border 1 :rules all :frame border +| VFS Function | Group | Status | +|---------------------------------+----------+--------| +| SMB_VFS_SYS_ACL_DELETE_DEF_FD() | [[xpathref][xpathref]] | - | +| SMB_VFS_FNTIMENS() | [[fsp][fsp]] | - | +|---------------------------------+----------+--------| + +** VFS functions by category +*** Disk operations <> + - SMB_VFS_CONNECT() + - SMB_VFS_DISCONNECT() + - SMB_VFS_DISK_FREE() + - SMB_VFS_FS_CAPABILITIES() + - SMB_VFS_GET_DFS_REFERRALS() + - SMB_VFS_SNAP_CHECK_PATH() + - SMB_VFS_SNAP_CREATE() + - SMB_VFS_SNAP_DELETE() + - SMB_VFS_STATVFS() + + No changes needed. +*** Handle based VFS functions <> + - SMB_VFS_AIO_FORCE() + - SMB_VFS_BRL_LOCK_WINDOWS() + - SMB_VFS_BRL_UNLOCK_WINDOWS() + - SMB_VFS_CLOSE() + - SMB_VFS_CLOSEDIR() + - SMB_VFS_DURABLE_COOKIE() + - SMB_VFS_DURABLE_DISCONNECT() + - SMB_VFS_FALLOCATE() + - SMB_VFS_FCHMOD() + - SMB_VFS_FCHOWN() + - SMB_VFS_FCNTL() + - SMB_VFS_FDOPENDIR() + - SMB_VFS_FGET_DOS_ATTRIBUTES() + - SMB_VFS_FGET_NT_ACL() + - SMB_VFS_FSCTL() + - SMB_VFS_FSET_DOS_ATTRIBUTES() + - SMB_VFS_FSET_NT_ACL() + - SMB_VFS_FSTAT() + - SMB_VFS_FSYNC() + - SMB_VFS_FSYNC_SEND() + - SMB_VFS_FTRUNCATE() + - SMB_VFS_GETLOCK() + - SMB_VFS_GET_ALLOC_SIZE() + - SMB_VFS_GET_SHADOW_COPY_DATA() + - SMB_VFS_FILESYSTEM_SHAREMODE() + - SMB_VFS_LINUX_SETLEASE() + - SMB_VFS_LOCK() + - SMB_VFS_LSEEK() + - SMB_VFS_OFFLOAD_READ_SEND() + - SMB_VFS_OFFLOAD_WRITE_SEND() + - SMB_VFS_PREAD() + - SMB_VFS_PREAD_SEND() + - SMB_VFS_PWRITE() + - SMB_VFS_PWRITE_SEND() + - SMB_VFS_READDIR() + - SMB_VFS_RECVFILE() + - SMB_VFS_REWINDDIR() + - SMB_VFS_SENDFILE() + - SMB_VFS_SET_COMPRESSION() + - SMB_VFS_STRICT_LOCK_CHECK() + + If an fsp is provided by the SMB layer we use that, otherwise we use the + pathref fsp =smb_fname->fsp= provided by =filename_convert()=. +*** Namespace changing VFS functions <> + + - SMB_VFS_CREATE_FILE() + + All intermediate VFS calls within =SMB_VFS_CREATE_FILE()= will be based on + =smb_fname->fsp= if the requested path exists. When creating a file we rely on + =non_widelink_open()= which doesn't depend on a dirfsp. + + - SMB_VFS_MKDIRAT() + + Needs a real dirfsp (done). + + - SMB_VFS_OPENAT() + + Is only called from within =non_widelink_open()= with a dirfsp equivalent of + =AT_FDCWD= and so doesn't need a real dirfsp. + + The following operations need a real dirfsp: + + - SMB_VFS_LINKAT() + - SMB_VFS_MKNODAT() + - SMB_VFS_RENAMEAT() + - SMB_VFS_SYMLINKAT() + - SMB_VFS_UNLINKAT() + + Callers use =openat_pathref_fsp()= to open a fsp on the parent directory. + +*** Path based VFS functions <> + All path based VFS functions will be replaced by handle based variants using the + =smb_fname->fsp= provided by =filename_convert()=. + + - SMB_VFS_CHDIR() + - SMB_VFS_CHFLAGS() + - SMB_VFS_CHMOD() + - SMB_VFS_DURABLE_RECONNECT() + - SMB_VFS_GETXATTR() + - SMB_VFS_GET_COMPRESSION() + - SMB_VFS_GET_DOS_ATTRIBUTES() + - SMB_VFS_GET_NT_ACL_AT() + - SMB_VFS_LCHOWN() + - SMB_VFS_LISTXATTR() + - SMB_VFS_LSTAT() + - SMB_VFS_NTIMES() + - SMB_VFS_REMOVEXATTR() + - SMB_VFS_SETXATTR() + - SMB_VFS_SET_DOS_ATTRIBUTES() + - SMB_VFS_STAT() + - SMB_VFS_STREAMINFO() + - SMB_VFS_SYS_ACL_BLOB_GET_FILE() + - SMB_VFS_SYS_ACL_DELETE_DEF_FILE() + - SMB_VFS_SYS_ACL_GET_FILE() + - SMB_VFS_SYS_ACL_SET_FILE() + + Replace with corresponding handle based VFS calls. +*** AT VFS functions that can't be based on handles <> + + - SMB_VFS_CREATE_DFS_PATHAT() + - SMB_VFS_READ_DFS_PATHAT() + - SMB_VFS_READLINKAT() + + As the DFS link implementation is based on symlinks, we have to use *AT based + functions with real dirfsps. + +*** AT VFS functions needed for directory enumeration <> + - SMB_VFS_GET_DOS_ATTRIBUTES_SEND() + - SMB_VFS_GETXATTRAT_SEND() +*** Handle based VFS functions not allowed on O_PATH opened handles <> + - SMB_VFS_FGETXATTR() + - SMB_VFS_FLISTXATTR() + - SMB_VFS_FREMOVEXATTR() + - SMB_VFS_FSETXATTR() + - SMB_VFS_SYS_ACL_BLOB_GET_FD() + - SMB_VFS_SYS_ACL_GET_FD() + - SMB_VFS_SYS_ACL_DELETE_DEF_FD() (NEW) + - SMB_VFS_SYS_ACL_SET_FD() + + Based upon securely opening a full fd based on =/proc/self/fd/%d= as in the case + of xattrs, pathref handles can't be used for xattr IO, and in the case of ACLs + pathref handles can't be used to access default ACEs. +*** Pure path to path translation <> + - SMB_VFS_CONNECTPATH() + - SMB_VFS_GET_REAL_FILENAME() + - SMB_VFS_REALPATH() + - SMB_VFS_TRANSLATE_NAME() + + No changes needed. +*** Special cases <> + - SMB_VFS_FILE_ID_CREATE() + - SMB_VFS_FS_FILE_ID() + - SMB_VFS_GET_QUOTA() + - SMB_VFS_GETWD() + - SMB_VFS_SET_QUOTA() + + No changes needed. + + - SMB_VFS_AUDIT_FILE() + + This is currently unused. + +[fn:VFS_API] ~grep 'SMB_VFS_*' source3/include/vfs_macros.h | grep -v NEXT_ | sed 's|.*\(SMB_VFS_.*\)(.*|\1()|' | sort~ diff --git a/source3/modules/The_New_VFS.txt b/source3/modules/The_New_VFS.txt new file mode 100644 index 0000000..af50723 --- /dev/null +++ b/source3/modules/The_New_VFS.txt @@ -0,0 +1,607 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + THE NEW SAMBA VFS + + Ralph Böhme, SerNet, Samba Team + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + 2021-01-14 + + +Table of Contents +───────────────── + +1. The new VFS +.. 1. Summary +.. 2. Samba and O_PATH +..... 1. Background +..... 2. Usecases for O_PATH in Samba +..... 3. When to open with O_PATH +..... 4. Fallback on systems without O_PATH support +..... 5. When to use fsp_get_io_fd() or fsp_get_pathref_fd() +2. VFS status quo and remaining work +.. 1. VFS Functions Tables [2] +..... 1. Existing VFS Functions +..... 2. New VFS Functions +.. 2. VFS functions by category +..... 1. Disk operations +..... 2. Handle based VFS functions +..... 3. Namespace changing VFS functions +..... 4. Path based VFS functions +..... 5. AT VFS functions that can't be based on handles +..... 6. AT VFS functions needed for directory enumeration +..... 7. Handle based VFS functions not allowed on O_PATH opened handles +..... 8. Pure path to path translation +..... 9. Special cases + + +1 The new VFS +═════════════ + +1.1 Summary +─────────── + + The effort to modernize Samba's VFS interface has reached a major + milestone with the next release Samba 4.14. + + Starting with version 4.14 Samba provides core infrastructure code that + allows basing all access to the server's filesystem on file handles and + not on paths. An example of this is using `fstat()' instead of `stat()', + or `SMB_VFS_FSTAT()' instead of `SMB_VFS_STAT()' in Samba parlance. + + Historically Samba's fileserver code had to deal a lot with processing + path based SMB requests. While the SMB protocol itself has been + streamlined to be purely handle based starting with SMB2, large parts of + infrastructure code remains in place that will "degrade" handle based SMB2 + requests to path based filesystem access. + + In order to fully leverage the handle based nature of the SMB2 protocol we + came up with a straight forward way to convert this infrastructure code. + + At the core, we introduced a helper function that opens a file handle that + only serves as a path reference and hence can not be used for any sort of + access to file data. + + Samba's internal file handle structure is of type `struct files_struct' + and all variable pointing to objects of such type are typically called + `fsp'. Until very recently the only function that would open such a file + handle and return an fsp was `SMB_VFS_CREATE_FILE()'. + + Internally `SMB_VFS_CREATE_FILE()' consisted of processing through Samba's + VFS open function to open the low level file and then going through + Samba's Windows NTFS emulation code. + + The key point of the new helper function which is called + `openat_pathref_fsp()' is that it skips the NTFS emulation + logic. Additionally, the handle is restricted internally to be only usable + as a path reference but not for any sort of IO. On Linux this is achieved + by using the `O_PATH' `open()' flag, on systems without `O_PATH' support + other mechanisms are used described in more detail below. + + Path processing in Samba typically means processing client supplied paths + by Samba's core path processing function `filename_convert()' which returns + a pointer to an object of type `struct smb_filename'. Pointers to such + objects are then passed around, often passing many layers of code. + + By attaching an `fsp' file handle returned from `openat_pathref_fsp()' to + all `struct smb_filename' objects returned from `filename_convert()', the + whole infrastructure code has immediate access to a file handle and so the + large infrastructure codebase can be converted to use handle based VFS + functions whenever VFS access is done in a piecemeal fashion. + + +1.2 Samba and O_PATH +──────────────────── + +1.2.1 Background +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + On Linux the `O_PATH' flag to `open()' can be used to open a filehandle on + a file or directory with interesting properties: [1] + + • the file-handle indicates a location in the filesystem tree, + + • no permission checks are done by the kernel on the filesystem object and + + • only operations that act purely at the file descriptor level are + allowed. + + The file itself is not opened, and other file operations (e.g., `read(2)', + `write(2)', `fchmod(2)', `fchown(2)', `fgetxattr(2)', `ioctl(2)', + `mmap(2)') fail with the error `EBADF'. + + The following subset of operations that is relevant to Samba is allowed: + + • `close(2)', + + • `fchdir(2)', if the file descriptor refers to a directory, + + • `fstat(2)', + + • `fstatfs(2)' and + + • passing the file descriptor as the dirfd argument of `openat()' and the + other "*at()" system calls. This includes `linkat(2)' with AT_EMPTY_PATH + (or via procfs using AT_SYMLINK_FOLLOW) even if the file is not a + directory. + + Opening a file or directory with the `O_PATH' flag requires no permissions + on the object itself (but does require execute permission on the + directories in the path prefix). By contrast, obtaining a reference to a + filesystem object by opening it with the `O_RDONLY' flag requires that the + caller have read permission on the object, even when the subsequent + operation (e.g., `fchdir(2)', `fstat(2)') does not require read permis‐ + sion on the object. + + If for example Samba receives an SMB request to open a file requesting + `SEC_FILE_READ_ATTRIBUTE' access rights because the client wants to read + the file's metadata from the handle, Samba will have to call `open()' with + at least `O_RDONLY' access rights. + + +1.2.2 Usecases for O_PATH in Samba +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The `O_PATH' flag is currently not used in Samba. By leveraging this Linux + specific flags we can avoid permission mismatches as described above. + + Additionally `O_PATH' allows basing all filesystem accesses done by the + fileserver on handle based syscalls by opening all client pathnames with + `O_PATH' and consistently using for example `fstat()' instead of `stat()' + throughout the codebase. + + Subsequent parts of this document will call such file-handles opened with + O_PATH *path referencing file-handles* or *pathref*s for short. + + +1.2.3 When to open with O_PATH +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + In Samba the decision whether to call POSIX `open()' on a client pathname + or whether to leave the low-level handle at -1 (what we call a stat-open) + is based on the client requested SMB access mask. + + The set of access rights that trigger an `open()' includes + `READ_CONTROL_ACCESS'. As a result, the open() will be done with at least + `O_RDONLY'. If the filesystem supports NT style ACLs natively (like GPFS + or ZFS), the filesystem may grant the user requested right + `READ_CONTROL_ACCESS', but it may not grant `READ_DATA' (`O_RDONLY'). + + Currently the full set of access rights that trigger opening a file is: + + • FILE_READ_DATA + • FILE_WRITE_DATA + • FILE_APPEND_DATA + • FILE_EXECUTE + • WRITE_DAC_ACCESS + • WRITE_OWNER_ACCESS + • SEC_FLAG_SYSTEM_SECURITY + • READ_CONTROL_ACCESS + + In the future we can remove the following rights from the list on systems + that support O_PATH: + + • WRITE_DAC_ACCESS + • WRITE_OWNER_ACCESS + • SEC_FLAG_SYSTEM_SECURITY + • READ_CONTROL_ACCESS + + +1.2.4 Fallback on systems without O_PATH support +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The code of higher level file-handle consumers must be kept simple and + streamlined, avoiding special casing the handling of the file-handles + opened with or without `O_PATH'. To achieve this, a fallback that allows + opening a file-handle with the same higher level semantics even if the + system doesn't support `O_PATH' is needed. + + The way this is implemented on such systems is impersonating the root user + for the `open()' syscall. In order to avoid privilege escalations security + issues, we must carefully control the use these file-handles. + + The low level filehandle is stored in a public struct `struct file_handle' + that is part of the widely used `struct files_struct'. Consumers used to + simply access the fd directly by dereferencing pointers to `struct + files_struct'. + + In order to guard access to such file-handles we do two things: + + • tag the pathref file-handles and + + • control access to the file-handle by making the structure `struct + file_handle' private, only allowing access with accessor functions + that implement a security boundary. + + In order to avoid bypassing restrictive permissions on intermediate + directories of a client path, the root user is only impersonated after + changing directory to the parent directory of the client requested + pathname. + + Two functions can then be used to fetch the low-level system file-handle + from a `struct files_struct': + + • `fsp_get_io_fd(fsp)': enforces fsp is NOT a pathref file-handle and + + • `fsp_get_pathref_fd(fsp)': allows fsp to be either a pathref file-handle + or a traditional POSIX file-handle opened with O_RDONLY or any other + POSIX open flag. + + Note that the name `fsp_get_pathref_fd()' may sound confusing at first + given that the fsp can be either a pathref fsp or a "normal/full" fsp, but + as any full file-handle can be used for IO and as path reference, the name + correctly reflects the intended usage of the caller. + + +1.2.5 When to use fsp_get_io_fd() or fsp_get_pathref_fd() +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The general guideline is: + + • if you do something like `fstat(fd)', use `fsp_get_pathref_fd()', + + • if you do something like `*at(dirfd, ...)', use `fsp_get_pathref_fd()', + + • if you want to print the fd for example in `DEBUG' messages, use + `fsp_get_pathref_fd()', + + • if you want to call `close(fd)', use `fsp_get_pathref_fd()', + + • if you're doing a logical comparison of fd values, use + `fsp_get_pathref_fd()'. + + In any other case use `fsp_get_io_fd()'. + + +2 VFS status quo and remaining work +═══════════════════════════════════ + +2.1 VFS Functions Tables [2] +──────────────────────────── + +2.1.1 Existing VFS Functions +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + VFS Function Group Status + ─────────────────────────────────────────────────────── + SMB_VFS_AIO_FORCE() [fsp] - + SMB_VFS_AUDIT_FILE() [Special] - + SMB_VFS_BRL_LOCK_WINDOWS() [fsp] - + SMB_VFS_BRL_UNLOCK_WINDOWS() [fsp] - + SMB_VFS_CHDIR() [Path] Todo + SMB_VFS_CHFLAGS() [Path] - + SMB_VFS_CHMOD() [Path] - + SMB_VFS_CLOSE() [fsp] - + SMB_VFS_CLOSEDIR() [fsp] - + SMB_VFS_CONNECT() [Disk] - + SMB_VFS_CONNECTPATH() [P2px] - + SMB_VFS_CREATE_DFS_PATHAT() [NsC] - + SMB_VFS_CREATE_FILE() [NsC] - + SMB_VFS_DISCONNECT() [Disk] - + SMB_VFS_DISK_FREE() [Disk] - + SMB_VFS_DURABLE_COOKIE() [fsp] - + SMB_VFS_DURABLE_DISCONNECT() [fsp] - + SMB_VFS_DURABLE_RECONNECT() [fsp] - + SMB_VFS_FALLOCATE() [fsp] - + SMB_VFS_FCHMOD() [fsp] - + SMB_VFS_FCHOWN() [fsp] - + SMB_VFS_FCNTL() [fsp] - + SMB_VFS_FDOPENDIR() [fsp] - + SMB_VFS_FGET_COMPRESSION() [fsp] - + SMB_VFS_FGET_DOS_ATTRIBUTES() [fsp] - + SMB_VFS_FGET_NT_ACL() [fsp] - + SMB_VFS_FGETXATTR() [xpathref] - + SMB_VFS_FILE_ID_CREATE() [Special] - + SMB_VFS_FLISTXATTR() [xpathref] - + SMB_VFS_FREMOVEXATTR() [xpathref] - + SMB_VFS_FS_CAPABILITIES() [Disk] - + SMB_VFS_FSCTL() [fsp] - + SMB_VFS_FSET_DOS_ATTRIBUTES() [fsp] - + SMB_VFS_FSET_NT_ACL() [fsp] - + SMB_VFS_FSETXATTR() [xpathref] - + SMB_VFS_FS_FILE_ID() [Special] - + SMB_VFS_FSTAT() [fsp] - + SMB_VFS_FSYNC() [fsp] - + SMB_VFS_FSYNC_SEND() [fsp] - + SMB_VFS_FTRUNCATE() [fsp] - + SMB_VFS_GET_ALLOC_SIZE() [fsp] - + SMB_VFS_GET_DFS_REFERRALS() [Disk] - + SMB_VFS_GET_DOS_ATTRIBUTES_RECV() [Enum] - + SMB_VFS_GET_DOS_ATTRIBUTES_SEND() [Enum] - + SMB_VFS_GETLOCK() [fsp] - + SMB_VFS_GET_NT_ACL_AT() [Path] - + SMB_VFS_GET_QUOTA() [Special] - + SMB_VFS_GET_REAL_FILENAME() [P2px] - + SMB_VFS_GET_SHADOW_COPY_DATA() [fsp] - + SMB_VFS_GETWD() [Special] - + SMB_VFS_GETXATTR() [Path] - + SMB_VFS_GETXATTRAT_RECV() [Enum] - + SMB_VFS_GETXATTRAT_SEND() [Enum] - + SMB_VFS_FILESYSTEM_SHAREMODE() [fsp] - + SMB_VFS_LCHOWN() [Path] Todo + SMB_VFS_LINKAT() [NsC] - + SMB_VFS_LINUX_SETLEASE() [fsp] - + SMB_VFS_LISTXATTR() [Path] - + SMB_VFS_LOCK() [fsp] - + SMB_VFS_LSEEK() [fsp] - + SMB_VFS_LSTAT() [Path] Todo + SMB_VFS_MKDIRAT() [NsC] - + SMB_VFS_MKNODAT() [NsC] - + SMB_VFS_NTIMES() [Path] - + SMB_VFS_OFFLOAD_READ_RECV() [fsp] - + SMB_VFS_OFFLOAD_READ_SEND() [fsp] - + SMB_VFS_OFFLOAD_WRITE_RECV() [fsp] - + SMB_VFS_OFFLOAD_WRITE_SEND() [fsp] - + SMB_VFS_OPENAT() [NsC] - + SMB_VFS_PREAD() [fsp] - + SMB_VFS_PREAD_SEND() [fsp] - + SMB_VFS_PWRITE() [fsp] - + SMB_VFS_PWRITE_SEND() [fsp] - + SMB_VFS_READ_DFS_PATHAT() [Symlink] - + SMB_VFS_READDIR() [fsp] - + SMB_VFS_READDIR_ATTR() [Path] - + SMB_VFS_READLINKAT() [Symlink] - + SMB_VFS_REALPATH() [P2px] - + SMB_VFS_RECVFILE() [fsp] - + SMB_VFS_REMOVEXATTR() [Path] - + SMB_VFS_RENAMEAT() [Path] - + SMB_VFS_REWINDDIR() [fsp] - + SMB_VFS_SENDFILE() [fsp] - + SMB_VFS_SET_COMPRESSION() [fsp] - + SMB_VFS_SET_DOS_ATTRIBUTES() [Path] - + SMB_VFS_SET_QUOTA() [Special] - + SMB_VFS_SETXATTR() [Path] - + SMB_VFS_SNAP_CHECK_PATH() [Disk] - + SMB_VFS_SNAP_CREATE() [Disk] - + SMB_VFS_SNAP_DELETE() [Disk] - + SMB_VFS_STAT() [Path] Todo + SMB_VFS_STATVFS() [Disk] - + SMB_VFS_STREAMINFO() [Path] - + SMB_VFS_STRICT_LOCK_CHECK() [fsp] - + SMB_VFS_SYMLINKAT() [NsC] - + SMB_VFS_SYS_ACL_BLOB_GET_FD() [xpathref] - + SMB_VFS_SYS_ACL_BLOB_GET_FILE() [Path] - + SMB_VFS_SYS_ACL_DELETE_DEF_FILE() [Path] - + SMB_VFS_SYS_ACL_GET_FD() [xpathref] - + SMB_VFS_SYS_ACL_GET_FILE() [Path] - + SMB_VFS_SYS_ACL_SET_FD() [xpathref] - + SMB_VFS_TRANSLATE_NAME() [P2px] - + SMB_VFS_UNLINKAT() [NsC] - + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +[fsp] See section 2.2.2 + +[Special] See section 2.2.9 + +[Path] See section 2.2.4 + +[Disk] See section 2.2.1 + +[P2px] See section 2.2.8 + +[NsC] See section 2.2.3 + +[xpathref] See section 2.2.7 + +[Enum] See section 2.2.6 + +[Symlink] See section 2.2.5 + + +2.1.2 New VFS Functions +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + VFS Function Group Status + ───────────────────────────────────────────────────── + SMB_VFS_SYS_ACL_DELETE_DEF_FD() [xpathref] - + SMB_VFS_FNTIMENS() [fsp] - + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +[xpathref] See section 2.2.7 + +[Enum] See section 2.2.6 + +[fsp] See section 2.2.2 + + +2.2 VFS functions by category +───────────────────────────── + +2.2.1 Disk operations +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_CONNECT() + • SMB_VFS_DISCONNECT() + • SMB_VFS_DISK_FREE() + • SMB_VFS_FS_CAPABILITIES() + • SMB_VFS_GET_DFS_REFERRALS() + • SMB_VFS_SNAP_CHECK_PATH() + • SMB_VFS_SNAP_CREATE() + • SMB_VFS_SNAP_DELETE() + • SMB_VFS_STATVFS() + + No changes needed. + + +2.2.2 Handle based VFS functions +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_AIO_FORCE() + • SMB_VFS_BRL_LOCK_WINDOWS() + • SMB_VFS_BRL_UNLOCK_WINDOWS() + • SMB_VFS_CLOSE() + • SMB_VFS_CLOSEDIR() + • SMB_VFS_DURABLE_COOKIE() + • SMB_VFS_DURABLE_DISCONNECT() + • SMB_VFS_FALLOCATE() + • SMB_VFS_FCHMOD() + • SMB_VFS_FCHOWN() + • SMB_VFS_FCNTL() + • SMB_VFS_FDOPENDIR() + • SMB_VFS_FGET_DOS_ATTRIBUTES() + • SMB_VFS_FGET_NT_ACL() + • SMB_VFS_FSCTL() + • SMB_VFS_FSET_DOS_ATTRIBUTES() + • SMB_VFS_FSET_NT_ACL() + • SMB_VFS_FSTAT() + • SMB_VFS_FSYNC() + • SMB_VFS_FSYNC_SEND() + • SMB_VFS_FTRUNCATE() + • SMB_VFS_GETLOCK() + • SMB_VFS_GET_ALLOC_SIZE() + • SMB_VFS_GET_SHADOW_COPY_DATA() + • SMB_VFS_FILESYSTEM_SHAREMODE() + • SMB_VFS_LINUX_SETLEASE() + • SMB_VFS_LOCK() + • SMB_VFS_LSEEK() + • SMB_VFS_OFFLOAD_READ_SEND() + • SMB_VFS_OFFLOAD_WRITE_SEND() + • SMB_VFS_PREAD() + • SMB_VFS_PREAD_SEND() + • SMB_VFS_PWRITE() + • SMB_VFS_PWRITE_SEND() + • SMB_VFS_READDIR() + • SMB_VFS_RECVFILE() + • SMB_VFS_REWINDDIR() + • SMB_VFS_SENDFILE() + • SMB_VFS_SET_COMPRESSION() + • SMB_VFS_STRICT_LOCK_CHECK() + + If an fsp is provided by the SMB layer we use that, otherwise we use the + pathref fsp `smb_fname->fsp' provided by `filename_convert()'. + + +2.2.3 Namespace changing VFS functions +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_CREATE_FILE() + + All intermediate VFS calls within `SMB_VFS_CREATE_FILE()' will be based on + `smb_fname->fsp' if the requested path exists. When creating a file we + rely on `non_widelink_open()' which doesn't depend on a dirfsp. + + • SMB_VFS_MKDIRAT() + + Needs a real dirfsp (done). + + • SMB_VFS_OPENAT() + + Is only called from within `non_widelink_open()' with a dirfsp equivalent + of `AT_FDCWD' and so doesn't need a real dirfsp. + + The following operations need a real dirfsp: + + • SMB_VFS_LINKAT() + • SMB_VFS_MKNODAT() + • SMB_VFS_RENAMEAT() + • SMB_VFS_SYMLINKAT() + • SMB_VFS_UNLINKAT() + + Callers use `openat_pathref_fsp()' to open a fsp on the parent directory. + + +2.2.4 Path based VFS functions +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + All path based VFS functions will be replaced by handle based variants + using the `smb_fname->fsp' provided by `filename_convert()'. + + • SMB_VFS_CHDIR() + • SMB_VFS_CHFLAGS() + • SMB_VFS_CHMOD() + • SMB_VFS_DURABLE_RECONNECT() + • SMB_VFS_GETXATTR() + • SMB_VFS_GET_COMPRESSION() + • SMB_VFS_GET_DOS_ATTRIBUTES() + • SMB_VFS_GET_NT_ACL_AT() + • SMB_VFS_LCHOWN() + • SMB_VFS_LISTXATTR() + • SMB_VFS_LSTAT() + • SMB_VFS_NTIMES() + • SMB_VFS_REMOVEXATTR() + • SMB_VFS_SETXATTR() + • SMB_VFS_SET_DOS_ATTRIBUTES() + • SMB_VFS_STAT() + • SMB_VFS_STREAMINFO() + • SMB_VFS_SYS_ACL_BLOB_GET_FILE() + • SMB_VFS_SYS_ACL_DELETE_DEF_FILE() + • SMB_VFS_SYS_ACL_GET_FILE() + • SMB_VFS_SYS_ACL_SET_FILE() + + Replace with corresponding handle based VFS calls. + + +2.2.5 AT VFS functions that can't be based on handles +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_CREATE_DFS_PATHAT() + • SMB_VFS_READ_DFS_PATHAT() + • SMB_VFS_READLINKAT() + + As the DFS link implementation is based on symlinks, we have to use *AT + based functions with real dirfsps. + + +2.2.6 AT VFS functions needed for directory enumeration +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_GET_DOS_ATTRIBUTES_SEND() + • SMB_VFS_GETXATTRAT_SEND() + + +2.2.7 Handle based VFS functions not allowed on O_PATH opened handles +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_FGETXATTR() + • SMB_VFS_FLISTXATTR() + • SMB_VFS_FREMOVEXATTR() + • SMB_VFS_FSETXATTR() + • SMB_VFS_SYS_ACL_BLOB_GET_FD() + • SMB_VFS_SYS_ACL_GET_FD() + • SMB_VFS_SYS_ACL_DELETE_DEF_FD() (NEW) + • SMB_VFS_SYS_ACL_SET_FD() + + Based upon securely opening a full fd based on `/proc/self/fd/%d' as in + the case of xattrs, pathref handles can't be used for xattr IO, and in the + case of ACLs pathref handles can't be used to access default ACEs. + + +2.2.8 Pure path to path translation +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_CONNECTPATH() + • SMB_VFS_GET_REAL_FILENAME() + • SMB_VFS_REALPATH() + • SMB_VFS_TRANSLATE_NAME() + + No changes needed. + + +2.2.9 Special cases +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + • SMB_VFS_FILE_ID_CREATE() + • SMB_VFS_FS_FILE_ID() + • SMB_VFS_GET_QUOTA() + • SMB_VFS_GETWD() + • SMB_VFS_SET_QUOTA() + + No changes needed. + + • SMB_VFS_AUDIT_FILE() + + This is currently unused. + + + +Footnotes +───────── + +[1] parts of the following sections copied from man open(2) + +[2] `grep 'SMB_VFS_*' source3/include/vfs_macros.h | grep -v NEXT_ | sed +'s|.*\(SMB_VFS_.*\)(.*|\1()|' | sort' diff --git a/source3/modules/getdate.c b/source3/modules/getdate.c new file mode 100644 index 0000000..763ff93 --- /dev/null +++ b/source3/modules/getdate.c @@ -0,0 +1,2742 @@ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + + 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 . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.0.4" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + + + +/* Copy the first part of user declarations. */ +#line 1 "source3/modules/getdate.y" /* yacc.c:339 */ + +/* Parse a string into an internal time stamp. + Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc. + + 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 2, 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 . */ + +/* Originally written by Steven M. Bellovin while + at the University of North Carolina at Chapel Hill. Later tweaked by + a couple of people on Usenet. Completely overhauled by Rich $alz + and Jim Berets in August, 1990. + + Modified by Paul Eggert in August 1999 to do + the right thing about local DST. Unlike previous versions, this + version is reentrant. */ + +#ifdef HAVE_CONFIG_H +# include +# ifdef HAVE_ALLOCA_H +# include +# endif +#endif + +/* Since the code of getdate.y is not included in the Emacs executable + itself, there is no need to #define static in this file. Even if + the code were included in the Emacs executable, it probably + wouldn't do any harm to #undef it here; this will only cause + problems if we try to write to a static variable, which I don't + think this code needs to do. */ +#ifdef emacs +# undef static +#endif + +#include +#include + +#ifdef HAVE_STDLIB_H +# include /* for `free'; used by Bison 1.27 */ +#endif + +#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) isascii (c) +#endif + +#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) +#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) + +/* ISDIGIT differs from ISDIGIT_LOCALE, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char. + - It's guaranteed to evaluate its argument exactly once. + - It's typically faster. + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + ISDIGIT_LOCALE unless it's important to use the locale's definition + of `digit' even when the host does not conform to POSIX. */ +#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) + +#if STDC_HEADERS || HAVE_STRING_H +# include +#endif + +#ifndef HAVE___ATTRIBUTE__ +# define __attribute__(x) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#define EPOCH_YEAR 1970 +#define TM_YEAR_BASE 1900 + +#define HOUR(x) ((x) * 60) + +/* An integer value, and the number of digits in its textual + representation. */ +typedef struct +{ + int value; + int digits; +} textint; + +/* An entry in the lexical lookup table. */ +typedef struct +{ + char const *name; + int type; + int value; +} table; + +/* Meridian: am, pm, or 24-hour style. */ +enum { MERam, MERpm, MER24 }; + +/* Information passed to and from the parser. */ +struct parser_control +{ + /* The input string remaining to be parsed. */ + const char *input; + + /* N, if this is the Nth Tuesday. */ + int day_ordinal; + + /* Day of week; Sunday is 0. */ + int day_number; + + /* tm_isdst flag for the local zone. */ + int local_isdst; + + /* Time zone, in minutes east of UTC. */ + int time_zone; + + /* Style used for time. */ + int meridian; + + /* Gregorian year, month, day, hour, minutes, and seconds. */ + textint year; + int month; + int day; + int hour; + int minutes; + int seconds; + + /* Relative year, month, day, hour, minutes, and seconds. */ + int rel_year; + int rel_month; + int rel_day; + int rel_hour; + int rel_minutes; + int rel_seconds; + + /* Counts of nonterminals of various flavors parsed so far. */ + int dates_seen; + int days_seen; + int local_zones_seen; + int rels_seen; + int times_seen; + int zones_seen; + + /* Table of local time zone abbreviations, terminated by a null entry. */ + table local_time_zone_table[3]; +}; + + +#line 223 "source3/modules/getdate.c" /* yacc.c:339 */ + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + + +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + tAGO = 258, + tDST = 259, + tDAY = 260, + tDAY_UNIT = 261, + tDAYZONE = 262, + tHOUR_UNIT = 263, + tLOCAL_ZONE = 264, + tMERIDIAN = 265, + tMINUTE_UNIT = 266, + tMONTH = 267, + tMONTH_UNIT = 268, + tSEC_UNIT = 269, + tYEAR_UNIT = 270, + tZONE = 271, + tSNUMBER = 272, + tUNUMBER = 273 + }; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +union YYSTYPE +{ +#line 168 "source3/modules/getdate.y" /* yacc.c:355 */ + + int intval; + textint textintval; + +#line 284 "source3/modules/getdate.c" /* yacc.c:355 */ +}; + +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + + +int yyparse (struct parser_control *pc); + + + +/* Copy the second part of user declarations. */ +#line 173 "source3/modules/getdate.y" /* yacc.c:358 */ + + +static int yyerror(struct parser_control *, const char *); +static int yylex(YYSTYPE *, struct parser_control *); + + +#line 306 "source3/modules/getdate.c" /* yacc.c:358 */ + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 2 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 52 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 22 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 12 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 54 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 64 + +/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned + by yylex, with out-of-bounds checking. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 273 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, without out-of-bounds checking. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 20, 2, 2, 21, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 19, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 191, 191, 193, 197, 199, 201, 203, 205, 207, + 209, 213, 220, 227, 235, 242, 254, 256, 261, 263, + 265, 270, 275, 280, 288, 293, 313, 320, 328, 333, + 339, 344, 353, 362, 366, 368, 370, 372, 374, 376, + 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, + 398, 400, 405, 442, 443 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || 0 +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "tAGO", "tDST", "tDAY", "tDAY_UNIT", + "tDAYZONE", "tHOUR_UNIT", "tLOCAL_ZONE", "tMERIDIAN", "tMINUTE_UNIT", + "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tYEAR_UNIT", "tZONE", "tSNUMBER", + "tUNUMBER", "':'", "','", "'/'", "$accept", "spec", "item", "time", + "local_zone", "zone", "day", "date", "rel", "relunit", "number", + "o_merid", YY_NULLPTR +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 58, + 44, 47 +}; +# endif + +#define YYPACT_NINF -17 + +#define yypact_value_is_default(Yystate) \ + (!!((Yystate) == (-17))) + +#define YYTABLE_NINF -1 + +#define yytable_value_is_error(Yytable_value) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int8 yypact[] = +{ + -17, 0, -17, 1, -17, -17, -17, 19, -17, -14, + -17, -17, -17, 32, 26, 14, -17, -17, -17, -17, + -17, -17, -17, 27, -17, -17, -17, 22, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -16, -17, -17, -17, 29, 25, 30, -17, 31, -17, + -17, -17, 28, 23, -17, -17, -17, 33, -17, 34, + -7, -17, -17, -17 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 2, 0, 1, 21, 42, 19, 45, 16, 48, 0, + 39, 51, 36, 18, 0, 52, 3, 4, 5, 6, + 8, 7, 9, 33, 10, 22, 17, 28, 20, 41, + 44, 47, 38, 50, 35, 23, 40, 43, 11, 46, + 30, 37, 49, 34, 0, 0, 0, 32, 0, 27, + 31, 26, 53, 24, 29, 54, 13, 0, 12, 0, + 53, 25, 15, 14 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -10 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 58 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_uint8 yytable[] = +{ + 2, 49, 50, 55, 27, 3, 4, 5, 6, 7, + 62, 8, 9, 10, 11, 12, 13, 14, 15, 35, + 36, 25, 37, 26, 38, 39, 40, 41, 42, 43, + 47, 44, 29, 45, 30, 46, 28, 31, 55, 32, + 33, 34, 48, 52, 59, 56, 51, 57, 53, 54, + 63, 60, 61 +}; + +static const yytype_uint8 yycheck[] = +{ + 0, 17, 18, 10, 18, 5, 6, 7, 8, 9, + 17, 11, 12, 13, 14, 15, 16, 17, 18, 5, + 6, 20, 8, 4, 10, 11, 12, 13, 14, 15, + 3, 17, 6, 19, 8, 21, 4, 11, 10, 13, + 14, 15, 20, 18, 21, 17, 17, 19, 18, 18, + 60, 18, 18 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 23, 0, 5, 6, 7, 8, 9, 11, 12, + 13, 14, 15, 16, 17, 18, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 20, 4, 18, 4, 6, + 8, 11, 13, 14, 15, 5, 6, 8, 10, 11, + 12, 13, 14, 15, 17, 19, 21, 3, 20, 17, + 18, 17, 18, 18, 18, 10, 17, 19, 33, 21, + 18, 18, 17, 33 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 22, 23, 23, 24, 24, 24, 24, 24, 24, + 24, 25, 25, 25, 25, 25, 26, 26, 27, 27, + 27, 28, 28, 28, 29, 29, 29, 29, 29, 29, + 29, 29, 30, 30, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 32, 33, 33 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 0, 2, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 4, 6, 6, 1, 2, 1, 1, + 2, 1, 2, 2, 3, 5, 3, 3, 2, 4, + 2, 3, 2, 1, 2, 2, 1, 2, 2, 1, + 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, + 2, 1, 1, 0, 1 +}; + + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (pc, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (0) + +/* Error token number */ +#define YYTERROR 1 +#define YYERRCODE 256 + + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +#ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +#endif + + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, pc); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*----------------------------------------. +| Print this symbol's value on YYOUTPUT. | +`----------------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, struct parser_control *pc) +{ + FILE *yyo = yyoutput; + YYUSE (yyo); + YYUSE (pc); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + YYUSE (yytype); +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, struct parser_control *pc) +{ + YYFPRINTF (yyoutput, "%s %s (", + yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep, pc); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule, struct parser_control *pc) +{ + unsigned long int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + yystos[yyssp[yyi + 1 - yynrhs]], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , pc); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, pc); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +yystrlen (const char *yystr) +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +yystpcpy (char *yydest, const char *yysrc) +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + { + YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + { + YYSIZE_T yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, struct parser_control *pc) +{ + YYUSE (yyvaluep); + YYUSE (pc); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (struct parser_control *pc) +{ +/* The lookahead symbol. */ +int yychar; + + +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs + or non-GCC compilers. */ +YY_INITIAL_VALUE (static YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); + + /* Number of syntax errors so far. */ + int yynerrs; + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken = 0; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = yylex (&yylval, pc); + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 4: +#line 198 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->times_seen++; } +#line 1430 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 5: +#line 200 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->local_zones_seen++; } +#line 1436 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 6: +#line 202 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->zones_seen++; } +#line 1442 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 7: +#line 204 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->dates_seen++; } +#line 1448 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 8: +#line 206 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->days_seen++; } +#line 1454 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 9: +#line 208 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rels_seen++; } +#line 1460 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 11: +#line 214 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->hour = (yyvsp[-1].textintval).value; + pc->minutes = 0; + pc->seconds = 0; + pc->meridian = (yyvsp[0].intval); + } +#line 1471 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 12: +#line 221 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->hour = (yyvsp[-3].textintval).value; + pc->minutes = (yyvsp[-1].textintval).value; + pc->seconds = 0; + pc->meridian = (yyvsp[0].intval); + } +#line 1482 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 13: +#line 228 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->hour = (yyvsp[-3].textintval).value; + pc->minutes = (yyvsp[-1].textintval).value; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = (yyvsp[0].textintval).value % 100 + ((yyvsp[0].textintval).value / 100) * 60; + } +#line 1494 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 14: +#line 236 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->hour = (yyvsp[-5].textintval).value; + pc->minutes = (yyvsp[-3].textintval).value; + pc->seconds = (yyvsp[-1].textintval).value; + pc->meridian = (yyvsp[0].intval); + } +#line 1505 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 15: +#line 243 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->hour = (yyvsp[-5].textintval).value; + pc->minutes = (yyvsp[-3].textintval).value; + pc->seconds = (yyvsp[-1].textintval).value; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = (yyvsp[0].textintval).value % 100 + ((yyvsp[0].textintval).value / 100) * 60; + } +#line 1518 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 16: +#line 255 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->local_isdst = (yyvsp[0].intval); } +#line 1524 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 17: +#line 257 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->local_isdst = (yyvsp[-1].intval) < 0 ? 1 : (yyvsp[-1].intval) + 1; } +#line 1530 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 18: +#line 262 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->time_zone = (yyvsp[0].intval); } +#line 1536 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 19: +#line 264 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->time_zone = (yyvsp[0].intval) + 60; } +#line 1542 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 20: +#line 266 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->time_zone = (yyvsp[-1].intval) + 60; } +#line 1548 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 21: +#line 271 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->day_ordinal = 1; + pc->day_number = (yyvsp[0].intval); + } +#line 1557 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 22: +#line 276 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->day_ordinal = 1; + pc->day_number = (yyvsp[-1].intval); + } +#line 1566 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 23: +#line 281 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->day_ordinal = (yyvsp[-1].textintval).value; + pc->day_number = (yyvsp[0].intval); + } +#line 1575 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 24: +#line 289 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->month = (yyvsp[-2].textintval).value; + pc->day = (yyvsp[0].textintval).value; + } +#line 1584 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 25: +#line 294 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + /* Interpret as YYYY/MM/DD if the first value has 4 or more digits, + otherwise as MM/DD/YY. + The goal in recognizing YYYY/MM/DD is solely to support legacy + machine-generated dates like those in an RCS log listing. If + you want portability, use the ISO 8601 format. */ + if (4 <= (yyvsp[-4].textintval).digits) + { + pc->year = (yyvsp[-4].textintval); + pc->month = (yyvsp[-2].textintval).value; + pc->day = (yyvsp[0].textintval).value; + } + else + { + pc->month = (yyvsp[-4].textintval).value; + pc->day = (yyvsp[-2].textintval).value; + pc->year = (yyvsp[0].textintval); + } + } +#line 1608 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 26: +#line 314 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + /* ISO 8601 format. YYYY-MM-DD. */ + pc->year = (yyvsp[-2].textintval); + pc->month = -(yyvsp[-1].textintval).value; + pc->day = -(yyvsp[0].textintval).value; + } +#line 1619 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 27: +#line 321 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + /* e.g. 17-JUN-1992. */ + pc->day = (yyvsp[-2].textintval).value; + pc->month = (yyvsp[-1].intval); + pc->year.value = -(yyvsp[0].textintval).value; + pc->year.digits = (yyvsp[0].textintval).digits; + } +#line 1631 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 28: +#line 329 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->month = (yyvsp[-1].intval); + pc->day = (yyvsp[0].textintval).value; + } +#line 1640 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 29: +#line 334 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->month = (yyvsp[-3].intval); + pc->day = (yyvsp[-2].textintval).value; + pc->year = (yyvsp[0].textintval); + } +#line 1650 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 30: +#line 340 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->day = (yyvsp[-1].textintval).value; + pc->month = (yyvsp[0].intval); + } +#line 1659 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 31: +#line 345 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->day = (yyvsp[-2].textintval).value; + pc->month = (yyvsp[-1].intval); + pc->year = (yyvsp[0].textintval); + } +#line 1669 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 32: +#line 354 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + pc->rel_seconds = -pc->rel_seconds; + pc->rel_minutes = -pc->rel_minutes; + pc->rel_hour = -pc->rel_hour; + pc->rel_day = -pc->rel_day; + pc->rel_month = -pc->rel_month; + pc->rel_year = -pc->rel_year; + } +#line 1682 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 34: +#line 367 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_year += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1688 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 35: +#line 369 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_year += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1694 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 36: +#line 371 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_year += (yyvsp[0].intval); } +#line 1700 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 37: +#line 373 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_month += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1706 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 38: +#line 375 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_month += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1712 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 39: +#line 377 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_month += (yyvsp[0].intval); } +#line 1718 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 40: +#line 379 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_day += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1724 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 41: +#line 381 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_day += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1730 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 42: +#line 383 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_day += (yyvsp[0].intval); } +#line 1736 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 43: +#line 385 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_hour += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1742 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 44: +#line 387 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_hour += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1748 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 45: +#line 389 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_hour += (yyvsp[0].intval); } +#line 1754 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 46: +#line 391 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_minutes += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1760 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 47: +#line 393 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_minutes += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1766 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 48: +#line 395 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_minutes += (yyvsp[0].intval); } +#line 1772 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 49: +#line 397 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_seconds += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1778 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 50: +#line 399 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_seconds += (yyvsp[-1].textintval).value * (yyvsp[0].intval); } +#line 1784 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 51: +#line 401 "source3/modules/getdate.y" /* yacc.c:1646 */ + { pc->rel_seconds += (yyvsp[0].intval); } +#line 1790 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 52: +#line 406 "source3/modules/getdate.y" /* yacc.c:1646 */ + { + if (pc->dates_seen + && ! pc->rels_seen && (pc->times_seen || 2 < (yyvsp[0].textintval).digits)) + pc->year = (yyvsp[0].textintval); + else + { + if (4 < (yyvsp[0].textintval).digits) + { + pc->dates_seen++; + pc->day = (yyvsp[0].textintval).value % 100; + pc->month = ((yyvsp[0].textintval).value / 100) % 100; + pc->year.value = (yyvsp[0].textintval).value / 10000; + pc->year.digits = (yyvsp[0].textintval).digits - 4; + } + else + { + pc->times_seen++; + if ((yyvsp[0].textintval).digits <= 2) + { + pc->hour = (yyvsp[0].textintval).value; + pc->minutes = 0; + } + else + { + pc->hour = (yyvsp[0].textintval).value / 100; + pc->minutes = (yyvsp[0].textintval).value % 100; + } + pc->seconds = 0; + pc->meridian = MER24; + } + } + } +#line 1827 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 53: +#line 442 "source3/modules/getdate.y" /* yacc.c:1646 */ + { (yyval.intval) = MER24; } +#line 1833 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + case 54: +#line 444 "source3/modules/getdate.y" /* yacc.c:1646 */ + { (yyval.intval) = (yyvsp[0].intval); } +#line 1839 "source3/modules/getdate.c" /* yacc.c:1646 */ + break; + + +#line 1843 "source3/modules/getdate.c" /* yacc.c:1646 */ + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (pc, YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (pc, yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, pc); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp, pc); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined yyoverflow || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (pc, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, pc); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, pc); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} +#line 447 "source3/modules/getdate.y" /* yacc.c:1906 */ + + +/* Include this file down here because bison inserts code above which + may define-away `const'. We want the prototype for get_date to have + the same signature as the function definition. */ +#include "modules/getdate.h" + +#ifndef gmtime +struct tm *gmtime (const time_t *); +#endif +#ifndef localtime +struct tm *localtime (const time_t *); +#endif +#ifndef mktime +time_t mktime (struct tm *); +#endif + +static table const meridian_table[] = +{ + { "AM", tMERIDIAN, MERam }, + { "A.M.", tMERIDIAN, MERam }, + { "PM", tMERIDIAN, MERpm }, + { "P.M.", tMERIDIAN, MERpm }, + { 0, 0, 0 } +}; + +static table const dst_table[] = +{ + { "DST", tDST, 0 } +}; + +static table const month_and_day_table[] = +{ + { "JANUARY", tMONTH, 1 }, + { "FEBRUARY", tMONTH, 2 }, + { "MARCH", tMONTH, 3 }, + { "APRIL", tMONTH, 4 }, + { "MAY", tMONTH, 5 }, + { "JUNE", tMONTH, 6 }, + { "JULY", tMONTH, 7 }, + { "AUGUST", tMONTH, 8 }, + { "SEPTEMBER",tMONTH, 9 }, + { "SEPT", tMONTH, 9 }, + { "OCTOBER", tMONTH, 10 }, + { "NOVEMBER", tMONTH, 11 }, + { "DECEMBER", tMONTH, 12 }, + { "SUNDAY", tDAY, 0 }, + { "MONDAY", tDAY, 1 }, + { "TUESDAY", tDAY, 2 }, + { "TUES", tDAY, 2 }, + { "WEDNESDAY",tDAY, 3 }, + { "WEDNES", tDAY, 3 }, + { "THURSDAY", tDAY, 4 }, + { "THUR", tDAY, 4 }, + { "THURS", tDAY, 4 }, + { "FRIDAY", tDAY, 5 }, + { "SATURDAY", tDAY, 6 }, + { 0, 0, 0 } +}; + +static table const time_units_table[] = +{ + { "YEAR", tYEAR_UNIT, 1 }, + { "MONTH", tMONTH_UNIT, 1 }, + { "FORTNIGHT",tDAY_UNIT, 14 }, + { "WEEK", tDAY_UNIT, 7 }, + { "DAY", tDAY_UNIT, 1 }, + { "HOUR", tHOUR_UNIT, 1 }, + { "MINUTE", tMINUTE_UNIT, 1 }, + { "MIN", tMINUTE_UNIT, 1 }, + { "SECOND", tSEC_UNIT, 1 }, + { "SEC", tSEC_UNIT, 1 }, + { 0, 0, 0 } +}; + +/* Assorted relative-time words. */ +static table const relative_time_table[] = +{ + { "TOMORROW", tMINUTE_UNIT, 24 * 60 }, + { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) }, + { "TODAY", tMINUTE_UNIT, 0 }, + { "NOW", tMINUTE_UNIT, 0 }, + { "LAST", tUNUMBER, -1 }, + { "THIS", tUNUMBER, 0 }, + { "NEXT", tUNUMBER, 1 }, + { "FIRST", tUNUMBER, 1 }, +/*{ "SECOND", tUNUMBER, 2 }, */ + { "THIRD", tUNUMBER, 3 }, + { "FOURTH", tUNUMBER, 4 }, + { "FIFTH", tUNUMBER, 5 }, + { "SIXTH", tUNUMBER, 6 }, + { "SEVENTH", tUNUMBER, 7 }, + { "EIGHTH", tUNUMBER, 8 }, + { "NINTH", tUNUMBER, 9 }, + { "TENTH", tUNUMBER, 10 }, + { "ELEVENTH", tUNUMBER, 11 }, + { "TWELFTH", tUNUMBER, 12 }, + { "AGO", tAGO, 1 }, + { 0, 0, 0 } +}; + +/* The time zone table. This table is necessarily incomplete, as time + zone abbreviations are ambiguous; e.g. Australians interpret "EST" + as Eastern time in Australia, not as US Eastern Standard Time. + You cannot rely on getdate to handle arbitrary time zone + abbreviations; use numeric abbreviations like `-0500' instead. */ +static table const time_zone_table[] = +{ + { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ + { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ + { "UTC", tZONE, HOUR ( 0) }, + { "WET", tZONE, HOUR ( 0) }, /* Western European */ + { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */ + { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */ + { "ART", tZONE, -HOUR ( 3) }, /* Argentina */ + { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */ + { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */ + { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */ + { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */ + { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */ + { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */ + { "CLT", tZONE, -HOUR ( 4) }, /* Chile */ + { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */ + { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */ + { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */ + { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */ + { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */ + { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */ + { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */ + { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */ + { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */ + { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */ + { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */ + { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */ + { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */ + { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */ + { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */ + { "WAT", tZONE, HOUR ( 1) }, /* West Africa */ + { "CET", tZONE, HOUR ( 1) }, /* Central European */ + { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */ + { "MET", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "EET", tZONE, HOUR ( 2) }, /* Eastern European */ + { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */ + { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */ + { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */ + { "EAT", tZONE, HOUR ( 3) }, /* East Africa */ + { "MSK", tZONE, HOUR ( 3) }, /* Moscow */ + { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */ + { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */ + { "SGT", tZONE, HOUR ( 8) }, /* Singapore */ + { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */ + { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */ + { "GST", tZONE, HOUR (10) }, /* Guam Standard */ + { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */ + { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */ + { 0, 0, 0 } +}; + +/* Military time zone table. */ +static table const military_table[] = +{ + { "A", tZONE, -HOUR ( 1) }, + { "B", tZONE, -HOUR ( 2) }, + { "C", tZONE, -HOUR ( 3) }, + { "D", tZONE, -HOUR ( 4) }, + { "E", tZONE, -HOUR ( 5) }, + { "F", tZONE, -HOUR ( 6) }, + { "G", tZONE, -HOUR ( 7) }, + { "H", tZONE, -HOUR ( 8) }, + { "I", tZONE, -HOUR ( 9) }, + { "K", tZONE, -HOUR (10) }, + { "L", tZONE, -HOUR (11) }, + { "M", tZONE, -HOUR (12) }, + { "N", tZONE, HOUR ( 1) }, + { "O", tZONE, HOUR ( 2) }, + { "P", tZONE, HOUR ( 3) }, + { "Q", tZONE, HOUR ( 4) }, + { "R", tZONE, HOUR ( 5) }, + { "S", tZONE, HOUR ( 6) }, + { "T", tZONE, HOUR ( 7) }, + { "U", tZONE, HOUR ( 8) }, + { "V", tZONE, HOUR ( 9) }, + { "W", tZONE, HOUR (10) }, + { "X", tZONE, HOUR (11) }, + { "Y", tZONE, HOUR (12) }, + { "Z", tZONE, HOUR ( 0) }, + { 0, 0, 0 } +}; + + + +static int +to_hour (int hours, int meridian) +{ + switch (meridian) + { + case MER24: + return 0 <= hours && hours < 24 ? hours : -1; + case MERam: + return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1; + case MERpm: + return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1; + default: + abort (); + } + /* NOTREACHED */ + return 0; +} + +static int +to_year (textint textyear) +{ + int year = textyear.value; + + if (year < 0) + year = -year; + + /* XPG4 suggests that years 00-68 map to 2000-2068, and + years 69-99 map to 1969-1999. */ + if (textyear.digits == 2) + year += year < 69 ? 2000 : 1900; + + return year; +} + +static table const * +lookup_zone (struct parser_control const *pc, char const *name) +{ + table const *tp; + + /* Try local zone abbreviations first; they're more likely to be right. */ + for (tp = pc->local_time_zone_table; tp->name; tp++) + if (strcmp (name, tp->name) == 0) + return tp; + + for (tp = time_zone_table; tp->name; tp++) + if (strcmp (name, tp->name) == 0) + return tp; + + return 0; +} + +#if ! HAVE_TM_GMTOFF +/* Yield the difference between *A and *B, + measured in seconds, ignoring leap seconds. + The body of this function is taken directly from the GNU C Library; + see src/strftime.c. */ +static int +tm_diff (struct tm const *a, struct tm const *b) +{ + /* Compute intervening leap days correctly even if year is negative. + Take care to avoid int overflow in leap day calculations, + but it's OK to assume that A and B are close to each other. */ + int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); + int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = a100 >> 2; + int b400 = b100 >> 2; + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + int years = a->tm_year - b->tm_year; + int days = (365 * years + intervening_leap_days + + (a->tm_yday - b->tm_yday)); + return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} +#endif /* ! HAVE_TM_GMTOFF */ + +static table const * +lookup_word (struct parser_control const *pc, char *word) +{ + char *p; + char *q; + size_t wordlen; + table const *tp; + int i; + int abbrev; + + /* Make it uppercase. */ + for (p = word; *p; p++) + if (ISLOWER ((unsigned char) *p)) + *p = toupper ((unsigned char) *p); + + for (tp = meridian_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* See if we have an abbreviation for a month. */ + wordlen = strlen (word); + abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.'); + + for (tp = month_and_day_table; tp->name; tp++) + if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0) + return tp; + + if ((tp = lookup_zone (pc, word))) + return tp; + + if (strcmp (word, dst_table[0].name) == 0) + return dst_table; + + for (tp = time_units_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* Strip off any plural and try the units table again. */ + if (word[wordlen - 1] == 'S') + { + word[wordlen - 1] = '\0'; + for (tp = time_units_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */ + } + + for (tp = relative_time_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* Military time zones. */ + if (wordlen == 1) + for (tp = military_table; tp->name; tp++) + if (word[0] == tp->name[0]) + return tp; + + /* Drop out any periods and try the time zone table again. */ + for (i = 0, p = q = word; (*p = *q); q++) + if (*q == '.') + i = 1; + else + p++; + if (i && (tp = lookup_zone (pc, word))) + return tp; + + return 0; +} + +static int +yylex (YYSTYPE *lvalp, struct parser_control *pc) +{ + unsigned char c; + size_t count; + + for (;;) + { + while (c = *pc->input, ISSPACE (c)) + pc->input++; + + if (ISDIGIT (c) || c == '-' || c == '+') + { + char const *p; + int sign; + int value; + if (c == '-' || c == '+') + { + sign = c == '-' ? -1 : 1; + c = *++pc->input; + if (! ISDIGIT (c)) + /* skip the '-' sign */ + continue; + } + else + sign = 0; + p = pc->input; + value = 0; + do + { + value = 10 * value + c - '0'; + c = *++p; + } + while (ISDIGIT (c)); + lvalp->textintval.value = sign < 0 ? -value : value; + lvalp->textintval.digits = p - pc->input; + pc->input = p; + return sign ? tSNUMBER : tUNUMBER; + } + + if (ISALPHA (c)) + { + char buff[20]; + size_t i = 0; + table const *tp; + + do + { + if (i < 20) + buff[i++] = c; + c = *++pc->input; + } + while (ISALPHA (c) || c == '.'); + + buff[i] = '\0'; + tp = lookup_word (pc, buff); + if (! tp) + return '?'; + lvalp->intval = tp->value; + return tp->type; + } + + if (c != '(') + return *pc->input++; + count = 0; + do + { + c = *pc->input++; + if (c == '\0') + return c; + if (c == '(') + count++; + else if (c == ')') + count--; + } + while (count > 0); + } +} + +/* Do nothing if the parser reports an error. */ +static int +yyerror (struct parser_control *pc ATTRIBUTE_UNUSED, const char *s ATTRIBUTE_UNUSED) +{ + return 0; +} + +/* Parse a date/time string P. Return the corresponding time_t value, + or (time_t) -1 if there is an error. P can be an incomplete or + relative time specification; if so, use *NOW as the basis for the + returned time. */ +time_t +get_date (const char *p, const time_t *now) +{ + time_t Start = now ? *now : time (0); + struct tm *tmp = localtime (&Start); + struct tm tm; + struct tm tm0; + struct parser_control pc; + + if (! tmp) + return -1; + + pc.input = p; + pc.year.value = tmp->tm_year + TM_YEAR_BASE; + pc.year.digits = 4; + pc.month = tmp->tm_mon + 1; + pc.day = tmp->tm_mday; + pc.hour = tmp->tm_hour; + pc.minutes = tmp->tm_min; + pc.seconds = tmp->tm_sec; + tm.tm_isdst = tmp->tm_isdst; + + pc.meridian = MER24; + pc.rel_seconds = 0; + pc.rel_minutes = 0; + pc.rel_hour = 0; + pc.rel_day = 0; + pc.rel_month = 0; + pc.rel_year = 0; + pc.dates_seen = 0; + pc.days_seen = 0; + pc.rels_seen = 0; + pc.times_seen = 0; + pc.local_zones_seen = 0; + pc.zones_seen = 0; + +#ifdef HAVE_STRUCT_TM_TM_ZONE + pc.local_time_zone_table[0].name = tmp->tm_zone; + pc.local_time_zone_table[0].type = tLOCAL_ZONE; + pc.local_time_zone_table[0].value = tmp->tm_isdst; + pc.local_time_zone_table[1].name = 0; + + /* Probe the names used in the next three calendar quarters, looking + for a tm_isdst different from the one we already have. */ + { + int quarter; + for (quarter = 1; quarter <= 3; quarter++) + { + time_t probe = Start + quarter * (90 * 24 * 60 * 60); + struct tm *probe_tm = localtime (&probe); + if (probe_tm && probe_tm->tm_zone + && probe_tm->tm_isdst != pc.local_time_zone_table[0].value) + { + { + pc.local_time_zone_table[1].name = probe_tm->tm_zone; + pc.local_time_zone_table[1].type = tLOCAL_ZONE; + pc.local_time_zone_table[1].value = probe_tm->tm_isdst; + pc.local_time_zone_table[2].name = 0; + } + break; + } + } + } +#else +#ifdef HAVE_TZNAME + { +# ifndef tzname + extern char *tzname[]; +# endif + int i; + for (i = 0; i < 2; i++) + { + pc.local_time_zone_table[i].name = tzname[i]; + pc.local_time_zone_table[i].type = tLOCAL_ZONE; + pc.local_time_zone_table[i].value = i; + } + pc.local_time_zone_table[i].name = 0; + } +#else + pc.local_time_zone_table[0].name = 0; +#endif +#endif + + if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name + && ! strcmp (pc.local_time_zone_table[0].name, + pc.local_time_zone_table[1].name)) + { + /* This locale uses the same abbreviation for standard and + daylight times. So if we see that abbreviation, we don't + know whether it's daylight time. */ + pc.local_time_zone_table[0].value = -1; + pc.local_time_zone_table[1].name = 0; + } + + if (yyparse (&pc) != 0 + || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen + || 1 < (pc.local_zones_seen + pc.zones_seen) + || (pc.local_zones_seen && 1 < pc.local_isdst)) + return -1; + + tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year; + tm.tm_mon = pc.month - 1 + pc.rel_month; + tm.tm_mday = pc.day + pc.rel_day; + if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen)) + { + tm.tm_hour = to_hour (pc.hour, pc.meridian); + if (tm.tm_hour < 0) + return -1; + tm.tm_min = pc.minutes; + tm.tm_sec = pc.seconds; + } + else + { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + } + + /* Let mktime deduce tm_isdst if we have an absolute time stamp, + or if the relative time stamp mentions days, months, or years. */ + if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day + | pc.rel_month | pc.rel_year) + tm.tm_isdst = -1; + + /* But if the input explicitly specifies local time with or without + DST, give mktime that information. */ + if (pc.local_zones_seen) + tm.tm_isdst = pc.local_isdst; + + tm0 = tm; + + Start = mktime (&tm); + + if (Start == (time_t) -1) + { + + /* Guard against falsely reporting errors near the time_t boundaries + when parsing times in other time zones. For example, if the min + time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead + of UTC, then the min localtime value is 1970-01-01 08:00:00; if + we apply mktime to 1970-01-01 00:00:00 we will get an error, so + we apply mktime to 1970-01-02 08:00:00 instead and adjust the time + zone by 24 hours to compensate. This algorithm assumes that + there is no DST transition within a day of the time_t boundaries. */ + if (pc.zones_seen) + { + tm = tm0; + if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE) + { + tm.tm_mday++; + pc.time_zone += 24 * 60; + } + else + { + tm.tm_mday--; + pc.time_zone -= 24 * 60; + } + Start = mktime (&tm); + } + + if (Start == (time_t) -1) + return Start; + } + + if (pc.days_seen && ! pc.dates_seen) + { + tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + + 7 * (pc.day_ordinal - (0 < pc.day_ordinal))); + tm.tm_isdst = -1; + Start = mktime (&tm); + if (Start == (time_t) -1) + return Start; + } + + if (pc.zones_seen) + { + int delta = pc.time_zone * 60; +#ifdef HAVE_TM_GMTOFF + delta -= tm.tm_gmtoff; +#else + struct tm *gmt = gmtime (&Start); + if (! gmt) + return -1; + delta -= tm_diff (&tm, gmt); +#endif + if ((Start < Start - delta) != (delta < 0)) + return -1; /* time_t overflow */ + Start -= delta; + } + + /* Add relative hours, minutes, and seconds. Ignore leap seconds; + i.e. "+ 10 minutes" means 600 seconds, even if one of them is a + leap second. Typically this is not what the user wants, but it's + too hard to do it the other way, because the time zone indicator + must be applied before relative times, and if mktime is applied + again the time zone will be lost. */ + { + time_t t0 = Start; + long d1 = 60 * 60 * (long) pc.rel_hour; + time_t t1 = t0 + d1; + long d2 = 60 * (long) pc.rel_minutes; + time_t t2 = t1 + d2; + int d3 = pc.rel_seconds; + time_t t3 = t2 + d3; + if ((d1 / (60 * 60) ^ pc.rel_hour) + | (d2 / 60 ^ pc.rel_minutes) + | ((t0 + d1 < t0) ^ (d1 < 0)) + | ((t1 + d2 < t1) ^ (d2 < 0)) + | ((t2 + d3 < t2) ^ (d3 < 0))) + return -1; + Start = t3; + } + + return Start; +} + +#if TEST + +#include + +int +main (int ac, char **av) +{ + char buff[BUFSIZ]; + time_t d; + + printf ("Enter date, or blank line to exit.\n\t> "); + fflush (stdout); + + buff[BUFSIZ - 1] = 0; + while (fgets (buff, BUFSIZ - 1, stdin) && buff[0]) + { + d = get_date (buff, 0); + if (d == (time_t) -1) + printf ("Bad format - couldn't convert.\n"); + else + printf ("%s", ctime (&d)); + printf ("\t> "); + fflush (stdout); + } + return 0; +} +#endif /* defined TEST */ diff --git a/source3/modules/getdate.h b/source3/modules/getdate.h new file mode 100644 index 0000000..80b4a98 --- /dev/null +++ b/source3/modules/getdate.h @@ -0,0 +1,45 @@ +/* Copyright (C) 1995, 1997, 1998 Free Software Foundation, Inc. + + 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 2, 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifndef PARAMS +# if defined PROTOTYPES || (defined __STDC__ && __STDC__) +# define PARAMS(Args) Args +# else +# define PARAMS(Args) () +# endif +#endif + +#ifdef vms +# include +# include +#else +# include +# if TIME_WITH_SYS_TIME +# include +# include +# else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +# endif +#endif /* defined (vms) */ + +time_t get_date PARAMS ((const char *p, const time_t *now)); diff --git a/source3/modules/getdate.y b/source3/modules/getdate.y new file mode 100644 index 0000000..8e349ba --- /dev/null +++ b/source3/modules/getdate.y @@ -0,0 +1,1118 @@ +%{ +/* Parse a string into an internal time stamp. + Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc. + + 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 2, 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 . */ + +/* Originally written by Steven M. Bellovin while + at the University of North Carolina at Chapel Hill. Later tweaked by + a couple of people on Usenet. Completely overhauled by Rich $alz + and Jim Berets in August, 1990. + + Modified by Paul Eggert in August 1999 to do + the right thing about local DST. Unlike previous versions, this + version is reentrant. */ + +#ifdef HAVE_CONFIG_H +# include +# ifdef HAVE_ALLOCA_H +# include +# endif +#endif + +/* Since the code of getdate.y is not included in the Emacs executable + itself, there is no need to #define static in this file. Even if + the code were included in the Emacs executable, it probably + wouldn't do any harm to #undef it here; this will only cause + problems if we try to write to a static variable, which I don't + think this code needs to do. */ +#ifdef emacs +# undef static +#endif + +#include +#include + +#ifdef HAVE_STDLIB_H +# include /* for `free'; used by Bison 1.27 */ +#endif + +#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) isascii (c) +#endif + +#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) +#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) + +/* ISDIGIT differs from ISDIGIT_LOCALE, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char. + - It's guaranteed to evaluate its argument exactly once. + - It's typically faster. + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + ISDIGIT_LOCALE unless it's important to use the locale's definition + of `digit' even when the host does not conform to POSIX. */ +#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) + +#if STDC_HEADERS || HAVE_STRING_H +# include +#endif + +#ifndef HAVE___ATTRIBUTE__ +# define __attribute__(x) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#define EPOCH_YEAR 1970 +#define TM_YEAR_BASE 1900 + +#define HOUR(x) ((x) * 60) + +/* An integer value, and the number of digits in its textual + representation. */ +typedef struct +{ + int value; + int digits; +} textint; + +/* An entry in the lexical lookup table. */ +typedef struct +{ + char const *name; + int type; + int value; +} table; + +/* Meridian: am, pm, or 24-hour style. */ +enum { MERam, MERpm, MER24 }; + +/* Information passed to and from the parser. */ +struct parser_control +{ + /* The input string remaining to be parsed. */ + const char *input; + + /* N, if this is the Nth Tuesday. */ + int day_ordinal; + + /* Day of week; Sunday is 0. */ + int day_number; + + /* tm_isdst flag for the local zone. */ + int local_isdst; + + /* Time zone, in minutes east of UTC. */ + int time_zone; + + /* Style used for time. */ + int meridian; + + /* Gregorian year, month, day, hour, minutes, and seconds. */ + textint year; + int month; + int day; + int hour; + int minutes; + int seconds; + + /* Relative year, month, day, hour, minutes, and seconds. */ + int rel_year; + int rel_month; + int rel_day; + int rel_hour; + int rel_minutes; + int rel_seconds; + + /* Counts of nonterminals of various flavors parsed so far. */ + int dates_seen; + int days_seen; + int local_zones_seen; + int rels_seen; + int times_seen; + int zones_seen; + + /* Table of local time zone abbreviations, terminated by a null entry. */ + table local_time_zone_table[3]; +}; + +%} + +%lex-param {struct parser_control *pc} +%parse-param {struct parser_control *pc} + +/* We want a reentrant parser. */ +%pure-parser + +/* This grammar has 13 shift/reduce conflicts. */ +%expect 13 + +%union +{ + int intval; + textint textintval; +} + +%{ + +static int yyerror(struct parser_control *, const char *); +static int yylex(YYSTYPE *, struct parser_control *); + +%} + +%token tAGO tDST + +%token tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN +%token tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE + +%token tSNUMBER tUNUMBER + +%type o_merid + +%% + +spec: + /* empty */ + | spec item + ; + +item: + time + { pc->times_seen++; } + | local_zone + { pc->local_zones_seen++; } + | zone + { pc->zones_seen++; } + | date + { pc->dates_seen++; } + | day + { pc->days_seen++; } + | rel + { pc->rels_seen++; } + | number + ; + +time: + tUNUMBER tMERIDIAN + { + pc->hour = $1.value; + pc->minutes = 0; + pc->seconds = 0; + pc->meridian = $2; + } + | tUNUMBER ':' tUNUMBER o_merid + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = 0; + pc->meridian = $4; + } + | tUNUMBER ':' tUNUMBER tSNUMBER + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = $4.value % 100 + ($4.value / 100) * 60; + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5.value; + pc->meridian = $6; + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5.value; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = $6.value % 100 + ($6.value / 100) * 60; + } + ; + +local_zone: + tLOCAL_ZONE + { pc->local_isdst = $1; } + | tLOCAL_ZONE tDST + { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; } + ; + +zone: + tZONE + { pc->time_zone = $1; } + | tDAYZONE + { pc->time_zone = $1 + 60; } + | tZONE tDST + { pc->time_zone = $1 + 60; } + ; + +day: + tDAY + { + pc->day_ordinal = 1; + pc->day_number = $1; + } + | tDAY ',' + { + pc->day_ordinal = 1; + pc->day_number = $1; + } + | tUNUMBER tDAY + { + pc->day_ordinal = $1.value; + pc->day_number = $2; + } + ; + +date: + tUNUMBER '/' tUNUMBER + { + pc->month = $1.value; + pc->day = $3.value; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER + { + /* Interpret as YYYY/MM/DD if the first value has 4 or more digits, + otherwise as MM/DD/YY. + The goal in recognizing YYYY/MM/DD is solely to support legacy + machine-generated dates like those in an RCS log listing. If + you want portability, use the ISO 8601 format. */ + if (4 <= $1.digits) + { + pc->year = $1; + pc->month = $3.value; + pc->day = $5.value; + } + else + { + pc->month = $1.value; + pc->day = $3.value; + pc->year = $5; + } + } + | tUNUMBER tSNUMBER tSNUMBER + { + /* ISO 8601 format. YYYY-MM-DD. */ + pc->year = $1; + pc->month = -$2.value; + pc->day = -$3.value; + } + | tUNUMBER tMONTH tSNUMBER + { + /* e.g. 17-JUN-1992. */ + pc->day = $1.value; + pc->month = $2; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + } + | tMONTH tUNUMBER ',' tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + pc->year = $4; + } + | tUNUMBER tMONTH + { + pc->day = $1.value; + pc->month = $2; + } + | tUNUMBER tMONTH tUNUMBER + { + pc->day = $1.value; + pc->month = $2; + pc->year = $3; + } + ; + +rel: + relunit tAGO + { + pc->rel_seconds = -pc->rel_seconds; + pc->rel_minutes = -pc->rel_minutes; + pc->rel_hour = -pc->rel_hour; + pc->rel_day = -pc->rel_day; + pc->rel_month = -pc->rel_month; + pc->rel_year = -pc->rel_year; + } + | relunit + ; + +relunit: + tUNUMBER tYEAR_UNIT + { pc->rel_year += $1.value * $2; } + | tSNUMBER tYEAR_UNIT + { pc->rel_year += $1.value * $2; } + | tYEAR_UNIT + { pc->rel_year += $1; } + | tUNUMBER tMONTH_UNIT + { pc->rel_month += $1.value * $2; } + | tSNUMBER tMONTH_UNIT + { pc->rel_month += $1.value * $2; } + | tMONTH_UNIT + { pc->rel_month += $1; } + | tUNUMBER tDAY_UNIT + { pc->rel_day += $1.value * $2; } + | tSNUMBER tDAY_UNIT + { pc->rel_day += $1.value * $2; } + | tDAY_UNIT + { pc->rel_day += $1; } + | tUNUMBER tHOUR_UNIT + { pc->rel_hour += $1.value * $2; } + | tSNUMBER tHOUR_UNIT + { pc->rel_hour += $1.value * $2; } + | tHOUR_UNIT + { pc->rel_hour += $1; } + | tUNUMBER tMINUTE_UNIT + { pc->rel_minutes += $1.value * $2; } + | tSNUMBER tMINUTE_UNIT + { pc->rel_minutes += $1.value * $2; } + | tMINUTE_UNIT + { pc->rel_minutes += $1; } + | tUNUMBER tSEC_UNIT + { pc->rel_seconds += $1.value * $2; } + | tSNUMBER tSEC_UNIT + { pc->rel_seconds += $1.value * $2; } + | tSEC_UNIT + { pc->rel_seconds += $1; } + ; + +number: + tUNUMBER + { + if (pc->dates_seen + && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits)) + pc->year = $1; + else + { + if (4 < $1.digits) + { + pc->dates_seen++; + pc->day = $1.value % 100; + pc->month = ($1.value / 100) % 100; + pc->year.value = $1.value / 10000; + pc->year.digits = $1.digits - 4; + } + else + { + pc->times_seen++; + if ($1.digits <= 2) + { + pc->hour = $1.value; + pc->minutes = 0; + } + else + { + pc->hour = $1.value / 100; + pc->minutes = $1.value % 100; + } + pc->seconds = 0; + pc->meridian = MER24; + } + } + } + ; + +o_merid: + /* empty */ + { $$ = MER24; } + | tMERIDIAN + { $$ = $1; } + ; + +%% + +/* Include this file down here because bison inserts code above which + may define-away `const'. We want the prototype for get_date to have + the same signature as the function definition. */ +#include "modules/getdate.h" + +#ifndef gmtime +struct tm *gmtime (const time_t *); +#endif +#ifndef localtime +struct tm *localtime (const time_t *); +#endif +#ifndef mktime +time_t mktime (struct tm *); +#endif + +static table const meridian_table[] = +{ + { "AM", tMERIDIAN, MERam }, + { "A.M.", tMERIDIAN, MERam }, + { "PM", tMERIDIAN, MERpm }, + { "P.M.", tMERIDIAN, MERpm }, + { 0, 0, 0 } +}; + +static table const dst_table[] = +{ + { "DST", tDST, 0 } +}; + +static table const month_and_day_table[] = +{ + { "JANUARY", tMONTH, 1 }, + { "FEBRUARY", tMONTH, 2 }, + { "MARCH", tMONTH, 3 }, + { "APRIL", tMONTH, 4 }, + { "MAY", tMONTH, 5 }, + { "JUNE", tMONTH, 6 }, + { "JULY", tMONTH, 7 }, + { "AUGUST", tMONTH, 8 }, + { "SEPTEMBER",tMONTH, 9 }, + { "SEPT", tMONTH, 9 }, + { "OCTOBER", tMONTH, 10 }, + { "NOVEMBER", tMONTH, 11 }, + { "DECEMBER", tMONTH, 12 }, + { "SUNDAY", tDAY, 0 }, + { "MONDAY", tDAY, 1 }, + { "TUESDAY", tDAY, 2 }, + { "TUES", tDAY, 2 }, + { "WEDNESDAY",tDAY, 3 }, + { "WEDNES", tDAY, 3 }, + { "THURSDAY", tDAY, 4 }, + { "THUR", tDAY, 4 }, + { "THURS", tDAY, 4 }, + { "FRIDAY", tDAY, 5 }, + { "SATURDAY", tDAY, 6 }, + { 0, 0, 0 } +}; + +static table const time_units_table[] = +{ + { "YEAR", tYEAR_UNIT, 1 }, + { "MONTH", tMONTH_UNIT, 1 }, + { "FORTNIGHT",tDAY_UNIT, 14 }, + { "WEEK", tDAY_UNIT, 7 }, + { "DAY", tDAY_UNIT, 1 }, + { "HOUR", tHOUR_UNIT, 1 }, + { "MINUTE", tMINUTE_UNIT, 1 }, + { "MIN", tMINUTE_UNIT, 1 }, + { "SECOND", tSEC_UNIT, 1 }, + { "SEC", tSEC_UNIT, 1 }, + { 0, 0, 0 } +}; + +/* Assorted relative-time words. */ +static table const relative_time_table[] = +{ + { "TOMORROW", tMINUTE_UNIT, 24 * 60 }, + { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) }, + { "TODAY", tMINUTE_UNIT, 0 }, + { "NOW", tMINUTE_UNIT, 0 }, + { "LAST", tUNUMBER, -1 }, + { "THIS", tUNUMBER, 0 }, + { "NEXT", tUNUMBER, 1 }, + { "FIRST", tUNUMBER, 1 }, +/*{ "SECOND", tUNUMBER, 2 }, */ + { "THIRD", tUNUMBER, 3 }, + { "FOURTH", tUNUMBER, 4 }, + { "FIFTH", tUNUMBER, 5 }, + { "SIXTH", tUNUMBER, 6 }, + { "SEVENTH", tUNUMBER, 7 }, + { "EIGHTH", tUNUMBER, 8 }, + { "NINTH", tUNUMBER, 9 }, + { "TENTH", tUNUMBER, 10 }, + { "ELEVENTH", tUNUMBER, 11 }, + { "TWELFTH", tUNUMBER, 12 }, + { "AGO", tAGO, 1 }, + { 0, 0, 0 } +}; + +/* The time zone table. This table is necessarily incomplete, as time + zone abbreviations are ambiguous; e.g. Australians interpret "EST" + as Eastern time in Australia, not as US Eastern Standard Time. + You cannot rely on getdate to handle arbitrary time zone + abbreviations; use numeric abbreviations like `-0500' instead. */ +static table const time_zone_table[] = +{ + { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ + { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ + { "UTC", tZONE, HOUR ( 0) }, + { "WET", tZONE, HOUR ( 0) }, /* Western European */ + { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */ + { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */ + { "ART", tZONE, -HOUR ( 3) }, /* Argentina */ + { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */ + { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */ + { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */ + { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */ + { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */ + { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */ + { "CLT", tZONE, -HOUR ( 4) }, /* Chile */ + { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */ + { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */ + { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */ + { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */ + { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */ + { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */ + { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */ + { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */ + { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */ + { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */ + { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */ + { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */ + { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */ + { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */ + { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */ + { "WAT", tZONE, HOUR ( 1) }, /* West Africa */ + { "CET", tZONE, HOUR ( 1) }, /* Central European */ + { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */ + { "MET", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "EET", tZONE, HOUR ( 2) }, /* Eastern European */ + { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */ + { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */ + { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */ + { "EAT", tZONE, HOUR ( 3) }, /* East Africa */ + { "MSK", tZONE, HOUR ( 3) }, /* Moscow */ + { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */ + { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */ + { "SGT", tZONE, HOUR ( 8) }, /* Singapore */ + { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */ + { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */ + { "GST", tZONE, HOUR (10) }, /* Guam Standard */ + { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */ + { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */ + { 0, 0, 0 } +}; + +/* Military time zone table. */ +static table const military_table[] = +{ + { "A", tZONE, -HOUR ( 1) }, + { "B", tZONE, -HOUR ( 2) }, + { "C", tZONE, -HOUR ( 3) }, + { "D", tZONE, -HOUR ( 4) }, + { "E", tZONE, -HOUR ( 5) }, + { "F", tZONE, -HOUR ( 6) }, + { "G", tZONE, -HOUR ( 7) }, + { "H", tZONE, -HOUR ( 8) }, + { "I", tZONE, -HOUR ( 9) }, + { "K", tZONE, -HOUR (10) }, + { "L", tZONE, -HOUR (11) }, + { "M", tZONE, -HOUR (12) }, + { "N", tZONE, HOUR ( 1) }, + { "O", tZONE, HOUR ( 2) }, + { "P", tZONE, HOUR ( 3) }, + { "Q", tZONE, HOUR ( 4) }, + { "R", tZONE, HOUR ( 5) }, + { "S", tZONE, HOUR ( 6) }, + { "T", tZONE, HOUR ( 7) }, + { "U", tZONE, HOUR ( 8) }, + { "V", tZONE, HOUR ( 9) }, + { "W", tZONE, HOUR (10) }, + { "X", tZONE, HOUR (11) }, + { "Y", tZONE, HOUR (12) }, + { "Z", tZONE, HOUR ( 0) }, + { 0, 0, 0 } +}; + + + +static int +to_hour (int hours, int meridian) +{ + switch (meridian) + { + case MER24: + return 0 <= hours && hours < 24 ? hours : -1; + case MERam: + return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1; + case MERpm: + return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1; + default: + abort (); + } + /* NOTREACHED */ + return 0; +} + +static int +to_year (textint textyear) +{ + int year = textyear.value; + + if (year < 0) + year = -year; + + /* XPG4 suggests that years 00-68 map to 2000-2068, and + years 69-99 map to 1969-1999. */ + if (textyear.digits == 2) + year += year < 69 ? 2000 : 1900; + + return year; +} + +static table const * +lookup_zone (struct parser_control const *pc, char const *name) +{ + table const *tp; + + /* Try local zone abbreviations first; they're more likely to be right. */ + for (tp = pc->local_time_zone_table; tp->name; tp++) + if (strcmp (name, tp->name) == 0) + return tp; + + for (tp = time_zone_table; tp->name; tp++) + if (strcmp (name, tp->name) == 0) + return tp; + + return 0; +} + +#if ! HAVE_TM_GMTOFF +/* Yield the difference between *A and *B, + measured in seconds, ignoring leap seconds. + The body of this function is taken directly from the GNU C Library; + see src/strftime.c. */ +static int +tm_diff (struct tm const *a, struct tm const *b) +{ + /* Compute intervening leap days correctly even if year is negative. + Take care to avoid int overflow in leap day calculations, + but it's OK to assume that A and B are close to each other. */ + int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); + int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = a100 >> 2; + int b400 = b100 >> 2; + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + int years = a->tm_year - b->tm_year; + int days = (365 * years + intervening_leap_days + + (a->tm_yday - b->tm_yday)); + return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} +#endif /* ! HAVE_TM_GMTOFF */ + +static table const * +lookup_word (struct parser_control const *pc, char *word) +{ + char *p; + char *q; + size_t wordlen; + table const *tp; + int i; + int abbrev; + + /* Make it uppercase. */ + for (p = word; *p; p++) + if (ISLOWER ((unsigned char) *p)) + *p = toupper ((unsigned char) *p); + + for (tp = meridian_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* See if we have an abbreviation for a month. */ + wordlen = strlen (word); + abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.'); + + for (tp = month_and_day_table; tp->name; tp++) + if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0) + return tp; + + if ((tp = lookup_zone (pc, word))) + return tp; + + if (strcmp (word, dst_table[0].name) == 0) + return dst_table; + + for (tp = time_units_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* Strip off any plural and try the units table again. */ + if (word[wordlen - 1] == 'S') + { + word[wordlen - 1] = '\0'; + for (tp = time_units_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */ + } + + for (tp = relative_time_table; tp->name; tp++) + if (strcmp (word, tp->name) == 0) + return tp; + + /* Military time zones. */ + if (wordlen == 1) + for (tp = military_table; tp->name; tp++) + if (word[0] == tp->name[0]) + return tp; + + /* Drop out any periods and try the time zone table again. */ + for (i = 0, p = q = word; (*p = *q); q++) + if (*q == '.') + i = 1; + else + p++; + if (i && (tp = lookup_zone (pc, word))) + return tp; + + return 0; +} + +static int +yylex (YYSTYPE *lvalp, struct parser_control *pc) +{ + unsigned char c; + size_t count; + + for (;;) + { + while (c = *pc->input, ISSPACE (c)) + pc->input++; + + if (ISDIGIT (c) || c == '-' || c == '+') + { + char const *p; + int sign; + int value; + if (c == '-' || c == '+') + { + sign = c == '-' ? -1 : 1; + c = *++pc->input; + if (! ISDIGIT (c)) + /* skip the '-' sign */ + continue; + } + else + sign = 0; + p = pc->input; + value = 0; + do + { + value = 10 * value + c - '0'; + c = *++p; + } + while (ISDIGIT (c)); + lvalp->textintval.value = sign < 0 ? -value : value; + lvalp->textintval.digits = p - pc->input; + pc->input = p; + return sign ? tSNUMBER : tUNUMBER; + } + + if (ISALPHA (c)) + { + char buff[20]; + size_t i = 0; + table const *tp; + + do + { + if (i < 20) + buff[i++] = c; + c = *++pc->input; + } + while (ISALPHA (c) || c == '.'); + + buff[i] = '\0'; + tp = lookup_word (pc, buff); + if (! tp) + return '?'; + lvalp->intval = tp->value; + return tp->type; + } + + if (c != '(') + return *pc->input++; + count = 0; + do + { + c = *pc->input++; + if (c == '\0') + return c; + if (c == '(') + count++; + else if (c == ')') + count--; + } + while (count > 0); + } +} + +/* Do nothing if the parser reports an error. */ +static int +yyerror (struct parser_control *pc ATTRIBUTE_UNUSED, const char *s ATTRIBUTE_UNUSED) +{ + return 0; +} + +/* Parse a date/time string P. Return the corresponding time_t value, + or (time_t) -1 if there is an error. P can be an incomplete or + relative time specification; if so, use *NOW as the basis for the + returned time. */ +time_t +get_date (const char *p, const time_t *now) +{ + time_t Start = now ? *now : time (0); + struct tm *tmp = localtime (&Start); + struct tm tm; + struct tm tm0; + struct parser_control pc; + + if (! tmp) + return -1; + + pc.input = p; + pc.year.value = tmp->tm_year + TM_YEAR_BASE; + pc.year.digits = 4; + pc.month = tmp->tm_mon + 1; + pc.day = tmp->tm_mday; + pc.hour = tmp->tm_hour; + pc.minutes = tmp->tm_min; + pc.seconds = tmp->tm_sec; + tm.tm_isdst = tmp->tm_isdst; + + pc.meridian = MER24; + pc.rel_seconds = 0; + pc.rel_minutes = 0; + pc.rel_hour = 0; + pc.rel_day = 0; + pc.rel_month = 0; + pc.rel_year = 0; + pc.dates_seen = 0; + pc.days_seen = 0; + pc.rels_seen = 0; + pc.times_seen = 0; + pc.local_zones_seen = 0; + pc.zones_seen = 0; + +#ifdef HAVE_STRUCT_TM_TM_ZONE + pc.local_time_zone_table[0].name = tmp->tm_zone; + pc.local_time_zone_table[0].type = tLOCAL_ZONE; + pc.local_time_zone_table[0].value = tmp->tm_isdst; + pc.local_time_zone_table[1].name = 0; + + /* Probe the names used in the next three calendar quarters, looking + for a tm_isdst different from the one we already have. */ + { + int quarter; + for (quarter = 1; quarter <= 3; quarter++) + { + time_t probe = Start + quarter * (90 * 24 * 60 * 60); + struct tm *probe_tm = localtime (&probe); + if (probe_tm && probe_tm->tm_zone + && probe_tm->tm_isdst != pc.local_time_zone_table[0].value) + { + { + pc.local_time_zone_table[1].name = probe_tm->tm_zone; + pc.local_time_zone_table[1].type = tLOCAL_ZONE; + pc.local_time_zone_table[1].value = probe_tm->tm_isdst; + pc.local_time_zone_table[2].name = 0; + } + break; + } + } + } +#else +#ifdef HAVE_TZNAME + { +# ifndef tzname + extern char *tzname[]; +# endif + int i; + for (i = 0; i < 2; i++) + { + pc.local_time_zone_table[i].name = tzname[i]; + pc.local_time_zone_table[i].type = tLOCAL_ZONE; + pc.local_time_zone_table[i].value = i; + } + pc.local_time_zone_table[i].name = 0; + } +#else + pc.local_time_zone_table[0].name = 0; +#endif +#endif + + if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name + && ! strcmp (pc.local_time_zone_table[0].name, + pc.local_time_zone_table[1].name)) + { + /* This locale uses the same abbreviation for standard and + daylight times. So if we see that abbreviation, we don't + know whether it's daylight time. */ + pc.local_time_zone_table[0].value = -1; + pc.local_time_zone_table[1].name = 0; + } + + if (yyparse (&pc) != 0 + || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen + || 1 < (pc.local_zones_seen + pc.zones_seen) + || (pc.local_zones_seen && 1 < pc.local_isdst)) + return -1; + + tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year; + tm.tm_mon = pc.month - 1 + pc.rel_month; + tm.tm_mday = pc.day + pc.rel_day; + if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen)) + { + tm.tm_hour = to_hour (pc.hour, pc.meridian); + if (tm.tm_hour < 0) + return -1; + tm.tm_min = pc.minutes; + tm.tm_sec = pc.seconds; + } + else + { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + } + + /* Let mktime deduce tm_isdst if we have an absolute time stamp, + or if the relative time stamp mentions days, months, or years. */ + if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day + | pc.rel_month | pc.rel_year) + tm.tm_isdst = -1; + + /* But if the input explicitly specifies local time with or without + DST, give mktime that information. */ + if (pc.local_zones_seen) + tm.tm_isdst = pc.local_isdst; + + tm0 = tm; + + Start = mktime (&tm); + + if (Start == (time_t) -1) + { + + /* Guard against falsely reporting errors near the time_t boundaries + when parsing times in other time zones. For example, if the min + time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead + of UTC, then the min localtime value is 1970-01-01 08:00:00; if + we apply mktime to 1970-01-01 00:00:00 we will get an error, so + we apply mktime to 1970-01-02 08:00:00 instead and adjust the time + zone by 24 hours to compensate. This algorithm assumes that + there is no DST transition within a day of the time_t boundaries. */ + if (pc.zones_seen) + { + tm = tm0; + if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE) + { + tm.tm_mday++; + pc.time_zone += 24 * 60; + } + else + { + tm.tm_mday--; + pc.time_zone -= 24 * 60; + } + Start = mktime (&tm); + } + + if (Start == (time_t) -1) + return Start; + } + + if (pc.days_seen && ! pc.dates_seen) + { + tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + + 7 * (pc.day_ordinal - (0 < pc.day_ordinal))); + tm.tm_isdst = -1; + Start = mktime (&tm); + if (Start == (time_t) -1) + return Start; + } + + if (pc.zones_seen) + { + int delta = pc.time_zone * 60; +#ifdef HAVE_TM_GMTOFF + delta -= tm.tm_gmtoff; +#else + struct tm *gmt = gmtime (&Start); + if (! gmt) + return -1; + delta -= tm_diff (&tm, gmt); +#endif + if ((Start < Start - delta) != (delta < 0)) + return -1; /* time_t overflow */ + Start -= delta; + } + + /* Add relative hours, minutes, and seconds. Ignore leap seconds; + i.e. "+ 10 minutes" means 600 seconds, even if one of them is a + leap second. Typically this is not what the user wants, but it's + too hard to do it the other way, because the time zone indicator + must be applied before relative times, and if mktime is applied + again the time zone will be lost. */ + { + time_t t0 = Start; + long d1 = 60 * 60 * (long) pc.rel_hour; + time_t t1 = t0 + d1; + long d2 = 60 * (long) pc.rel_minutes; + time_t t2 = t1 + d2; + int d3 = pc.rel_seconds; + time_t t3 = t2 + d3; + if ((d1 / (60 * 60) ^ pc.rel_hour) + | (d2 / 60 ^ pc.rel_minutes) + | ((t0 + d1 < t0) ^ (d1 < 0)) + | ((t1 + d2 < t1) ^ (d2 < 0)) + | ((t2 + d3 < t2) ^ (d3 < 0))) + return -1; + Start = t3; + } + + return Start; +} + +#if TEST + +#include + +int +main (int ac, char **av) +{ + char buff[BUFSIZ]; + time_t d; + + printf ("Enter date, or blank line to exit.\n\t> "); + fflush (stdout); + + buff[BUFSIZ - 1] = 0; + while (fgets (buff, BUFSIZ - 1, stdin) && buff[0]) + { + d = get_date (buff, 0); + if (d == (time_t) -1) + printf ("Bad format - couldn't convert.\n"); + else + printf ("%s", ctime (&d)); + printf ("\t> "); + fflush (stdout); + } + return 0; +} +#endif /* defined TEST */ diff --git a/source3/modules/hash_inode.c b/source3/modules/hash_inode.c new file mode 100644 index 0000000..a914462 --- /dev/null +++ b/source3/modules/hash_inode.c @@ -0,0 +1,87 @@ +/* + * Unix SMB/Netbios implementation. + * + * Copyright (c) 2019 Andreas Schneider + * + * 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 "hash_inode.h" + +#include +#include +#include "lib/crypto/gnutls_helpers.h" + +SMB_INO_T hash_inode(const SMB_STRUCT_STAT *sbuf, const char *sname) +{ + gnutls_hash_hd_t hash_hnd = NULL; + uint8_t digest[gnutls_hash_get_len(GNUTLS_DIG_SHA1)]; + char *upper_sname = NULL; + SMB_INO_T result = 0; + int rc; + + DBG_DEBUG("hash_inode called for %ju/%ju [%s]\n", + (uintmax_t)sbuf->st_ex_dev, + (uintmax_t)sbuf->st_ex_ino, + sname); + + upper_sname = talloc_strdup_upper(talloc_tos(), sname); + SMB_ASSERT(upper_sname != NULL); + + GNUTLS_FIPS140_SET_LAX_MODE(); + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA1); + if (rc < 0) { + goto out; + } + + rc = gnutls_hash(hash_hnd, + &(sbuf->st_ex_dev), + sizeof(sbuf->st_ex_dev)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + goto out; + } + rc = gnutls_hash(hash_hnd, + &(sbuf->st_ex_ino), + sizeof(sbuf->st_ex_ino)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + goto out; + } + rc = gnutls_hash(hash_hnd, + upper_sname, + talloc_get_size(upper_sname) - 1); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + goto out; + } + + gnutls_hash_deinit(hash_hnd, digest); + + memcpy(&result, digest, sizeof(result)); + DBG_DEBUG("fruit_inode \"%s\": ino=%ju\n", + sname, (uintmax_t)result); + +out: + GNUTLS_FIPS140_SET_STRICT_MODE(); + TALLOC_FREE(upper_sname); + + DBG_DEBUG("hash_inode '%s': ino=%ju\n", + sname, + (uintmax_t)result); + + return result; +} diff --git a/source3/modules/hash_inode.h b/source3/modules/hash_inode.h new file mode 100644 index 0000000..e08fc48 --- /dev/null +++ b/source3/modules/hash_inode.h @@ -0,0 +1,25 @@ +/* + * Unix SMB/Netbios implementation. + * + * Copyright (c) 2019 Andreas Schneider + * + * 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 . + */ + +#ifndef _HASH_INODE_H +#define _HASH_INODE_H + +SMB_INO_T hash_inode(const SMB_STRUCT_STAT *sbuf, const char *sname); + +#endif /* _HASH_INODE_H */ diff --git a/source3/modules/lib_vxfs.c b/source3/modules/lib_vxfs.c new file mode 100644 index 0000000..e030cc3 --- /dev/null +++ b/source3/modules/lib_vxfs.c @@ -0,0 +1,279 @@ +/* + Unix SMB/CIFS implementation. + Wrap VxFS xattr calls. + + Copyright (C) Veritas Technologies LLC 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 . +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "string.h" +#include "vfs_vxfs.h" + +/* + * Available under GPL at + * http://www.veritas.com/community/downloads/vxfsmisc-library + */ +#define LIBVXFS "/usr/lib64/vxfsmisc.so" + + +static int (*vxfs_setxattr_fd_func) (int fd, const char *name, + const void *value, size_t len, int flags); +static int (*vxfs_getxattr_fd_func) (int fd, const char *name, void *value, + size_t *len); +static int (*vxfs_removexattr_fd_func) (int fd, const char *name); +static int (*vxfs_listxattr_fd_func) (int fd, void *value, size_t *len); +static int (*vxfs_setwxattr_fd_func) (int fd); +static int (*vxfs_checkwxattr_fd_func) (int fd); + +int vxfs_setxattr_fd(int fd, const char *name, const void *value, + size_t len, int flags) +{ + int ret = -1; + + DBG_DEBUG("In vxfs_setxattr_fd fd %d name %s len %zu flags %d\n", + fd, name, len, flags); + if (vxfs_setxattr_fd_func == NULL) { + errno = ENOSYS; + return ret; + } + + DBG_DEBUG("Calling vxfs_setxattr_fd\n"); + ret = vxfs_setxattr_fd_func(fd, name, value, len, flags); + DBG_DEBUG("vxfs_setxattr_fd ret = %d \n", ret); + if (ret) { + errno = ret; + ret = -1; + } + + return ret; +} + +int vxfs_getxattr_fd(int fd, const char *name, void *value, size_t len) +{ + int ret; + size_t size = len; + DBG_DEBUG("In vxfs_getxattr_fd fd %d name %s len %zu\n", + fd, name, len); + + if (vxfs_getxattr_fd_func == NULL) { + errno = ENOSYS; + return -1; + } + + DBG_DEBUG("Calling vxfs_getxattr_fd with %s\n", name); + ret = vxfs_getxattr_fd_func(fd, name, value, &size); + DBG_DEBUG("vxfs_getxattr_fd ret = %d\n", ret); + if (ret) { + errno = ret; + if (ret == EFBIG) { + errno = ERANGE; + } + return -1; + } + DBG_DEBUG("vxfs_getxattr_fd done with size %zu\n", size); + + return size; +} + +int vxfs_getxattr_path(const char *path, const char *name, void *value, + size_t len) +{ + int ret, fd = -1; + DBG_DEBUG("In vxfs_getxattr_path path %s name %s len %zu\n", + path, name, len); + + fd = open(path, O_RDONLY); + if (fd == -1) { + DBG_DEBUG("file not opened: vxfs_getxattr_path for %s\n", + path); + return -1; + } + + ret = vxfs_getxattr_fd(fd, name, value, len); + close(fd); + + return ret; +} + +int vxfs_removexattr_fd(int fd, const char *name) +{ + int ret = 0; + DBG_DEBUG("In vxfs_removexattr_fd fd %d name %s\n", fd, name); + + if (vxfs_removexattr_fd_func == NULL) { + errno = ENOSYS; + return -1; + } + + DBG_DEBUG("Calling vxfs_removexattr_fd with %s\n", name); + ret = vxfs_removexattr_fd_func(fd, name); + if (ret) { + errno = ret; + ret = -1; + } + + return ret; +} + +int vxfs_listxattr_fd(int fd, char *list, size_t size) +{ + int ret; + size_t len = size; + DBG_DEBUG("In vxfs_listxattr_fd fd %d list %s size %zu\n", fd, list, size); + + if (vxfs_listxattr_fd_func == NULL) { + errno = ENOSYS; + return -1; + } + + ret = vxfs_listxattr_fd_func(fd, list, &len); + DBG_DEBUG("vxfs_listxattr_fd: returned ret = %d\n", ret); + DBG_DEBUG("In vxfs_listxattr_fd done with len %zu\n", len); + if (ret) { + errno = ret; + if (ret == EFBIG) { + errno = ERANGE; + } + return -1; + } + + return len; +} + +int vxfs_setwxattr_fd(int fd) +{ + int ret = 0; + DBG_DEBUG("In vxfs_setwxattr_fd fd %d\n", fd); + + if (vxfs_setwxattr_fd_func == NULL) { + errno = ENOSYS; + return -1; + } + ret = vxfs_setwxattr_fd_func(fd); + DBG_DEBUG("ret = %d\n", ret); + if (ret != 0) { + errno = ret; + ret = -1; + } + + return ret; +} + +int vxfs_setwxattr_path(const char *path, bool is_dir) +{ + int ret, fd = -1; + DBG_DEBUG("In vxfs_setwxattr_path path %s is_dir %d\n", path, is_dir); + + if (is_dir) { + fd = open(path, O_RDONLY|O_DIRECTORY); + } else { + fd = open(path, O_WRONLY); + } + if (fd == -1) { + DBG_DEBUG("file %s not opened, errno:%s\n", + path, strerror(errno)); + return -1; + } + + ret = vxfs_setwxattr_fd(fd); + DBG_DEBUG("ret = %d\n", ret); + close(fd); + + return ret; +} + +int vxfs_checkwxattr_fd(int fd) +{ + int ret; + DBG_DEBUG("In vxfs_checkwxattr_fd fd %d\n", fd); + + if (vxfs_checkwxattr_fd_func == NULL) { + errno = ENOSYS; + return -1; + } + ret = vxfs_checkwxattr_fd_func(fd); + DBG_DEBUG("ret = %d\n", ret); + if (ret != 0) { + errno = ret; + ret = -1; + } + return ret; +} + +int vxfs_checkwxattr_path(const char *path) +{ + int ret, fd = -1; + DBG_DEBUG("In vxfs_checkwxattr_path path %s\n", path); + + fd = open(path, O_RDONLY); + + if (fd == -1) { + DBG_DEBUG("file %s not opened, errno:%s\n", + path, strerror(errno)); + return -1; + } + ret = vxfs_checkwxattr_fd(fd); + close(fd); + + return ret; +} + +static bool load_lib_vxfs_function(void *lib_handle, void *fn_ptr, + const char *fnc_name) +{ + void **vlib_handle = (void **)lib_handle; + void **fn_pointer = (void **)fn_ptr; + + *fn_pointer = dlsym(*vlib_handle, fnc_name); + if (*fn_pointer == NULL) { + DEBUG(10, ("Cannot find symbol for %s\n", fnc_name)); + return true; + } + + return false; +} + +void vxfs_init() +{ + static void *lib_handle = NULL; + + if (lib_handle != NULL ) { + return; + } + + lib_handle = dlopen(LIBVXFS, RTLD_LAZY); + if (lib_handle == NULL) { + DEBUG(10, ("Cannot get lib handle\n")); + return; + } + + DEBUG(10, ("Calling vxfs_init\n")); + load_lib_vxfs_function(&lib_handle, &vxfs_setxattr_fd_func, + "vxfs_nxattr_set"); + load_lib_vxfs_function(&lib_handle, &vxfs_getxattr_fd_func, + "vxfs_nxattr_get"); + load_lib_vxfs_function(&lib_handle, &vxfs_removexattr_fd_func, + "vxfs_nxattr_remove"); + load_lib_vxfs_function(&lib_handle, &vxfs_listxattr_fd_func, + "vxfs_nxattr_list"); + load_lib_vxfs_function(&lib_handle, &vxfs_setwxattr_fd_func, + "vxfs_wattr_set"); + load_lib_vxfs_function(&lib_handle, &vxfs_checkwxattr_fd_func, + "vxfs_wattr_check"); + +} diff --git a/source3/modules/nfs41acl.x b/source3/modules/nfs41acl.x new file mode 100644 index 0000000..9b3681d --- /dev/null +++ b/source3/modules/nfs41acl.x @@ -0,0 +1,111 @@ +typedef opaque utf8string<>; +typedef utf8string utf8str_mixed; + +const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; +const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; +const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; +const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; + +typedef u_int acetype4; + +const ACE4_FILE_INHERIT_ACE = 0x00000001; +const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; +const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; +const ACE4_INHERIT_ONLY_ACE = 0x00000008; +const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; +const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; +const ACE4_IDENTIFIER_GROUP = 0x00000040; +const ACE4_INHERITED_ACE = 0x00000080; + +typedef u_int aceflag4; + +/* + * The following aceiflag4 is extensions for RFC 5661 that deals with storing + * identifiers as numerical ids instead UTF8 strings in order to avoid wasting + * CPU cycles for the costly conversion. + * + * Placed in a separate field to avoid ever running into conflicts with newly + * defined NFSv4 flags. + */ + +const ACEI4_SPECIAL_WHO = 0x00000001; + +typedef u_int aceiflag4; + +/* + * Numerical representation of special identifiers from 6.2.1.5. + * ACEI4_SPECIAL_WHO MUST be set in nfsace4.aceiflag4. + */ +const ACE4_SPECIAL_OWNER = 1; +const ACE4_SPECIAL_GROUP = 2; +const ACE4_SPECIAL_EVERYONE = 3; +const ACE4_SPECIAL_INTERACTIVE = 4; +const ACE4_SPECIAL_NETWORK = 5; +const ACE4_SPECIAL_DIALUP = 6; +const ACE4_SPECIAL_BATCH = 7; +const ACE4_SPECIAL_ANONYMOUS = 8; +const ACE4_SPECIAL_AUTHENTICATED = 9; +const ACE4_SPECIAL_SERVICE = 10; + +const ACE4_READ_DATA = 0x00000001; +const ACE4_LIST_DIRECTORY = 0x00000001; +const ACE4_WRITE_DATA = 0x00000002; +const ACE4_ADD_FILE = 0x00000002; +const ACE4_APPEND_DATA = 0x00000004; +const ACE4_ADD_SUBDIRECTORY = 0x00000004; +const ACE4_READ_NAMED_ATTRS = 0x00000008; +const ACE4_WRITE_NAMED_ATTRS = 0x00000010; +const ACE4_EXECUTE = 0x00000020; +const ACE4_DELETE_CHILD = 0x00000040; +const ACE4_READ_ATTRIBUTES = 0x00000080; +const ACE4_WRITE_ATTRIBUTES = 0x00000100; +const ACE4_WRITE_RETENTION = 0x00000200; +const ACE4_WRITE_RETENTION_HOLD = 0x00000400; + +const ACE4_DELETE = 0x00010000; +const ACE4_READ_ACL = 0x00020000; +const ACE4_WRITE_ACL = 0x00040000; +const ACE4_WRITE_OWNER = 0x00080000; +const ACE4_SYNCHRONIZE = 0x00100000; + +typedef u_int acemask4; + +/* ACL structure definition as per RFC 7530 Section-6.2.1 */ +struct nfsace4 { + acetype4 type; + aceflag4 flag; + acemask4 access_mask; + utf8str_mixed who; +}; + +struct nfsace4i { + acetype4 type; + aceflag4 flag; + aceiflag4 iflag; + acemask4 access_mask; + u_int who; +}; + +const ACL4_XATTR_VERSION_40 = 0; +const ACL4_XATTR_VERSION_41 = 1; +const ACL4_XATTR_VERSION_DEFAULT = ACL4_XATTR_VERSION_40; + +const ACL4_AUTO_INHERIT = 0x00000001; +const ACL4_PROTECTED = 0x00000002; +const ACL4_DEFAULTED = 0x00000004; + +typedef u_int aclflag4; + +struct nfsacl40 { + nfsace4 na40_aces<>; +}; + +struct nfsacl41 { + aclflag4 na41_flag; + nfsace4 na41_aces<>; +}; + +struct nfsacl41i { + aclflag4 na41_flag; + nfsace4i na41_aces<>; +}; diff --git a/source3/modules/nfs4_acls.c b/source3/modules/nfs4_acls.c new file mode 100644 index 0000000..c80f839 --- /dev/null +++ b/source3/modules/nfs4_acls.c @@ -0,0 +1,1223 @@ +/* + * NFS4 ACL handling + * + * Copyright (C) Jim McDonough, 2006 + * Copyright (C) Christof Schmitt 2019 + * + * 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 "smbd/smbd.h" +#include "nfs4_acls.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/idmap.h" +#include "../libcli/security/dom_sid.h" +#include "../libcli/security/security.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "system/filesys.h" +#include "passdb/lookup_sid.h" +#include "util_tdb.h" +#include "lib/param/loadparm.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_ACLS + +#define SMBACL4_PARAM_TYPE_NAME "nfs4" + +extern const struct generic_mapping file_generic_mapping; + +struct SMB4ACE_T +{ + SMB_ACE4PROP_T prop; + struct SMB4ACE_T *next; +}; + +struct SMB4ACL_T +{ + uint16_t controlflags; + uint32_t naces; + struct SMB4ACE_T *first; + struct SMB4ACE_T *last; +}; + +/* + * Gather special parameters for NFS4 ACL handling + */ +int smbacl4_get_vfs_params(struct connection_struct *conn, + struct smbacl4_vfs_params *params) +{ + static const struct enum_list enum_smbacl4_modes[] = { + { e_simple, "simple" }, + { e_special, "special" }, + { -1 , NULL } + }; + static const struct enum_list enum_smbacl4_acedups[] = { + { e_dontcare, "dontcare" }, + { e_reject, "reject" }, + { e_ignore, "ignore" }, + { e_merge, "merge" }, + { -1 , NULL } + }; + int enumval; + + *params = (struct smbacl4_vfs_params) { 0 }; + + enumval = lp_parm_enum(SNUM(conn), SMBACL4_PARAM_TYPE_NAME, "mode", + enum_smbacl4_modes, e_simple); + if (enumval == -1) { + DEBUG(10, ("value for %s:mode unknown\n", + SMBACL4_PARAM_TYPE_NAME)); + return -1; + } + params->mode = (enum smbacl4_mode_enum)enumval; + if (params->mode == e_special) { + DBG_WARNING("nfs4:mode special is deprecated.\n"); + } + + params->do_chown = lp_parm_bool(SNUM(conn), SMBACL4_PARAM_TYPE_NAME, + "chown", true); + + enumval = lp_parm_enum(SNUM(conn), SMBACL4_PARAM_TYPE_NAME, "acedup", + enum_smbacl4_acedups, e_merge); + if (enumval == -1) { + DEBUG(10, ("value for %s:acedup unknown\n", + SMBACL4_PARAM_TYPE_NAME)); + return -1; + } + params->acedup = (enum smbacl4_acedup_enum)enumval; + if (params->acedup == e_ignore) { + DBG_WARNING("nfs4:acedup ignore is deprecated.\n"); + } + if (params->acedup == e_reject) { + DBG_WARNING("nfs4:acedup ignore is deprecated.\n"); + } + + params->map_full_control = lp_acl_map_full_control(SNUM(conn)); + + DEBUG(10, ("mode:%s, do_chown:%s, acedup: %s map full control:%s\n", + enum_smbacl4_modes[params->mode].name, + params->do_chown ? "true" : "false", + enum_smbacl4_acedups[params->acedup].name, + params->map_full_control ? "true" : "false")); + + return 0; +} + +static int fstatat_with_cap_dac_override(int fd, + const char *pathname, + SMB_STRUCT_STAT *sbuf, + int flags, + bool fake_dir_create_times) +{ + int ret; + + set_effective_capability(DAC_OVERRIDE_CAPABILITY); + ret = sys_fstatat(fd, + pathname, + sbuf, + flags, + fake_dir_create_times); + drop_effective_capability(DAC_OVERRIDE_CAPABILITY); + + return ret; +} + +static int stat_with_cap_dac_override(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname, int flag) +{ + bool fake_dctime = lp_fake_directory_create_times(SNUM(handle->conn)); + int fd = -1; + NTSTATUS status; + struct smb_filename *dir_name = NULL; + struct smb_filename *rel_name = NULL; + int ret = -1; +#ifdef O_PATH + int open_flags = O_PATH; +#else + int open_flags = O_RDONLY; +#endif + + status = SMB_VFS_PARENT_PATHNAME(handle->conn, + talloc_tos(), + smb_fname, + &dir_name, + &rel_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + fd = open(dir_name->base_name, open_flags, 0); + if (fd == -1) { + TALLOC_FREE(dir_name); + return -1; + } + + ret = fstatat_with_cap_dac_override(fd, + rel_name->base_name, + &smb_fname->st, + flag, + fake_dctime); + + TALLOC_FREE(dir_name); + close(fd); + + return ret; +} + +int nfs4_acl_stat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret == -1 && errno == EACCES) { + DEBUG(10, ("Trying stat with capability for %s\n", + smb_fname->base_name)); + ret = stat_with_cap_dac_override(handle, smb_fname, 0); + } + return ret; +} + +static int fstat_with_cap_dac_override(int fd, SMB_STRUCT_STAT *sbuf, + bool fake_dir_create_times) +{ + int ret; + + set_effective_capability(DAC_OVERRIDE_CAPABILITY); + ret = sys_fstat(fd, sbuf, fake_dir_create_times); + drop_effective_capability(DAC_OVERRIDE_CAPABILITY); + + return ret; +} + +int nfs4_acl_fstat(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int ret; + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == -1 && errno == EACCES) { + bool fake_dctime = + lp_fake_directory_create_times(SNUM(handle->conn)); + + DBG_DEBUG("fstat for %s failed with EACCES. Trying with " + "CAP_DAC_OVERRIDE.\n", fsp->fsp_name->base_name); + ret = fstat_with_cap_dac_override(fsp_get_pathref_fd(fsp), + sbuf, + fake_dctime); + } + + return ret; +} + +int nfs4_acl_lstat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret == -1 && errno == EACCES) { + DEBUG(10, ("Trying lstat with capability for %s\n", + smb_fname->base_name)); + ret = stat_with_cap_dac_override(handle, smb_fname, + AT_SYMLINK_NOFOLLOW); + } + return ret; +} + +int nfs4_acl_fstatat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + int ret; + + ret = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags); + if (ret == -1 && errno == EACCES) { + bool fake_dctime = + lp_fake_directory_create_times(SNUM(handle->conn)); + + DBG_DEBUG("fstatat for %s failed with EACCES. Trying with " + "CAP_DAC_OVERRIDE.\n", dirfsp->fsp_name->base_name); + ret = fstatat_with_cap_dac_override(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + sbuf, + flags, + fake_dctime); + } + + return ret; +} + +/************************************************ + Split the ACE flag mapping between nfs4 and Windows + into two separate functions rather than trying to do + it inline. Allows us to carefully control what flags + are mapped to what in one place. +************************************************/ + +static uint32_t map_nfs4_ace_flags_to_windows_ace_flags( + uint32_t nfs4_ace_flags) +{ + uint32_t win_ace_flags = 0; + + /* The nfs4 flags <= 0xf map perfectly. */ + win_ace_flags = nfs4_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY); + + /* flags greater than 0xf have diverged :-(. */ + /* See the nfs4 ace flag definitions here: + http://www.ietf.org/rfc/rfc3530.txt. + And the Windows ace flag definitions here: + librpc/idl/security.idl. */ + if (nfs4_ace_flags & SMB_ACE4_INHERITED_ACE) { + win_ace_flags |= SEC_ACE_FLAG_INHERITED_ACE; + } + + return win_ace_flags; +} + +static uint32_t map_windows_ace_flags_to_nfs4_ace_flags(uint32_t win_ace_flags) +{ + uint32_t nfs4_ace_flags = 0; + + /* The windows flags <= 0xf map perfectly. */ + nfs4_ace_flags = win_ace_flags & (SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_NO_PROPAGATE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + + /* flags greater than 0xf have diverged :-(. */ + /* See the nfs4 ace flag definitions here: + http://www.ietf.org/rfc/rfc3530.txt. + And the Windows ace flag definitions here: + librpc/idl/security.idl. */ + if (win_ace_flags & SEC_ACE_FLAG_INHERITED_ACE) { + nfs4_ace_flags |= SMB_ACE4_INHERITED_ACE; + } + + return nfs4_ace_flags; +} + +struct SMB4ACL_T *smb_create_smb4acl(TALLOC_CTX *mem_ctx) +{ + struct SMB4ACL_T *theacl; + + theacl = talloc_zero(mem_ctx, struct SMB4ACL_T); + if (theacl==NULL) + { + DEBUG(0, ("TALLOC_SIZE failed\n")); + errno = ENOMEM; + return NULL; + } + theacl->controlflags = SEC_DESC_SELF_RELATIVE; + /* theacl->first, last = NULL not needed */ + return theacl; +} + +struct SMB4ACE_T *smb_add_ace4(struct SMB4ACL_T *acl, SMB_ACE4PROP_T *prop) +{ + struct SMB4ACE_T *ace; + + ace = talloc_zero(acl, struct SMB4ACE_T); + if (ace==NULL) + { + DBG_ERR("talloc_zero failed\n"); + errno = ENOMEM; + return NULL; + } + ace->prop = *prop; + + if (acl->first==NULL) + { + acl->first = ace; + acl->last = ace; + } else { + acl->last->next = ace; + acl->last = ace; + } + acl->naces++; + + return ace; +} + +SMB_ACE4PROP_T *smb_get_ace4(struct SMB4ACE_T *ace) +{ + if (ace == NULL) { + return NULL; + } + + return &ace->prop; +} + +struct SMB4ACE_T *smb_next_ace4(struct SMB4ACE_T *ace) +{ + if (ace == NULL) { + return NULL; + } + + return ace->next; +} + +struct SMB4ACE_T *smb_first_ace4(struct SMB4ACL_T *acl) +{ + if (acl == NULL) { + return NULL; + } + + return acl->first; +} + +uint32_t smb_get_naces(struct SMB4ACL_T *acl) +{ + if (acl == NULL) { + return 0; + } + + return acl->naces; +} + +uint16_t smbacl4_get_controlflags(struct SMB4ACL_T *acl) +{ + if (acl == NULL) { + return 0; + } + + return acl->controlflags; +} + +bool smbacl4_set_controlflags(struct SMB4ACL_T *acl, uint16_t controlflags) +{ + if (acl == NULL) { + return false; + } + + acl->controlflags = controlflags; + return true; +} + +bool nfs_ace_is_inherit(SMB_ACE4PROP_T *ace) +{ + return ace->aceFlags & (SMB_ACE4_INHERIT_ONLY_ACE| + SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_DIRECTORY_INHERIT_ACE); +} + +static int smbacl4_GetFileOwner(struct connection_struct *conn, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *psbuf) +{ + ZERO_STRUCTP(psbuf); + + /* Get the stat struct for the owner info. */ + if (vfs_stat_smb_basename(conn, smb_fname, psbuf) != 0) + { + DEBUG(8, ("vfs_stat_smb_basename failed with error %s\n", + strerror(errno))); + return -1; + } + + return 0; +} + +static void check_for_duplicate_sec_ace(struct security_ace *nt_ace_list, + int *good_aces) +{ + struct security_ace *last = NULL; + int i; + + if (*good_aces < 2) { + return; + } + + last = &nt_ace_list[(*good_aces) - 1]; + + for (i = 0; i < (*good_aces) - 1; i++) { + struct security_ace *cur = &nt_ace_list[i]; + + if (cur->type == last->type && + cur->flags == last->flags && + cur->access_mask == last->access_mask && + dom_sid_equal(&cur->trustee, &last->trustee)) + { + struct dom_sid_buf sid_buf; + + DBG_INFO("Removing duplicate entry for SID %s.\n", + dom_sid_str_buf(&last->trustee, &sid_buf)); + (*good_aces)--; + } + } +} + +static bool smbacl4_nfs42win(TALLOC_CTX *mem_ctx, + const struct smbacl4_vfs_params *params, + struct SMB4ACL_T *acl, /* in */ + struct dom_sid *psid_owner, /* in */ + struct dom_sid *psid_group, /* in */ + bool is_directory, /* in */ + struct security_ace **ppnt_ace_list, /* out */ + int *pgood_aces /* out */ +) +{ + struct SMB4ACE_T *aceint; + struct security_ace *nt_ace_list = NULL; + int good_aces = 0; + + DEBUG(10, ("%s entered\n", __func__)); + + nt_ace_list = talloc_zero_array(mem_ctx, struct security_ace, + 2 * acl->naces); + if (nt_ace_list==NULL) + { + DEBUG(10, ("talloc error with %d aces\n", acl->naces)); + errno = ENOMEM; + return false; + } + + for (aceint = acl->first; aceint != NULL; aceint = aceint->next) { + uint32_t mask; + struct dom_sid sid; + struct dom_sid_buf buf; + SMB_ACE4PROP_T *ace = &aceint->prop; + uint32_t win_ace_flags; + + DEBUG(10, ("type: %d, iflags: %x, flags: %x, " + "mask: %x, who: %d\n", + ace->aceType, ace->flags, + ace->aceFlags, ace->aceMask, ace->who.id)); + + if (ace->flags & SMB_ACE4_ID_SPECIAL) { + switch (ace->who.special_id) { + case SMB_ACE4_WHO_OWNER: + sid_copy(&sid, psid_owner); + break; + case SMB_ACE4_WHO_GROUP: + sid_copy(&sid, psid_group); + break; + case SMB_ACE4_WHO_EVERYONE: + sid_copy(&sid, &global_sid_World); + break; + default: + DEBUG(8, ("invalid special who id %d " + "ignored\n", ace->who.special_id)); + continue; + } + } else { + if (ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + gid_to_sid(&sid, ace->who.gid); + } else { + uid_to_sid(&sid, ace->who.uid); + } + } + DEBUG(10, ("mapped %d to %s\n", ace->who.id, + dom_sid_str_buf(&sid, &buf))); + + if (!is_directory && params->map_full_control) { + /* + * Do we have all access except DELETE_CHILD + * (not caring about the delete bit). + */ + uint32_t test_mask = ((ace->aceMask|SMB_ACE4_DELETE|SMB_ACE4_DELETE_CHILD) & + SMB_ACE4_ALL_MASKS); + if (test_mask == SMB_ACE4_ALL_MASKS) { + ace->aceMask |= SMB_ACE4_DELETE_CHILD; + } + } + + win_ace_flags = map_nfs4_ace_flags_to_windows_ace_flags( + ace->aceFlags); + if (!is_directory && + (win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_CONTAINER_INHERIT))) { + /* + * GPFS sets inherits dir_inherit and file_inherit flags + * to files, too, which confuses windows, and seems to + * be wrong anyways. ==> Map these bits away for files. + */ + DEBUG(10, ("removing inherit flags from nfs4 ace\n")); + win_ace_flags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_CONTAINER_INHERIT); + } + DEBUG(10, ("Windows mapped ace flags: 0x%x => 0x%x\n", + ace->aceFlags, win_ace_flags)); + + mask = ace->aceMask; + + /* Mapping of owner@ and group@ to creator owner and + creator group. Keep old behavior in mode special. */ + if (params->mode != e_special && + ace->flags & SMB_ACE4_ID_SPECIAL && + (ace->who.special_id == SMB_ACE4_WHO_OWNER || + ace->who.special_id == SMB_ACE4_WHO_GROUP)) { + DEBUG(10, ("Map special entry\n")); + if (!(win_ace_flags & SEC_ACE_FLAG_INHERIT_ONLY)) { + uint32_t win_ace_flags_current; + DEBUG(10, ("Map current sid\n")); + win_ace_flags_current = win_ace_flags & + ~(SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT); + init_sec_ace(&nt_ace_list[good_aces++], &sid, + ace->aceType, mask, + win_ace_flags_current); + } + if (ace->who.special_id == SMB_ACE4_WHO_OWNER && + win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT)) { + uint32_t win_ace_flags_creator; + DEBUG(10, ("Map creator owner\n")); + win_ace_flags_creator = win_ace_flags | + SMB_ACE4_INHERIT_ONLY_ACE; + init_sec_ace(&nt_ace_list[good_aces++], + &global_sid_Creator_Owner, + ace->aceType, mask, + win_ace_flags_creator); + } + if (ace->who.special_id == SMB_ACE4_WHO_GROUP && + win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT)) { + uint32_t win_ace_flags_creator; + DEBUG(10, ("Map creator owner group\n")); + win_ace_flags_creator = win_ace_flags | + SMB_ACE4_INHERIT_ONLY_ACE; + init_sec_ace(&nt_ace_list[good_aces++], + &global_sid_Creator_Group, + ace->aceType, mask, + win_ace_flags_creator); + } + } else { + DEBUG(10, ("Map normal sid\n")); + init_sec_ace(&nt_ace_list[good_aces++], &sid, + ace->aceType, mask, + win_ace_flags); + } + + check_for_duplicate_sec_ace(nt_ace_list, &good_aces); + } + + nt_ace_list = talloc_realloc(mem_ctx, nt_ace_list, struct security_ace, + good_aces); + + /* returns a NULL ace list when good_aces is zero. */ + if (good_aces && nt_ace_list == NULL) { + DEBUG(10, ("realloc error with %d aces\n", good_aces)); + errno = ENOMEM; + return false; + } + + *ppnt_ace_list = nt_ace_list; + *pgood_aces = good_aces; + + return true; +} + +static NTSTATUS smb_get_nt_acl_nfs4_common(const SMB_STRUCT_STAT *sbuf, + const struct smbacl4_vfs_params *params, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, + struct SMB4ACL_T *theacl) +{ + int good_aces = 0; + struct dom_sid sid_owner, sid_group; + size_t sd_size = 0; + struct security_ace *nt_ace_list = NULL; + struct security_acl *psa = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + bool ok; + + if (theacl==NULL) { + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; /* special because we + * need to think through + * the null case.*/ + } + + uid_to_sid(&sid_owner, sbuf->st_ex_uid); + gid_to_sid(&sid_group, sbuf->st_ex_gid); + + ok = smbacl4_nfs42win(frame, params, theacl, &sid_owner, &sid_group, + S_ISDIR(sbuf->st_ex_mode), + &nt_ace_list, &good_aces); + if (!ok) { + DEBUG(8,("smbacl4_nfs42win failed\n")); + TALLOC_FREE(frame); + return map_nt_error_from_unix(errno); + } + + psa = make_sec_acl(frame, NT4_ACL_REVISION, good_aces, nt_ace_list); + if (psa == NULL) { + DEBUG(2,("make_sec_acl failed\n")); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10,("after make sec_acl\n")); + *ppdesc = make_sec_desc( + mem_ctx, SD_REVISION, smbacl4_get_controlflags(theacl), + (security_info & SECINFO_OWNER) ? &sid_owner : NULL, + (security_info & SECINFO_GROUP) ? &sid_group : NULL, + NULL, psa, &sd_size); + if (*ppdesc==NULL) { + DEBUG(2,("make_sec_desc failed\n")); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10, ("smb_get_nt_acl_nfs4_common successfully exited with " + "sd_size %d\n", + (int)ndr_size_security_descriptor(*ppdesc, 0))); + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS smb_fget_nt_acl_nfs4(files_struct *fsp, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, + struct SMB4ACL_T *theacl) +{ + struct smbacl4_vfs_params params; + + DEBUG(10, ("smb_fget_nt_acl_nfs4 invoked for %s\n", fsp_str_dbg(fsp))); + + if (!VALID_STAT(fsp->fsp_name->st)) { + NTSTATUS status; + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (pparams == NULL) { + /* Special behaviours */ + if (smbacl4_get_vfs_params(fsp->conn, ¶ms)) { + return NT_STATUS_NO_MEMORY; + } + pparams = ¶ms; + } + + return smb_get_nt_acl_nfs4_common(&fsp->fsp_name->st, pparams, + security_info, + mem_ctx, ppdesc, theacl); +} + +NTSTATUS smb_get_nt_acl_nfs4(struct connection_struct *conn, + const struct smb_filename *smb_fname, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, + struct SMB4ACL_T *theacl) +{ + SMB_STRUCT_STAT sbuf; + struct smbacl4_vfs_params params; + const SMB_STRUCT_STAT *psbuf = NULL; + + DEBUG(10, ("smb_get_nt_acl_nfs4 invoked for %s\n", + smb_fname->base_name)); + + if (VALID_STAT(smb_fname->st)) { + psbuf = &smb_fname->st; + } + + if (psbuf == NULL) { + if (smbacl4_GetFileOwner(conn, smb_fname, &sbuf)) { + return map_nt_error_from_unix(errno); + } + psbuf = &sbuf; + } + + if (pparams == NULL) { + /* Special behaviours */ + if (smbacl4_get_vfs_params(conn, ¶ms)) { + return NT_STATUS_NO_MEMORY; + } + pparams = ¶ms; + } + + return smb_get_nt_acl_nfs4_common(psbuf, pparams, security_info, + mem_ctx, ppdesc, theacl); +} + +static void smbacl4_dump_nfs4acl(int level, struct SMB4ACL_T *acl) +{ + struct SMB4ACE_T *aceint; + + DEBUG(level, ("NFS4ACL: size=%d\n", acl->naces)); + + for (aceint = acl->first; aceint != NULL; aceint = aceint->next) { + SMB_ACE4PROP_T *ace = &aceint->prop; + + DEBUG(level, ("\tACE: type=%d, flags=0x%x, fflags=0x%x, " + "mask=0x%x, id=%d\n", + ace->aceType, + ace->aceFlags, ace->flags, + ace->aceMask, + ace->who.id)); + } +} + +/* + * Find 2 NFS4 who-special ACE property (non-copy!!!) + * match nonzero if "special" and who is equal + * return ace if found matching; otherwise NULL + */ +static SMB_ACE4PROP_T *smbacl4_find_equal_special( + struct SMB4ACL_T *acl, + SMB_ACE4PROP_T *aceNew) +{ + struct SMB4ACE_T *aceint; + + for (aceint = acl->first; aceint != NULL; aceint = aceint->next) { + SMB_ACE4PROP_T *ace = &aceint->prop; + + DEBUG(10,("ace type:0x%x flags:0x%x aceFlags:0x%x " + "new type:0x%x flags:0x%x aceFlags:0x%x\n", + ace->aceType, ace->flags, ace->aceFlags, + aceNew->aceType, aceNew->flags,aceNew->aceFlags)); + + if (ace->flags == aceNew->flags && + ace->aceType==aceNew->aceType && + ace->aceFlags==aceNew->aceFlags) + { + /* keep type safety; e.g. gid is an u.short */ + if (ace->flags & SMB_ACE4_ID_SPECIAL) + { + if (ace->who.special_id == + aceNew->who.special_id) + return ace; + } else { + if (ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) + { + if (ace->who.gid==aceNew->who.gid) + return ace; + } else { + if (ace->who.uid==aceNew->who.uid) + return ace; + } + } + } + } + + return NULL; +} + +static int smbacl4_MergeIgnoreReject(enum smbacl4_acedup_enum acedup, + struct SMB4ACL_T *theacl, + SMB_ACE4PROP_T *ace, + bool *paddNewACE) +{ + int result = 0; + SMB_ACE4PROP_T *ace4found = smbacl4_find_equal_special(theacl, ace); + if (ace4found) + { + switch(acedup) + { + case e_merge: /* "merge" flags */ + *paddNewACE = false; + ace4found->aceFlags |= ace->aceFlags; + ace4found->aceMask |= ace->aceMask; + break; + case e_ignore: /* leave out this record */ + *paddNewACE = false; + break; + case e_reject: /* do an error */ + DBG_INFO("ACL rejected by duplicate nt ace.\n"); + errno = EINVAL; /* SHOULD be set on any _real_ error */ + result = -1; + break; + default: + break; + } + } + return result; +} + +static int nfs4_acl_add_ace(enum smbacl4_acedup_enum acedup, + struct SMB4ACL_T *nfs4_acl, + SMB_ACE4PROP_T *nfs4_ace) +{ + bool add_ace = true; + + if (acedup != e_dontcare) { + int ret; + + ret = smbacl4_MergeIgnoreReject(acedup, nfs4_acl, + nfs4_ace, &add_ace); + if (ret == -1) { + return -1; + } + } + + if (add_ace) { + smb_add_ace4(nfs4_acl, nfs4_ace); + } + + return 0; +} + +static int nfs4_acl_add_sec_ace(bool is_directory, + const struct smbacl4_vfs_params *params, + uid_t ownerUID, + gid_t ownerGID, + const struct security_ace *ace_nt, + struct SMB4ACL_T *nfs4_acl) +{ + struct dom_sid_buf buf; + SMB_ACE4PROP_T nfs4_ace = { 0 }; + SMB_ACE4PROP_T nfs4_ace_2 = { 0 }; + bool add_ace2 = false; + int ret; + + DEBUG(10, ("got ace for %s\n", + dom_sid_str_buf(&ace_nt->trustee, &buf))); + + /* only ACCESS|DENY supported right now */ + nfs4_ace.aceType = ace_nt->type; + + nfs4_ace.aceFlags = + map_windows_ace_flags_to_nfs4_ace_flags(ace_nt->flags); + + /* remove inheritance flags on files */ + if (!is_directory) { + DEBUG(10, ("Removing inheritance flags from a file\n")); + nfs4_ace.aceFlags &= ~(SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_NO_PROPAGATE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + } + + nfs4_ace.aceMask = ace_nt->access_mask & (SEC_STD_ALL | SEC_FILE_ALL); + + se_map_generic(&nfs4_ace.aceMask, &file_generic_mapping); + + if (dom_sid_equal(&ace_nt->trustee, &global_sid_World)) { + nfs4_ace.who.special_id = SMB_ACE4_WHO_EVERYONE; + nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL; + } else if (params->mode!=e_special && + dom_sid_equal(&ace_nt->trustee, + &global_sid_Creator_Owner)) { + DEBUG(10, ("Map creator owner\n")); + nfs4_ace.who.special_id = SMB_ACE4_WHO_OWNER; + nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL; + /* A non inheriting creator owner entry has no effect. */ + nfs4_ace.aceFlags |= SMB_ACE4_INHERIT_ONLY_ACE; + if (!(nfs4_ace.aceFlags & SMB_ACE4_DIRECTORY_INHERIT_ACE) + && !(nfs4_ace.aceFlags & SMB_ACE4_FILE_INHERIT_ACE)) { + return 0; + } + } else if (params->mode!=e_special && + dom_sid_equal(&ace_nt->trustee, + &global_sid_Creator_Group)) { + DEBUG(10, ("Map creator owner group\n")); + nfs4_ace.who.special_id = SMB_ACE4_WHO_GROUP; + nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL; + /* A non inheriting creator group entry has no effect. */ + nfs4_ace.aceFlags |= SMB_ACE4_INHERIT_ONLY_ACE; + if (!(nfs4_ace.aceFlags & SMB_ACE4_DIRECTORY_INHERIT_ACE) + && !(nfs4_ace.aceFlags & SMB_ACE4_FILE_INHERIT_ACE)) { + return 0; + } + } else { + struct unixid unixid; + bool ok; + + ok = sids_to_unixids(&ace_nt->trustee, 1, &unixid); + if (!ok) { + DBG_WARNING("Could not convert %s to uid or gid.\n", + dom_sid_str_buf(&ace_nt->trustee, &buf)); + return 0; + } + + if (dom_sid_compare_domain(&ace_nt->trustee, + &global_sid_Unix_NFS) == 0) { + return 0; + } + + switch (unixid.type) { + case ID_TYPE_BOTH: + nfs4_ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP; + nfs4_ace.who.gid = unixid.id; + + if (ownerUID == unixid.id && + !nfs_ace_is_inherit(&nfs4_ace)) + { + /* + * IDMAP_TYPE_BOTH for owner. Add + * additional user entry, which can be + * mapped to special:owner to reflect + * the permissions in the modebits. + * + * This only applies to non-inheriting + * entries as only these are replaced + * with SPECIAL_OWNER in nfs4:mode=simple. + */ + nfs4_ace_2 = (SMB_ACE4PROP_T) { + .who.uid = unixid.id, + .aceFlags = (nfs4_ace.aceFlags & + ~SMB_ACE4_IDENTIFIER_GROUP), + .aceMask = nfs4_ace.aceMask, + .aceType = nfs4_ace.aceType, + }; + add_ace2 = true; + } + break; + case ID_TYPE_GID: + nfs4_ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP; + nfs4_ace.who.gid = unixid.id; + break; + case ID_TYPE_UID: + nfs4_ace.who.uid = unixid.id; + break; + case ID_TYPE_NOT_SPECIFIED: + default: + DBG_WARNING("Could not convert %s to uid or gid.\n", + dom_sid_str_buf(&ace_nt->trustee, &buf)); + return 0; + } + } + + ret = nfs4_acl_add_ace(params->acedup, nfs4_acl, &nfs4_ace); + if (ret != 0) { + return -1; + } + + if (!add_ace2) { + return 0; + } + + return nfs4_acl_add_ace(params->acedup, nfs4_acl, &nfs4_ace_2); +} + +static void smbacl4_substitute_special(struct SMB4ACL_T *acl, + uid_t ownerUID, + gid_t ownerGID) +{ + struct SMB4ACE_T *aceint; + + for (aceint = acl->first; aceint != NULL; aceint = aceint->next) { + SMB_ACE4PROP_T *ace = &aceint->prop; + + DEBUG(10,("ace type: %d, iflags: %x, flags: %x, " + "mask: %x, who: %d\n", + ace->aceType, ace->flags, ace->aceFlags, + ace->aceMask, ace->who.id)); + + if (!(ace->flags & SMB_ACE4_ID_SPECIAL) && + !(ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) && + ace->who.uid == ownerUID) { + ace->flags |= SMB_ACE4_ID_SPECIAL; + ace->who.special_id = SMB_ACE4_WHO_OWNER; + DEBUG(10,("replaced with special owner ace\n")); + } + + if (!(ace->flags & SMB_ACE4_ID_SPECIAL) && + ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP && + ace->who.uid == ownerGID) { + ace->flags |= SMB_ACE4_ID_SPECIAL; + ace->who.special_id = SMB_ACE4_WHO_GROUP; + DEBUG(10,("replaced with special group ace\n")); + } + } +} + +static void smbacl4_substitute_simple(struct SMB4ACL_T *acl, + uid_t ownerUID, + gid_t ownerGID) +{ + struct SMB4ACE_T *aceint; + + for (aceint = acl->first; aceint != NULL; aceint = aceint->next) { + SMB_ACE4PROP_T *ace = &aceint->prop; + + DEBUG(10,("ace type: %d, iflags: %x, flags: %x, " + "mask: %x, who: %d\n", + ace->aceType, ace->flags, ace->aceFlags, + ace->aceMask, ace->who.id)); + + if (!(ace->flags & SMB_ACE4_ID_SPECIAL) && + !(ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) && + ace->who.uid == ownerUID && + !nfs_ace_is_inherit(ace)) { + ace->flags |= SMB_ACE4_ID_SPECIAL; + ace->who.special_id = SMB_ACE4_WHO_OWNER; + DEBUG(10,("replaced with special owner ace\n")); + } + + if (!(ace->flags & SMB_ACE4_ID_SPECIAL) && + ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP && + ace->who.gid == ownerGID && + !nfs_ace_is_inherit(ace)) { + ace->flags |= SMB_ACE4_ID_SPECIAL; + ace->who.special_id = SMB_ACE4_WHO_GROUP; + DEBUG(10,("replaced with special group ace\n")); + } + } +} + +static struct SMB4ACL_T *smbacl4_win2nfs4( + TALLOC_CTX *mem_ctx, + bool is_directory, + const struct security_acl *dacl, + const struct smbacl4_vfs_params *pparams, + uid_t ownerUID, + gid_t ownerGID +) +{ + struct SMB4ACL_T *theacl; + uint32_t i; + + DEBUG(10, ("smbacl4_win2nfs4 invoked\n")); + + theacl = smb_create_smb4acl(mem_ctx); + if (theacl==NULL) + return NULL; + + for(i=0; inum_aces; i++) { + int ret; + + ret = nfs4_acl_add_sec_ace(is_directory, pparams, + ownerUID, ownerGID, + dacl->aces + i, theacl); + if (ret == -1) { + return NULL; + } + } + + if (pparams->mode==e_simple) { + smbacl4_substitute_simple(theacl, ownerUID, ownerGID); + } + + if (pparams->mode==e_special) { + smbacl4_substitute_special(theacl, ownerUID, ownerGID); + } + + return theacl; +} + +NTSTATUS smb_set_nt_acl_nfs4(vfs_handle_struct *handle, files_struct *fsp, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info_sent, + const struct security_descriptor *psd, + set_nfs4acl_native_fn_t set_nfs4_native) +{ + struct smbacl4_vfs_params params; + struct SMB4ACL_T *theacl = NULL; + bool result, is_directory; + + bool set_acl_as_root = false; + int saved_errno; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + + DEBUG(10, ("smb_set_nt_acl_nfs4 invoked for %s\n", fsp_str_dbg(fsp))); + + if ((security_info_sent & (SECINFO_DACL | + SECINFO_GROUP | SECINFO_OWNER)) == 0) + { + DEBUG(9, ("security_info_sent (0x%x) ignored\n", + security_info_sent)); + TALLOC_FREE(frame); + return NT_STATUS_OK; /* won't show error - later to be + * refined... */ + } + + if (security_descriptor_with_ms_nfs(psd)) { + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + + if (pparams == NULL) { + /* Special behaviours */ + if (smbacl4_get_vfs_params(fsp->conn, ¶ms)) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + pparams = ¶ms; + } + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode); + + if (pparams->do_chown) { + /* + * When the chown succeeds, the special entries in the + * file system ACL refer to the new owner. In order to + * apply the complete information from the DACL, + * setting the ACL then has to succeed. Track this + * case with set_acl_as_root and set the ACL as root + * accordingly. + */ + status = chown_if_needed(fsp, security_info_sent, psd, + &set_acl_as_root); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + } + + if (!(security_info_sent & SECINFO_DACL) || psd->dacl ==NULL) { + DEBUG(10, ("no dacl found; security_info_sent = 0x%x\n", + security_info_sent)); + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + + theacl = smbacl4_win2nfs4(frame, is_directory, psd->dacl, pparams, + fsp->fsp_name->st.st_ex_uid, + fsp->fsp_name->st.st_ex_gid); + if (!theacl) { + TALLOC_FREE(frame); + return map_nt_error_from_unix(errno); + } + + smbacl4_set_controlflags(theacl, psd->type); + smbacl4_dump_nfs4acl(10, theacl); + + if (set_acl_as_root) { + become_root(); + } + result = set_nfs4_native(handle, fsp, theacl); + saved_errno = errno; + if (set_acl_as_root) { + unbecome_root(); + } + + TALLOC_FREE(frame); + + if (result!=true) { + errno = saved_errno; + DEBUG(10, ("set_nfs4_native failed with %s\n", + strerror(errno))); + return map_nt_error_from_unix(errno); + } + + DEBUG(10, ("smb_set_nt_acl_nfs4 succeeded\n")); + return NT_STATUS_OK; +} diff --git a/source3/modules/nfs4_acls.h b/source3/modules/nfs4_acls.h new file mode 100644 index 0000000..011b9da --- /dev/null +++ b/source3/modules/nfs4_acls.h @@ -0,0 +1,184 @@ +/* + * NFS4 ACL handling + * + * Copyright (C) Jim McDonough, 2006 + * Reused & renamed some parts of AIX 5.3 sys/acl.h structures + * + * 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 . + */ + +#ifndef __NFS4_ACLS_H__ +#define __NFS4_ACLS_H__ + +/* + * Following union captures the identity as + * used in the NFS4 ACL structures. + */ +typedef union _SMB_NFS4_ACEWHOID_T { + uid_t uid; /* User id */ + gid_t gid; /* Group id */ + uint32_t special_id; /* Identifies special identities in NFS4 */ + +#define SMB_ACE4_WHO_OWNER 0x00000001 /*The owner of the file. */ +#define SMB_ACE4_WHO_GROUP 0x00000002 /*The group associated with the file. */ +#define SMB_ACE4_WHO_EVERYONE 0x00000003 /*The world. */ +#define SMB_ACE4_WHO_INTERACTIVE 0x00000004 /*Accessed from an interactive terminal. */ +#define SMB_ACE4_WHO_NETWORK 0x00000005 /*Accessed via the network. */ +#define SMB_ACE4_WHO_DIALUP 0x00000006 /*Accessed as a dialup user to the server. */ +#define SMB_ACE4_WHO_BATCH 0x00000007 /*Accessed from a batch job. */ +#define SMB_ACE4_WHO_ANONYMOUS 0x00000008 /*Accessed without any authentication. */ +#define SMB_ACE4_WHO_AUTHENTICATED 0x00000009 /*Any authenticated user (opposite of ANONYMOUS) */ +#define SMB_ACE4_WHO_SERVICE 0x0000000A /*Access from a system service. */ +#define SMB_ACE4_WHO_MAX SMB_ACE4_WHO_SERVICE /* largest valid ACE4_WHO */ + uint32_t id; +} SMB_NFS4_ACEWHOID_T; + +typedef struct _SMB_ACE4PROP_T { + uint32_t flags; /* Bit mask defining details of ACE */ +/*The following are constants for flags field */ +/* #define SMB_ACE4_ID_NOT_VALID 0x00000001 - from aix/jfs2 */ +#define SMB_ACE4_ID_SPECIAL 0x00000002 + + SMB_NFS4_ACEWHOID_T who; /* Identifies to whom this ACE applies */ + + /* The following part of ACE has the same layout as NFSv4 wire format. */ + + uint32_t aceType; /* Type of ACE PERMIT/ALLOW etc*/ +/*The constants used for the type field (acetype4) are as follows: */ +#define SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE 0x00000000 +#define SMB_ACE4_ACCESS_DENIED_ACE_TYPE 0x00000001 +#define SMB_ACE4_SYSTEM_AUDIT_ACE_TYPE 0x00000002 +#define SMB_ACE4_SYSTEM_ALARM_ACE_TYPE 0x00000003 +#define SMB_ACE4_MAX_TYPE SMB_ACE4_SYSTEM_ALARM_ACE_TYPE /* largest valid ACE4_TYPE */ + + uint32_t aceFlags; /* Controls Inheritance and such */ +/*The bitmask constants used for the flag field are as follows: */ +#define SMB_ACE4_FILE_INHERIT_ACE 0x00000001 +#define SMB_ACE4_DIRECTORY_INHERIT_ACE 0x00000002 +#define SMB_ACE4_NO_PROPAGATE_INHERIT_ACE 0x00000004 +#define SMB_ACE4_INHERIT_ONLY_ACE 0x00000008 +#define SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 +#define SMB_ACE4_FAILED_ACCESS_ACE_FLAG 0x00000020 +#define SMB_ACE4_IDENTIFIER_GROUP 0x00000040 +#define SMB_ACE4_INHERITED_ACE 0x00000080 +#define SMB_ACE4_ALL_FLAGS ( SMB_ACE4_FILE_INHERIT_ACE | SMB_ACE4_DIRECTORY_INHERIT_ACE \ +| SMB_ACE4_NO_PROPAGATE_INHERIT_ACE | SMB_ACE4_INHERIT_ONLY_ACE | SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG \ +| SMB_ACE4_FAILED_ACCESS_ACE_FLAG | SMB_ACE4_IDENTIFIER_GROUP | SMB_ACE4_INHERITED_ACE) + + uint32_t aceMask; /* Access rights */ +/*The bitmask constants used for the access mask field are as follows: */ +#define SMB_ACE4_READ_DATA 0x00000001 +#define SMB_ACE4_LIST_DIRECTORY 0x00000001 +#define SMB_ACE4_WRITE_DATA 0x00000002 +#define SMB_ACE4_ADD_FILE 0x00000002 +#define SMB_ACE4_APPEND_DATA 0x00000004 +#define SMB_ACE4_ADD_SUBDIRECTORY 0x00000004 +#define SMB_ACE4_READ_NAMED_ATTRS 0x00000008 +#define SMB_ACE4_WRITE_NAMED_ATTRS 0x00000010 +#define SMB_ACE4_EXECUTE 0x00000020 +#define SMB_ACE4_DELETE_CHILD 0x00000040 +#define SMB_ACE4_READ_ATTRIBUTES 0x00000080 +#define SMB_ACE4_WRITE_ATTRIBUTES 0x00000100 +#define SMB_ACE4_DELETE 0x00010000 +#define SMB_ACE4_READ_ACL 0x00020000 +#define SMB_ACE4_WRITE_ACL 0x00040000 +#define SMB_ACE4_WRITE_OWNER 0x00080000 +#define SMB_ACE4_SYNCHRONIZE 0x00100000 +#define SMB_ACE4_ALL_MASKS ( SMB_ACE4_READ_DATA | SMB_ACE4_LIST_DIRECTORY \ +| SMB_ACE4_WRITE_DATA | SMB_ACE4_ADD_FILE | SMB_ACE4_APPEND_DATA | SMB_ACE4_ADD_SUBDIRECTORY \ +| SMB_ACE4_READ_NAMED_ATTRS | SMB_ACE4_WRITE_NAMED_ATTRS | SMB_ACE4_EXECUTE | SMB_ACE4_DELETE_CHILD \ +| SMB_ACE4_READ_ATTRIBUTES | SMB_ACE4_WRITE_ATTRIBUTES | SMB_ACE4_DELETE | SMB_ACE4_READ_ACL \ +| SMB_ACE4_WRITE_ACL | SMB_ACE4_WRITE_OWNER | SMB_ACE4_SYNCHRONIZE ) +} SMB_ACE4PROP_T; + +struct SMB4ACL_T; +struct SMB4ACE_T; + +enum smbacl4_mode_enum {e_simple=0, e_special=1}; +enum smbacl4_acedup_enum {e_dontcare=0, e_reject=1, e_ignore=2, e_merge=3}; + +struct smbacl4_vfs_params { + enum smbacl4_mode_enum mode; + bool do_chown; + enum smbacl4_acedup_enum acedup; + bool map_full_control; +}; + +int smbacl4_get_vfs_params(struct connection_struct *conn, + struct smbacl4_vfs_params *params); + +int nfs4_acl_stat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname); + +int nfs4_acl_fstat(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_STAT *sbuf); + +int nfs4_acl_lstat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname); + +int nfs4_acl_fstatat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags); + +struct SMB4ACL_T *smb_create_smb4acl(TALLOC_CTX *mem_ctx); + +/* prop's contents are copied */ +/* it doesn't change the order, appends */ +struct SMB4ACE_T *smb_add_ace4(struct SMB4ACL_T *theacl, SMB_ACE4PROP_T *prop); + +SMB_ACE4PROP_T *smb_get_ace4(struct SMB4ACE_T *ace); + +/* Returns NULL if none - or error */ +struct SMB4ACE_T *smb_first_ace4(struct SMB4ACL_T *theacl); + +/* Returns NULL in the end - or error */ +struct SMB4ACE_T *smb_next_ace4(struct SMB4ACE_T *ace); + +uint32_t smb_get_naces(struct SMB4ACL_T *theacl); + +uint16_t smbacl4_get_controlflags(struct SMB4ACL_T *theacl); + +bool smbacl4_set_controlflags(struct SMB4ACL_T *theacl, uint16_t controlflags); + +bool nfs_ace_is_inherit(SMB_ACE4PROP_T *ace); + +NTSTATUS smb_fget_nt_acl_nfs4(files_struct *fsp, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, struct SMB4ACL_T *theacl); + +NTSTATUS smb_get_nt_acl_nfs4(connection_struct *conn, + const struct smb_filename *smb_fname, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, struct SMB4ACL_T *theacl); + +/* Callback function needed to set the native acl + * when applicable */ +typedef bool (*set_nfs4acl_native_fn_t)(vfs_handle_struct *handle, + files_struct *, + struct SMB4ACL_T *); + +NTSTATUS smb_set_nt_acl_nfs4(vfs_handle_struct *handle, files_struct *fsp, + const struct smbacl4_vfs_params *pparams, + uint32_t security_info_sent, + const struct security_descriptor *psd, + set_nfs4acl_native_fn_t set_nfs4_native); + +#endif /* __NFS4_ACLS_H__ */ diff --git a/source3/modules/nfs4acl_xattr.h b/source3/modules/nfs4acl_xattr.h new file mode 100644 index 0000000..0adede1 --- /dev/null +++ b/source3/modules/nfs4acl_xattr.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) Ralph Boehme 2017 + * + * 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 . + * + */ + +#ifndef __NFS4ACL_XATTR_H__ +#define __NFS4ACL_XATTR_H__ + +#define NFS4ACL_XDR_MAX_ACES 8192 + +enum nfs4acl_encoding { + NFS4ACL_ENCODING_NDR, + NFS4ACL_ENCODING_XDR, + NFS4ACL_ENCODING_NFS +}; + +struct nfs4acl_config { + unsigned nfs_version; + enum nfs4acl_encoding encoding; + char *xattr_name; + struct smbacl4_vfs_params nfs4_params; + enum default_acl_style default_acl_style; + bool nfs4_id_numeric; + bool validate_mode; +}; + +#endif /* __NFS4ACL_XATTR_H__ */ diff --git a/source3/modules/nfs4acl_xattr_ndr.c b/source3/modules/nfs4acl_xattr_ndr.c new file mode 100644 index 0000000..ffa3e69 --- /dev/null +++ b/source3/modules/nfs4acl_xattr_ndr.c @@ -0,0 +1,300 @@ +/* + * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa. + * + * Copyright (C) Jiri Sasek, 2007 + * based on the foobar.c module which is copyrighted by Volker Lendecke + * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006 + * + * based on vfs_fake_acls: + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Andrew Bartlett, 2002,2012 + * Copyright (C) Ralph Boehme 2017 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "nfs4_acls.h" +#include "librpc/gen_ndr/ndr_nfs4acl.h" +#include "nfs4acl_xattr.h" +#include "nfs4acl_xattr_ndr.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static struct nfs4acl *nfs4acl_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx) +{ + enum ndr_err_code ndr_err; + struct nfs4acl *acl = talloc_zero(mem_ctx, struct nfs4acl); + + if (acl == NULL) { + errno = ENOMEM; + return NULL; + } + + ndr_err = ndr_pull_struct_blob(blob, acl, acl, + (ndr_pull_flags_fn_t)ndr_pull_nfs4acl); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_acl_t failed: %s\n", ndr_errstr(ndr_err)); + TALLOC_FREE(acl); + return NULL; + } + return acl; +} + +static DATA_BLOB nfs4acl_acl2blob(TALLOC_CTX *mem_ctx, struct nfs4acl *acl) +{ + enum ndr_err_code ndr_err; + DATA_BLOB blob; + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl, + (ndr_push_flags_fn_t)ndr_push_nfs4acl); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_push_acl_t failed: %s\n", ndr_errstr(ndr_err)); + return data_blob_null; + } + return blob; +} + +static uint16_t nfs4acl_to_smb4acl_flags(uint8_t nfs4acl_flags) +{ + uint16_t smb4acl_flags = SEC_DESC_SELF_RELATIVE; + + if (nfs4acl_flags & ACL4_AUTO_INHERIT) { + smb4acl_flags |= SEC_DESC_DACL_AUTO_INHERITED; + } + if (nfs4acl_flags & ACL4_PROTECTED) { + smb4acl_flags |= SEC_DESC_DACL_PROTECTED; + } + if (nfs4acl_flags & ACL4_DEFAULTED) { + smb4acl_flags |= SEC_DESC_DACL_DEFAULTED; + } + + return smb4acl_flags; +} + +NTSTATUS nfs4acl_ndr_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl *nfs4acl = NULL; + struct SMB4ACL_T *smb4acl = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + struct nfs4acl_config *config = NULL; + int i; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + nfs4acl = nfs4acl_blob2acl(blob, frame); + if (nfs4acl == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INTERNAL_ERROR; + } + + smb4acl = smb_create_smb4acl(mem_ctx); + if (smb4acl == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + if (config->nfs_version > ACL4_XATTR_VERSION_40 && + nfs4acl->a_version > ACL4_XATTR_VERSION_40) + { + uint16_t smb4acl_flags; + + smb4acl_flags = nfs4acl_to_smb4acl_flags(nfs4acl->a_flags); + smbacl4_set_controlflags(smb4acl, smb4acl_flags); + } + + for (i = 0; i < nfs4acl->a_count; i++) { + SMB_ACE4PROP_T aceprop; + + aceprop.aceType = (uint32_t) nfs4acl->ace[i].e_type; + aceprop.aceFlags = (uint32_t) nfs4acl->ace[i].e_flags; + aceprop.aceMask = (uint32_t) nfs4acl->ace[i].e_mask; + aceprop.who.id = (uint32_t) nfs4acl->ace[i].e_id; + + if (!strcmp(nfs4acl->ace[i].e_who, + NFS4ACL_XATTR_OWNER_WHO)) { + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_OWNER; + } else if (!strcmp(nfs4acl->ace[i].e_who, + NFS4ACL_XATTR_GROUP_WHO)) { + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_GROUP; + } else if (!strcmp(nfs4acl->ace[i].e_who, + NFS4ACL_XATTR_EVERYONE_WHO)) { + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_EVERYONE; + } else { + aceprop.flags = 0; + } + + if (smb_add_ace4(smb4acl, &aceprop) == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + *_smb4acl = smb4acl; + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +static uint8_t smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags) +{ + uint8_t flags = 0; + + if (smb4acl_flags & SEC_DESC_DACL_AUTO_INHERITED) { + flags |= ACL4_AUTO_INHERIT; + } + if (smb4acl_flags & SEC_DESC_DACL_PROTECTED) { + flags |= ACL4_PROTECTED; + } + if (smb4acl_flags & SEC_DESC_DACL_DEFAULTED) { + flags |= ACL4_DEFAULTED; + } + + return flags; +} + +static bool nfs4acl_smb4acl2nfs4acl(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + struct nfs4acl **_nfs4acl, + bool denymissingspecial) +{ + struct nfs4acl_config *config = NULL; + struct nfs4acl *nfs4acl = NULL; + struct SMB4ACE_T *smbace = NULL; + bool have_special_id = false; + int i; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + nfs4acl = talloc_zero(mem_ctx, struct nfs4acl); + if (nfs4acl == NULL) { + errno = ENOMEM; + return false; + } + + nfs4acl->a_count = smb_get_naces(smbacl); + + nfs4acl->ace = talloc_zero_array(nfs4acl, struct nfs4ace, + nfs4acl->a_count); + if (nfs4acl->ace == NULL) { + TALLOC_FREE(nfs4acl); + errno = ENOMEM; + return false; + } + + nfs4acl->a_version = config->nfs_version; + if (nfs4acl->a_version > ACL4_XATTR_VERSION_40) { + uint16_t smb4acl_flags; + uint8_t flags; + + smb4acl_flags = smbacl4_get_controlflags(smbacl); + flags = smb4acl_to_nfs4acl_flags(smb4acl_flags); + nfs4acl->a_flags = flags; + } + + for (smbace = smb_first_ace4(smbacl), i = 0; + smbace != NULL; + smbace = smb_next_ace4(smbace), i++) + { + SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace); + + nfs4acl->ace[i].e_type = aceprop->aceType; + nfs4acl->ace[i].e_flags = aceprop->aceFlags; + nfs4acl->ace[i].e_mask = aceprop->aceMask; + nfs4acl->ace[i].e_id = aceprop->who.id; + if(aceprop->flags & SMB_ACE4_ID_SPECIAL) { + switch(aceprop->who.special_id) { + case SMB_ACE4_WHO_EVERYONE: + nfs4acl->ace[i].e_who = + NFS4ACL_XATTR_EVERYONE_WHO; + break; + case SMB_ACE4_WHO_OWNER: + nfs4acl->ace[i].e_who = + NFS4ACL_XATTR_OWNER_WHO; + break; + case SMB_ACE4_WHO_GROUP: + nfs4acl->ace[i].e_who = + NFS4ACL_XATTR_GROUP_WHO; + break; + default: + DBG_DEBUG("unsupported special_id %d\n", + aceprop->who.special_id); + continue; /* don't add it !!! */ + } + have_special_id = true; + } else { + nfs4acl->ace[i].e_who = ""; + } + } + + if (!have_special_id && denymissingspecial) { + TALLOC_FREE(nfs4acl); + errno = EACCES; + return false; + } + + SMB_ASSERT(i == nfs4acl->a_count); + + *_nfs4acl = nfs4acl; + return true; +} + +NTSTATUS nfs4acl_smb4acl_to_ndr_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + DATA_BLOB *_blob) +{ + struct nfs4acl *nfs4acl = NULL; + DATA_BLOB blob; + bool denymissingspecial; + bool ok; + + denymissingspecial = lp_parm_bool(SNUM(handle->conn), + "nfs4acl_xattr", + "denymissingspecial", false); + + ok = nfs4acl_smb4acl2nfs4acl(handle, talloc_tos(), smb4acl, &nfs4acl, + denymissingspecial); + if (!ok) { + DBG_ERR("Failed to convert smb ACL to nfs4 ACL.\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + blob = nfs4acl_acl2blob(mem_ctx, nfs4acl); + TALLOC_FREE(nfs4acl); + if (blob.data == NULL) { + DBG_ERR("Failed to convert ACL to linear blob for xattr\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + *_blob = blob; + return NT_STATUS_OK; +} diff --git a/source3/modules/nfs4acl_xattr_ndr.h b/source3/modules/nfs4acl_xattr_ndr.h new file mode 100644 index 0000000..17cf9da --- /dev/null +++ b/source3/modules/nfs4acl_xattr_ndr.h @@ -0,0 +1,44 @@ +/* + * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa. + * + * Copyright (C) Jiri Sasek, 2007 + * based on the foobar.c module which is copyrighted by Volker Lendecke + * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006 + * + * based on vfs_fake_acls: + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Andrew Bartlett, 2002,2012 + * Copyright (C) Ralph Boehme 2017 + * + * 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 . + * + */ + +#ifndef __NFS4ACL_XATTR_NDR_H__ +#define __NFS4ACL_XATTR_NDR_H__ + +struct SMB4ACL_T; + +NTSTATUS nfs4acl_ndr_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl); + +NTSTATUS nfs4acl_smb4acl_to_ndr_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + DATA_BLOB *blob); + +#endif /* _VFS_NFS4ACL_XATTR_NDR_H */ diff --git a/source3/modules/nfs4acl_xattr_nfs.c b/source3/modules/nfs4acl_xattr_nfs.c new file mode 100644 index 0000000..df3c0f5 --- /dev/null +++ b/source3/modules/nfs4acl_xattr_nfs.c @@ -0,0 +1,894 @@ +/* + * Copyright (C) Ralph Boehme 2018 + * + * 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 "smbd/proto.h" +#include "system/passwd.h" +#include "libcli/security/security_descriptor.h" +#include "libcli/security/security_token.h" + +#ifdef HAVE_RPC_XDR_H +/* uses TRUE and FALSE */ +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#ifdef HAVE_RPC_TYPES_H +#include +#endif +#include + +#include "nfs4_acls.h" +#include "nfs41acl.h" +#include "nfs4acl_xattr.h" +#include "nfs4acl_xattr_nfs.h" +#include "nfs4acl_xattr_util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define OVERFLOW_CHECK(val1, val2) ((val1) + (val2) < (val1)) +#define XDR_UTF8STR_ALIGNMENT 4 +#define XDR_UTF8STR_ALIGN(l) \ + (((l) + ((XDR_UTF8STR_ALIGNMENT) - 1)) & ~((XDR_UTF8STR_ALIGNMENT) - 1)) + +static struct nfs4_to_smb4_id_map { + const char *nfs4_id; + uint32_t smb4_id; +} nfs4_to_smb4_id_map[] = { + {"OWNER@", SMB_ACE4_WHO_OWNER}, + {"GROUP@", SMB_ACE4_WHO_GROUP}, + {"EVERYONE@", SMB_ACE4_WHO_EVERYONE}, + {"INTERACTIVE@", SMB_ACE4_WHO_INTERACTIVE}, + {"NETWORK@", SMB_ACE4_WHO_NETWORK}, + {"DIALUP@", SMB_ACE4_WHO_DIALUP}, + {"BATCH@", SMB_ACE4_WHO_BATCH}, + {"ANONYMOUS@", SMB_ACE4_WHO_ANONYMOUS}, + {"AUTHENTICATED@", SMB_ACE4_WHO_AUTHENTICATED}, + {"SERVICE@", SMB_ACE4_WHO_SERVICE}, +}; + +static bool is_special_nfs4_id(const char *nfs4_id) +{ + char *at = NULL; + + at = strchr(nfs4_id, '@'); + if (at == NULL) { + return false; + } + if (at[1] != '\0') { + return false; + } + return true; +} + +static bool map_special_nfs4_to_smb4_id(const char *nfs4_id, uint32_t *smb4_id) +{ + size_t i; + int cmp; + + for (i = 0; i < ARRAY_SIZE(nfs4_to_smb4_id_map); i++) { + cmp = strcmp(nfs4_to_smb4_id_map[i].nfs4_id, nfs4_id); + if (cmp != 0) { + continue; + } + *smb4_id = nfs4_to_smb4_id_map[i].smb4_id; + return true; + } + return false; +} + +static bool map_special_smb4_to_nfs4_id(uint32_t smb4_id, const char **nfs4_id) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(nfs4_to_smb4_id_map); i++) { + if (nfs4_to_smb4_id_map[i].smb4_id != smb4_id) { + continue; + } + *nfs4_id = nfs4_to_smb4_id_map[i].nfs4_id; + return true; + } + return false; +} + +static unsigned nfs40acl_get_naces(nfsacl40 *nacl) +{ + return nacl->na40_aces.na40_aces_len; +} + +static unsigned nfs41acl_get_naces(nfsacl41 *nacl) +{ + return nacl->na41_aces.na41_aces_len; +} + +static void nfs40acl_set_naces(nfsacl40 *nacl, unsigned naces) +{ + nacl->na40_aces.na40_aces_len = naces; +} + +static void nfs41acl_set_naces(nfsacl41 *nacl, unsigned naces) +{ + nacl->na41_aces.na41_aces_len = naces; +} + +static unsigned nfs41acl_get_flags(nfsacl41 *nacl) +{ + return nacl->na41_flag; +} + +static void nfs41acl_set_flags(nfsacl41 *nacl, unsigned flags) +{ + nacl->na41_flag = flags; +} + +static nfsace4 *nfs40acl_get_ace(nfsacl40 *nacl, size_t n) +{ + return &nacl->na40_aces.na40_aces_val[n]; +} + +static nfsace4 *nfs41acl_get_ace(nfsacl41 *nacl, size_t n) +{ + return &nacl->na41_aces.na41_aces_val[n]; +} + +static size_t nfs40acl_get_xdrblob_size(nfsacl40 *nacl) +{ + size_t acl_size; + size_t aces_size; + size_t identifier_size; + unsigned i; + unsigned naces = nfs40acl_get_naces(nacl); + + /* ACE structure minus actual identifier strings */ + struct nfsace4_size { + acetype4 type; + aceflag4 flag; + acemask4 access_mask; + u_int who_length; + }; + + /* + * acl_size = + * sizeof(ace_count) + + * (ace_count * (sizeof(nfsace4_size)) + + * length of all identifiers strings + */ + + acl_size = sizeof(unsigned); + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %u\n", naces); + return 0; + } + + aces_size = naces * sizeof(struct nfsace4_size); + + if (OVERFLOW_CHECK(acl_size, aces_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + acl_size += aces_size; + + identifier_size = 0; + for (i = 0; i < naces; i++) { + nfsace4 *nace = nfs40acl_get_ace(nacl, i); + size_t string_size = nace->who.utf8string_len; + size_t id_size; + + id_size = XDR_UTF8STR_ALIGN(string_size); + + if (OVERFLOW_CHECK(identifier_size, id_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + identifier_size += id_size; + } + + if (OVERFLOW_CHECK(acl_size, identifier_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + acl_size += identifier_size; + + DBG_DEBUG("acl_size: %zd\n", acl_size); + return acl_size; +} + +static size_t nfs41acl_get_xdrblob_size(nfsacl41 *nacl) +{ + size_t acl_size; + size_t aces_size; + size_t identifier_size; + unsigned i; + unsigned naces = nfs41acl_get_naces(nacl); + + /* ACE structure minus actual identifier strings */ + struct nfsace4_size { + acetype4 type; + aceflag4 flag; + acemask4 access_mask; + u_int who_length; + }; + + /* + * acl_size = + * sizeof(acl_flag) + + * sizeof(ace_count) + + * (ace_count * (sizeof(nfsace4_size)) + + * length of all identifiers strings + */ + + acl_size = 2 * sizeof(unsigned); + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %u\n", naces); + return 0; + } + + aces_size = naces * sizeof(struct nfsace4_size); + + if (OVERFLOW_CHECK(acl_size, aces_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + acl_size += aces_size; + + identifier_size = 0; + for (i = 0; i < naces; i++) { + nfsace4 *nace = nfs41acl_get_ace(nacl, i); + size_t string_size = nace->who.utf8string_len; + size_t id_size; + + id_size = XDR_UTF8STR_ALIGN(string_size); + + if (OVERFLOW_CHECK(identifier_size, id_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + identifier_size += id_size; + } + + if (OVERFLOW_CHECK(acl_size, identifier_size)) { + DBG_ERR("Integer Overflow error\n"); + return 0; + } + acl_size += identifier_size; + + DBG_DEBUG("acl_size: %zd\n", acl_size); + return acl_size; +} + +static nfsacl40 *nfs40acl_alloc(TALLOC_CTX *mem_ctx, unsigned naces) +{ + size_t acl_size; + size_t aces_size; + nfsacl40 *nacl = NULL; + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %d\n", naces); + return NULL; + } + + acl_size = sizeof(nfsacl40); + aces_size = (naces * sizeof(struct nfsace4)); + + if (OVERFLOW_CHECK(acl_size, aces_size)) { + DBG_ERR("Integer Overflow error\n"); + return NULL; + } + acl_size += aces_size; + + nacl = talloc_zero_size(mem_ctx, acl_size); + if (nacl == NULL) { + DBG_ERR("talloc_zero_size failed\n"); + return NULL; + } + + nfs40acl_set_naces(nacl, naces); + nacl->na40_aces.na40_aces_val = + (nfsace4 *)((uint8_t *)nacl + sizeof(nfsacl40)); + + return nacl; +} + +static nfsacl41 *nfs41acl_alloc(TALLOC_CTX *mem_ctx, unsigned naces) +{ + size_t acl_size; + size_t aces_size; + nfsacl41 *nacl = NULL; + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %d\n", naces); + return NULL; + } + + acl_size = sizeof(nfsacl41); + aces_size = (naces * sizeof(struct nfsace4)); + + if (OVERFLOW_CHECK(acl_size, aces_size)) { + DBG_ERR("Integer Overflow error\n"); + return NULL; + } + acl_size += aces_size; + + nacl = talloc_zero_size(mem_ctx, acl_size); + if (nacl == NULL) { + DBG_ERR("talloc_zero_size failed\n"); + return NULL; + } + + nfs41acl_set_naces(nacl, naces); + nacl->na41_aces.na41_aces_val = + (nfsace4 *)((uint8_t *)nacl + sizeof(nfsacl41)); + + return nacl; +} + +static bool create_special_id(TALLOC_CTX *mem_ctx, + nfsace4 *nace, + const char *id) +{ + char *s = talloc_strdup(mem_ctx, id); + + if (s == NULL) { + DBG_ERR("talloc_strdup failed\n"); + return false; + } + nace->who.utf8string_val = s; + nace->who.utf8string_len = talloc_get_size(s) - 1; + return true; +} + +static bool map_smb4_to_nfs4_id(TALLOC_CTX *mem_ctx, + struct nfs4acl_config *config, + nfsace4 *nace, + SMB_ACE4PROP_T *sace) +{ + const char *nfs4_id = NULL; + const char *name = NULL; + char *ace_name = NULL; + uid_t id; + bool ok; + + if (sace->flags & SMB_ACE4_ID_SPECIAL) { + ok = map_special_smb4_to_nfs4_id(sace->who.special_id, + &nfs4_id); + if (!ok) { + DBG_ERR("Unsupported special id [%"PRIu32"]\n", + sace->who.special_id); + return false; + } + + ok = create_special_id(mem_ctx, nace, nfs4_id); + if (!ok) { + return false; + } + DBG_DEBUG("Special id [%s]\n", nace->who.utf8string_val); + return true; + } + + if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + nace->flag |= ACE4_IDENTIFIER_GROUP; + } + + if (config->nfs4_id_numeric) { + char *strid = NULL; + + if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + id = sace->who.gid; + } else { + id = sace->who.uid; + } + + strid = talloc_asprintf(mem_ctx, "%jd", (intmax_t)id); + if (strid == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return false; + } + nace->who.utf8string_val = strid; + nace->who.utf8string_len = talloc_get_size(strid) - 1; + DBG_DEBUG("Numeric id [%s]\n", nace->who.utf8string_val); + return true; + } + + if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + struct group *grp = NULL; + + grp = getgrgid(sace->who.gid); + if (grp == NULL) { + DBG_ERR("Unknown gid [%jd]\n", (intmax_t)sace->who.gid); + return false; + } + name = grp->gr_name; + } else { + struct passwd *pwd = NULL; + + pwd = getpwuid(sace->who.uid); + if (pwd == NULL) { + DBG_ERR("Unknown uid [%jd]\n", (intmax_t)sace->who.uid); + return false; + } + name = pwd->pw_name; + } + + ace_name = talloc_strdup(mem_ctx, name); + if (ace_name == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return false; + } + nace->who.utf8string_val = ace_name; + nace->who.utf8string_len = talloc_get_size(ace_name) - 1; + + DBG_DEBUG("id [%s]\n", nace->who.utf8string_val); + return true; +} + +static bool smb4acl_to_nfs40acl(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + nfsacl40 **_nacl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACE_T *smb4ace = NULL; + nfsacl40 *nacl = NULL; + size_t naces = smb_get_naces(smb4acl); + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + nacl = nfs40acl_alloc(mem_ctx, naces); + nfs40acl_set_naces(nacl, 0); + + smb4ace = smb_first_ace4(smb4acl); + while (smb4ace != NULL) { + SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace); + size_t nace_count = nfs40acl_get_naces(nacl); + nfsace4 *nace = nfs40acl_get_ace(nacl, nace_count); + + nace->type = ace4prop->aceType; + nace->flag = ace4prop->aceFlags; + nace->access_mask = ace4prop->aceMask; + + ok = map_smb4_to_nfs4_id(nacl, config, nace, ace4prop); + if (!ok) { + smb4ace = smb_next_ace4(smb4ace); + continue; + } + + nace_count++; + nfs40acl_set_naces(nacl, nace_count); + smb4ace = smb_next_ace4(smb4ace); + } + + *_nacl = nacl; + return true; +} + +static bool smb4acl_to_nfs41acl(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + nfsacl41 **_nacl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACE_T *smb4ace = NULL; + nfsacl41 *nacl = NULL; + size_t naces = smb_get_naces(smb4acl); + uint16_t smb4acl_flags; + unsigned nacl_flags; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + nacl = nfs41acl_alloc(mem_ctx, naces); + nfs41acl_set_naces(nacl, 0); + + smb4acl_flags = smbacl4_get_controlflags(smb4acl); + nacl_flags = smb4acl_to_nfs4acl_flags(smb4acl_flags); + nfs41acl_set_flags(nacl, nacl_flags); + + smb4ace = smb_first_ace4(smb4acl); + while (smb4ace != NULL) { + SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace); + size_t nace_count = nfs41acl_get_naces(nacl); + nfsace4 *nace = nfs41acl_get_ace(nacl, nace_count); + + nace->type = ace4prop->aceType; + nace->flag = ace4prop->aceFlags; + nace->access_mask = ace4prop->aceMask; + + ok = map_smb4_to_nfs4_id(nacl, config, nace, ace4prop); + if (!ok) { + smb4ace = smb_next_ace4(smb4ace); + continue; + } + + nace_count++; + nfs41acl_set_naces(nacl, nace_count); + smb4ace = smb_next_ace4(smb4ace); + } + + *_nacl = nacl; + return true; +} + +NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + DATA_BLOB *_blob) +{ + struct nfs4acl_config *config = NULL; + nfsacl40 *nacl40 = NULL; + nfsacl41 *nacl41 = NULL; + XDR xdr = {0}; + size_t aclblobsize; + DATA_BLOB blob; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + if (config->nfs_version == ACL4_XATTR_VERSION_40) { + ok = smb4acl_to_nfs40acl(handle, mem_ctx, smb4acl, &nacl40); + if (!ok) { + DBG_ERR("smb4acl_to_nfs4acl failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + aclblobsize = nfs40acl_get_xdrblob_size(nacl40); + if (aclblobsize == 0) { + DBG_ERR("Error calculating XDR blob size\n"); + return NT_STATUS_INTERNAL_ERROR; + } + } else { + ok = smb4acl_to_nfs41acl(handle, mem_ctx, smb4acl, &nacl41); + if (!ok) { + DBG_ERR("smb4acl_to_nfs4acl failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + aclblobsize = nfs41acl_get_xdrblob_size(nacl41); + if (aclblobsize == 0) { + DBG_ERR("Error calculating XDR blob size\n"); + return NT_STATUS_INTERNAL_ERROR; + } + } + + blob = data_blob_talloc(mem_ctx, NULL, aclblobsize); + if (blob.data == NULL) { + TALLOC_FREE(nacl40); + TALLOC_FREE(nacl41); + return NT_STATUS_NO_MEMORY; + } + + xdrmem_create(&xdr, (char *)blob.data, blob.length, XDR_ENCODE); + + if (config->nfs_version == ACL4_XATTR_VERSION_40) { + ok = xdr_nfsacl40(&xdr, nacl40); + TALLOC_FREE(nacl40); + if (!ok) { + DBG_ERR("xdr_nfs4acl40 failed\n"); + return NT_STATUS_NO_MEMORY; + } + } else { + ok = xdr_nfsacl41(&xdr, nacl41); + TALLOC_FREE(nacl41); + if (!ok) { + DBG_ERR("xdr_nfs4acl40 failed\n"); + return NT_STATUS_NO_MEMORY; + } + } + + *_blob = blob; + return NT_STATUS_OK; +} + +static NTSTATUS nfs4acl_nfs_blob_to_nfs40acl(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + nfsacl40 **_nacl) +{ + nfsacl40 *nacl = NULL; + XDR xdr = {0}; + bool ok; + + nacl = talloc_zero_size(mem_ctx, sizeof(nfsacl40)); + if (nacl == NULL) { + DBG_ERR("talloc_zero_size failed\n"); + return NT_STATUS_NO_MEMORY; + } + + xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE); + + ok = xdr_nfsacl40(&xdr, nacl); + if (!ok) { + DBG_ERR("xdr_nfsacl40 failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + DBG_DEBUG("naces = %d \n", nacl->na40_aces.na40_aces_len); + + *_nacl = nacl; + return NT_STATUS_OK; +} + +static NTSTATUS nfs4acl_nfs_blob_to_nfs41acl(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + nfsacl41 **_nacl) +{ + nfsacl41 *nacl = NULL; + XDR xdr = {0}; + bool ok; + + nacl = talloc_zero_size(mem_ctx, sizeof(nfsacl41)); + if (nacl == NULL) { + DBG_ERR("talloc_zero_size failed\n"); + return NT_STATUS_NO_MEMORY; + } + + xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE); + + ok = xdr_nfsacl41(&xdr, nacl); + if (!ok) { + DBG_ERR("xdr_nfsacl40 failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + DBG_DEBUG("naces = %d \n", nacl->na41_aces.na41_aces_len); + + *_nacl = nacl; + return NT_STATUS_OK; +} + +static bool map_ace_nfs4_to_smb4(struct nfs4acl_config *config, + const nfsace4 *nace, + SMB_ACE4PROP_T *sace) +{ + char *name = NULL; + char *p = NULL; + uint32_t smb4_id; + bool ok; + + name = talloc_strndup(talloc_tos(), + nace->who.utf8string_val, + nace->who.utf8string_len); + if (name == NULL) { + return false; + } + + sace->aceType = nace->type; + sace->aceFlags = nace->flag; + sace->aceMask = nace->access_mask; + + if (is_special_nfs4_id(name)) { + ok = map_special_nfs4_to_smb4_id(name, &smb4_id); + if (!ok) { + DBG_WARNING("Unknown special id [%s]\n", name); + return false; + } + sace->flags |= SMB_ACE4_ID_SPECIAL; + sace->who.special_id = smb4_id; + return true; + } + + p = strtok(name, "@"); + if (p == NULL && !config->nfs4_id_numeric) { + DBG_ERR("Unqualified name [%s]\n", name); + TALLOC_FREE(name); + return false; + } + + /* + * nametouid() and nametogid() work with both names and numbers... + */ + + if (nace->flag & ACE4_IDENTIFIER_GROUP) { + sace->who.gid = nametogid(name); + if (sace->who.gid == (gid_t)-1) { + DBG_ERR("converting id [%s] failed\n", name); + TALLOC_FREE(name); + return false; + } + TALLOC_FREE(name); + return true; + } + + sace->who.uid = nametouid(name); + if (sace->who.uid == (gid_t)-1) { + DBG_ERR("converting id [%s] failed\n", name); + TALLOC_FREE(name); + return false; + } + TALLOC_FREE(name); + return true; +} + +static NTSTATUS nfs40acl_to_smb4acl(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + nfsacl40 *nacl, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACL_T *smb4acl = NULL; + unsigned naces = nfs40acl_get_naces(nacl); + unsigned int i; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + smb4acl = smb_create_smb4acl(mem_ctx); + if (smb4acl == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + DBG_DEBUG("nace [%u]\n", naces); + + for (i = 0; i < naces; i++) { + nfsace4 *nace = nfs40acl_get_ace(nacl, i); + SMB_ACE4PROP_T sace = { 0 }; + + DBG_DEBUG("type [%d] flag [%x] mask [%x] who [%*s]\n", + nace->type, nace->flag, + nace->access_mask, + nace->who.utf8string_len, + nace->who.utf8string_val); + + ok = map_ace_nfs4_to_smb4(config, nace, &sace); + if (!ok) { + continue; + } + + smb_add_ace4(smb4acl, &sace); + } + + *_smb4acl = smb4acl; + return NT_STATUS_OK; +} + +static NTSTATUS nfs41acl_to_smb4acl(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + nfsacl41 *nacl, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACL_T *smb4acl = NULL; + unsigned nfsacl41_flag = 0; + uint16_t smb4acl_flags = 0; + unsigned naces = nfs41acl_get_naces(nacl); + unsigned int i; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + smb4acl = smb_create_smb4acl(mem_ctx); + if (smb4acl == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + nfsacl41_flag = nfs41acl_get_flags(nacl); + smb4acl_flags = nfs4acl_to_smb4acl_flags(nfsacl41_flag); + smbacl4_set_controlflags(smb4acl, smb4acl_flags); + + DBG_DEBUG("flags [%x] nace [%u]\n", smb4acl_flags, naces); + + for (i = 0; i < naces; i++) { + nfsace4 *nace = nfs41acl_get_ace(nacl, i); + SMB_ACE4PROP_T sace = { 0 }; + + DBG_DEBUG("type [%d] flag [%x] mask [%x] who [%*s]\n", + nace->type, nace->flag, + nace->access_mask, + nace->who.utf8string_len, + nace->who.utf8string_val); + + ok = map_ace_nfs4_to_smb4(config, nace, &sace); + if (!ok) { + continue; + } + + smb_add_ace4(smb4acl, &sace); + } + + *_smb4acl = smb4acl; + return NT_STATUS_OK; +} + +NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACL_T *smb4acl = NULL; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + if (config->nfs_version == ACL4_XATTR_VERSION_40) { + nfsacl40 *nacl = NULL; + + status = nfs4acl_nfs_blob_to_nfs40acl(handle, + talloc_tos(), + blob, + &nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = nfs40acl_to_smb4acl(handle, mem_ctx, nacl, &smb4acl); + TALLOC_FREE(nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + nfsacl41 *nacl = NULL; + + status = nfs4acl_nfs_blob_to_nfs41acl(handle, + talloc_tos(), + blob, + &nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = nfs41acl_to_smb4acl(handle, mem_ctx, nacl, &smb4acl); + TALLOC_FREE(nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + *_smb4acl = smb4acl; + return NT_STATUS_OK; +} + +#else /* !HAVE_RPC_XDR_H */ +#include "nfs4_acls.h" +#include "nfs4acl_xattr_nfs.h" +NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + DATA_BLOB *blob) +{ + return NT_STATUS_NOT_SUPPORTED; +} +#endif /* HAVE_RPC_XDR_H */ diff --git a/source3/modules/nfs4acl_xattr_nfs.h b/source3/modules/nfs4acl_xattr_nfs.h new file mode 100644 index 0000000..3c4109c --- /dev/null +++ b/source3/modules/nfs4acl_xattr_nfs.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Ralph Boehme 2018 + * + * 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 . + * + */ + +#ifndef __NFS4ACL_XATTR_NFS_H__ +#define __NFS4ACL_XATTR_NFS_H__ + +#define NFS4ACL_NFS_XATTR_NAME "system.nfs4_acl" + +struct SMB4ACL_T; + +NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl); + +NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + DATA_BLOB *blob); + +#endif /* __NFS4ACL_XATTR_NFS_H__ */ diff --git a/source3/modules/nfs4acl_xattr_util.c b/source3/modules/nfs4acl_xattr_util.c new file mode 100644 index 0000000..998dbf2 --- /dev/null +++ b/source3/modules/nfs4acl_xattr_util.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) Ralph Boehme 2018 + * + * 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 "smbd/proto.h" +#include "libcli/security/security_descriptor.h" + +#ifdef HAVE_RPC_XDR_H +/* uses TRUE and FALSE */ +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#include "nfs4_acls.h" +#include "nfs41acl.h" +#include "nfs4acl_xattr_util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +unsigned smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags) +{ + unsigned nfs4acl_flags = 0; + + if (smb4acl_flags & SEC_DESC_DACL_AUTO_INHERITED) { + nfs4acl_flags |= ACL4_AUTO_INHERIT; + } + if (smb4acl_flags & SEC_DESC_DACL_PROTECTED) { + nfs4acl_flags |= ACL4_PROTECTED; + } + if (smb4acl_flags & SEC_DESC_DACL_DEFAULTED) { + nfs4acl_flags |= ACL4_DEFAULTED; + } + + return nfs4acl_flags; +} + +uint16_t nfs4acl_to_smb4acl_flags(unsigned nfsacl41_flags) +{ + uint16_t smb4acl_flags = SEC_DESC_SELF_RELATIVE; + + if (nfsacl41_flags & ACL4_AUTO_INHERIT) { + smb4acl_flags |= SEC_DESC_DACL_AUTO_INHERITED; + } + if (nfsacl41_flags & ACL4_PROTECTED) { + smb4acl_flags |= SEC_DESC_DACL_PROTECTED; + } + if (nfsacl41_flags & ACL4_DEFAULTED) { + smb4acl_flags |= SEC_DESC_DACL_DEFAULTED; + } + + return smb4acl_flags; +} +#endif /* HAVE_RPC_XDR_H */ diff --git a/source3/modules/nfs4acl_xattr_util.h b/source3/modules/nfs4acl_xattr_util.h new file mode 100644 index 0000000..2d2c6a1 --- /dev/null +++ b/source3/modules/nfs4acl_xattr_util.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Ralph Boehme 2018 + * + * 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 . + * + */ + +#ifndef _NFS4ACL_XATTR_UTIL_H_ +#define _NFS4ACL_XATTR_UTIL_H_ + +unsigned smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags); +uint16_t nfs4acl_to_smb4acl_flags(unsigned nfsacl41_flags); + +#endif /* _NFS4ACL_XATTR_UTIL_H_ */ diff --git a/source3/modules/nfs4acl_xattr_xdr.c b/source3/modules/nfs4acl_xattr_xdr.c new file mode 100644 index 0000000..439378e --- /dev/null +++ b/source3/modules/nfs4acl_xattr_xdr.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) Ralph Boehme 2017 + * + * 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 "smbd/proto.h" +#include "libcli/security/security_descriptor.h" +#include "libcli/security/security_token.h" +#include "nfs4_acls.h" +#include "nfs4acl_xattr.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#ifdef HAVE_RPC_XDR_H +/* uses TRUE and FALSE */ +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#ifdef HAVE_RPC_TYPES_H +#include +#endif +#include +#include "nfs41acl.h" +#include "nfs4acl_xattr_xdr.h" +#include "nfs4acl_xattr_util.h" + +static unsigned nfs4acli_get_naces(nfsacl41i *nacl) +{ + return nacl->na41_aces.na41_aces_len; +} + +static void nfs4acli_set_naces(nfsacl41i *nacl, unsigned naces) +{ + nacl->na41_aces.na41_aces_len = naces; +} + +static unsigned nfs4acli_get_flags(nfsacl41i *nacl) +{ + return nacl->na41_flag; +} + +static void nfs4acli_set_flags(nfsacl41i *nacl, unsigned flags) +{ + nacl->na41_flag = flags; +} + +static size_t nfs4acli_get_xdrblob_size(nfsacl41i *nacl) +{ + size_t acl_size; + size_t aces_size; + unsigned naces = nfs4acli_get_naces(nacl); + + acl_size = sizeof(aclflag4) + sizeof(unsigned); + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %u\n", naces); + return 0; + } + + aces_size = naces * sizeof(struct nfsace4i); + if (acl_size + aces_size < acl_size) { + return 0; + } + acl_size += aces_size; + + return acl_size; +} + +static size_t nfs4acli_get_xdrblob_naces(size_t _blobsize) +{ + size_t blobsize = _blobsize; + + blobsize -= sizeof(aclflag4); + blobsize -= sizeof(unsigned); + if (blobsize > _blobsize) { + return 0; + } + return (blobsize / sizeof(struct nfsace4i)); +} + +static nfsacl41i *nfs4acli_alloc(TALLOC_CTX *mem_ctx, unsigned naces) +{ + size_t acl_size = sizeof(nfsacl41i) + (naces * sizeof(struct nfsace4i)); + nfsacl41i *nacl = NULL; + + if (naces > NFS4ACL_XDR_MAX_ACES) { + DBG_ERR("Too many ACEs: %d\n", naces); + return NULL; + } + + nacl = talloc_zero_size(mem_ctx, acl_size); + if (nacl == NULL) { + DBG_ERR("talloc_zero_size failed\n"); + return NULL; + } + + nfs4acli_set_naces(nacl, naces); + nacl->na41_aces.na41_aces_val = + (nfsace4i *)((char *)nacl + sizeof(nfsacl41i)); + + return nacl; +} + +static nfsace4i *nfs4acli_get_ace(nfsacl41i *nacl, size_t n) +{ + return &nacl->na41_aces.na41_aces_val[n]; +} + +static bool smb4acl_to_nfs4acli(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + nfsacl41i **_nacl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACE_T *smb4ace = NULL; + size_t smb4naces = 0; + nfsacl41i *nacl = NULL; + uint16_t smb4acl_flags = 0; + unsigned nacl_flags = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + smb4naces = smb_get_naces(smb4acl); + nacl = nfs4acli_alloc(mem_ctx, smb4naces); + nfs4acli_set_naces(nacl, 0); + + if (config->nfs_version > ACL4_XATTR_VERSION_40) { + smb4acl_flags = smbacl4_get_controlflags(smb4acl); + nacl_flags = smb4acl_to_nfs4acl_flags(smb4acl_flags); + nfs4acli_set_flags(nacl, nacl_flags); + } + + smb4ace = smb_first_ace4(smb4acl); + while (smb4ace != NULL) { + SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace); + size_t nace_count = nfs4acli_get_naces(nacl); + nfsace4i *nace = nfs4acli_get_ace(nacl, nace_count); + + nace->type = ace4prop->aceType; + nace->flag = ace4prop->aceFlags; + nace->access_mask = ace4prop->aceMask; + + if (ace4prop->flags & SMB_ACE4_ID_SPECIAL) { + nace->iflag |= ACEI4_SPECIAL_WHO; + + switch (ace4prop->who.special_id) { + case SMB_ACE4_WHO_OWNER: + nace->who = ACE4_SPECIAL_OWNER; + break; + + case SMB_ACE4_WHO_GROUP: + nace->who = ACE4_SPECIAL_GROUP; + break; + + case SMB_ACE4_WHO_EVERYONE: + nace->who = ACE4_SPECIAL_EVERYONE; + break; + + default: + DBG_ERR("Unsupported special id [%d]\n", + ace4prop->who.special_id); + continue; + } + } else { + if (ace4prop->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + nace->flag |= ACE4_IDENTIFIER_GROUP; + nace->who = ace4prop->who.gid; + } else { + nace->who = ace4prop->who.uid; + } + } + + nace_count++; + nfs4acli_set_naces(nacl, nace_count); + smb4ace = smb_next_ace4(smb4ace); + } + + *_nacl = nacl; + return true; +} + +NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smb4acl, + DATA_BLOB *_blob) +{ + nfsacl41i *nacl = NULL; + XDR xdr = {0}; + size_t aclblobsize; + DATA_BLOB blob; + bool ok; + + ok = smb4acl_to_nfs4acli(handle, talloc_tos(), smb4acl, &nacl); + if (!ok) { + DBG_ERR("smb4acl_to_nfs4acl failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + aclblobsize = nfs4acli_get_xdrblob_size(nacl); + if (aclblobsize == 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + blob = data_blob_talloc(mem_ctx, NULL, aclblobsize); + if (blob.data == NULL) { + TALLOC_FREE(nacl); + return NT_STATUS_NO_MEMORY; + } + + xdrmem_create(&xdr, (char *)blob.data, blob.length, XDR_ENCODE); + + ok = xdr_nfsacl41i(&xdr, nacl); + TALLOC_FREE(nacl); + if (!ok) { + DBG_ERR("xdr_nfs4acl41 failed\n"); + return NT_STATUS_NO_MEMORY; + } + + *_blob = blob; + return NT_STATUS_OK; +} + +static NTSTATUS nfs4acl_xdr_blob_to_nfs4acli(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + nfsacl41i **_nacl) +{ + struct nfs4acl_config *config = NULL; + nfsacl41i *nacl = NULL; + size_t naces; + XDR xdr = {0}; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + naces = nfs4acli_get_xdrblob_naces(blob->length); + nacl = nfs4acli_alloc(mem_ctx, naces); + + xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE); + + ok = xdr_nfsacl41i(&xdr, nacl); + if (!ok) { + DBG_ERR("xdr_nfs4acl41 failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + if (config->nfs_version == ACL4_XATTR_VERSION_40) { + nacl->na41_flag = 0; + } + + *_nacl = nacl; + return NT_STATUS_OK; +} + +static NTSTATUS nfs4acli_to_smb4acl(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + nfsacl41i *nacl, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl_config *config = NULL; + struct SMB4ACL_T *smb4acl = NULL; + unsigned nfsacl41_flag = 0; + uint16_t smb4acl_flags = 0; + unsigned naces = nfs4acli_get_naces(nacl); + unsigned i; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + smb4acl = smb_create_smb4acl(mem_ctx); + if (smb4acl == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (config->nfs_version > ACL4_XATTR_VERSION_40) { + nfsacl41_flag = nfs4acli_get_flags(nacl); + smb4acl_flags = nfs4acl_to_smb4acl_flags(nfsacl41_flag); + smbacl4_set_controlflags(smb4acl, smb4acl_flags); + } + + DBG_DEBUG("flags [%x] nace [%u]\n", smb4acl_flags, naces); + + for (i = 0; i < naces; i++) { + nfsace4i *nace = nfs4acli_get_ace(nacl, i); + SMB_ACE4PROP_T smbace = { 0 }; + + DBG_DEBUG("type [%d] iflag [%x] flag [%x] mask [%x] who [%d]\n", + nace->type, nace->iflag, nace->flag, + nace->access_mask, nace->who); + + smbace.aceType = nace->type; + smbace.aceFlags = nace->flag; + smbace.aceMask = nace->access_mask; + + if (nace->iflag & ACEI4_SPECIAL_WHO) { + smbace.flags |= SMB_ACE4_ID_SPECIAL; + + switch (nace->who) { + case ACE4_SPECIAL_OWNER: + smbace.who.special_id = SMB_ACE4_WHO_OWNER; + break; + + case ACE4_SPECIAL_GROUP: + smbace.who.special_id = SMB_ACE4_WHO_GROUP; + break; + + case ACE4_SPECIAL_EVERYONE: + smbace.who.special_id = SMB_ACE4_WHO_EVERYONE; + break; + + default: + DBG_ERR("Unknown special id [%d]\n", nace->who); + continue; + } + } else { + if (nace->flag & ACE4_IDENTIFIER_GROUP) { + smbace.who.gid = nace->who; + } else { + smbace.who.uid = nace->who; + } + } + + smb_add_ace4(smb4acl, &smbace); + } + + *_smb4acl = smb4acl; + return NT_STATUS_OK; +} + +NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl) +{ + struct nfs4acl_config *config = NULL; + nfsacl41i *nacl = NULL; + struct SMB4ACL_T *smb4acl = NULL; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + status = nfs4acl_xdr_blob_to_nfs4acli(handle, talloc_tos(), blob, &nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = nfs4acli_to_smb4acl(handle, mem_ctx, nacl, &smb4acl); + TALLOC_FREE(nacl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_smb4acl = smb4acl; + return NT_STATUS_OK; +} + +#else /* !HAVE_RPC_XDR_H */ +#include "nfs4acl_xattr_xdr.h" +NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + DATA_BLOB *blob) +{ + return NT_STATUS_NOT_SUPPORTED; +} +#endif /* HAVE_RPC_XDR_H */ diff --git a/source3/modules/nfs4acl_xattr_xdr.h b/source3/modules/nfs4acl_xattr_xdr.h new file mode 100644 index 0000000..4a79a0d --- /dev/null +++ b/source3/modules/nfs4acl_xattr_xdr.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) Ralph Boehme 2017 + * + * 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 . + * + */ + +#ifndef __NFS4ACL_XATTR_XDR_H__ +#define __NFS4ACL_XATTR_XDR_H__ + +#define NFS4ACL_XDR_XATTR_NAME "security.nfs4acl_xdr" + +NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct SMB4ACL_T **_smb4acl); + +NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T *smbacl, + DATA_BLOB *blob); + +#endif /* __NFS4ACL_XATTR_XDR_H__ */ diff --git a/source3/modules/non_posix_acls.c b/source3/modules/non_posix_acls.c new file mode 100644 index 0000000..81e126f --- /dev/null +++ b/source3/modules/non_posix_acls.c @@ -0,0 +1,63 @@ +/* + Unix SMB/CIFS implementation. + Access Control List handling + Copyright (C) Andrew Bartlett 2012. + + 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 "../librpc/gen_ndr/ndr_xattr.h" +#include "modules/non_posix_acls.h" + +int non_posix_sys_acl_blob_get_fd_helper(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB acl_as_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob) +{ + SMB_STRUCT_STAT sbuf; + TALLOC_CTX *frame; + struct xattr_sys_acl_hash_wrapper acl_wrapper; + int ret; + + frame = talloc_stackframe(); + + acl_wrapper.acl_as_blob = acl_as_blob; + + if (!VALID_STAT(fsp->fsp_name->st)) { + ret = smb_vfs_call_fstat(handle, fsp, &sbuf); + if (ret == -1) { + TALLOC_FREE(frame); + return -1; + } + } else { + sbuf = fsp->fsp_name->st; + } + + acl_wrapper.owner = sbuf.st_ex_uid; + acl_wrapper.group = sbuf.st_ex_gid; + acl_wrapper.mode = sbuf.st_ex_mode; + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx, + &acl_wrapper, + (ndr_push_flags_fn_t)ndr_push_xattr_sys_acl_hash_wrapper))) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + TALLOC_FREE(frame); + return 0; +} diff --git a/source3/modules/non_posix_acls.h b/source3/modules/non_posix_acls.h new file mode 100644 index 0000000..efa0455 --- /dev/null +++ b/source3/modules/non_posix_acls.h @@ -0,0 +1,24 @@ +/* + Unix SMB/CIFS implementation. + Access Control List handling + Copyright (C) Andrew Bartlett 2012. + + 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 . +*/ + +int non_posix_sys_acl_blob_get_fd_helper(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB acl_as_blob, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob); diff --git a/source3/modules/offload_token.c b/source3/modules/offload_token.c new file mode 100644 index 0000000..3b71a00 --- /dev/null +++ b/source3/modules/offload_token.c @@ -0,0 +1,348 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Ralph Boehme 2017 + + 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 "smbd/smbd.h" +#include "smbd/globals.h" +#include "../libcli/security/security.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_rbt.h" +#include "dbwrap/dbwrap_open.h" +#include "../lib/util/util_tdb.h" +#include "librpc/gen_ndr/ndr_ioctl.h" +#include "librpc/gen_ndr/ioctl.h" +#include "offload_token.h" + +struct vfs_offload_ctx { + bool initialized; + struct db_context *db_ctx; +}; + +NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx, + struct vfs_offload_ctx **_ctx) +{ + struct vfs_offload_ctx *ctx = *_ctx; + + if (ctx != NULL) { + if (!ctx->initialized) { + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; + } + + ctx = talloc_zero(mem_ctx, struct vfs_offload_ctx); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ctx->db_ctx = db_open_rbt(mem_ctx); + if (ctx->db_ctx == NULL) { + TALLOC_FREE(ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + ctx->initialized = true; + *_ctx = ctx; + return NT_STATUS_OK; +} + +struct fsp_token_link { + struct vfs_offload_ctx *ctx; + DATA_BLOB token_blob; +}; + +static int fsp_token_link_destructor(struct fsp_token_link *link) +{ + DATA_BLOB token_blob = link->token_blob; + TDB_DATA key = make_tdb_data(token_blob.data, token_blob.length); + NTSTATUS status; + + status = dbwrap_delete(link->ctx->db_ctx, key); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dbwrap_delete failed: %s. Token:\n", nt_errstr(status)); + dump_data(0, token_blob.data, token_blob.length); + return -1; + } + + return 0; +} + +struct vfs_offload_token_db_store_fsp_state { + const struct files_struct *fsp; + const DATA_BLOB *token_blob; + NTSTATUS status; +}; + +static void vfs_offload_token_db_store_fsp_fn( + struct db_record *rec, TDB_DATA value, void *private_data) +{ + struct vfs_offload_token_db_store_fsp_state *state = private_data; + const struct files_struct *fsp = state->fsp; + const DATA_BLOB *token_blob = state->token_blob; + files_struct *token_db_fsp = NULL; + void *ptr = NULL; + + if (value.dsize == 0) { + value = make_tdb_data((uint8_t *)&fsp, sizeof(files_struct *)); + state->status = dbwrap_record_store(rec, value, 0); + return; + } + + if (value.dsize != sizeof(ptr)) { + DBG_ERR("Bad db entry for token:\n"); + dump_data(1, token_blob->data, token_blob->length); + state->status = NT_STATUS_INTERNAL_ERROR; + return; + } + memcpy(&ptr, value.dptr, value.dsize); + + token_db_fsp = talloc_get_type_abort(ptr, struct files_struct); + if (token_db_fsp != fsp) { + DBG_ERR("token for fsp [%s] matches already known " + "but different fsp [%s]:\n", + fsp_str_dbg(fsp), + fsp_str_dbg(token_db_fsp)); + dump_data(1, token_blob->data, token_blob->length); + state->status = NT_STATUS_INTERNAL_ERROR; + return; + } +} + +NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx, + const files_struct *fsp, + const DATA_BLOB *token_blob) +{ + struct vfs_offload_token_db_store_fsp_state state = { + .fsp = fsp, .token_blob = token_blob, + }; + struct fsp_token_link *link = NULL; + TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length); + NTSTATUS status; + + link = talloc(fsp, struct fsp_token_link); + if (link == NULL) { + return NT_STATUS_NO_MEMORY; + } + *link = (struct fsp_token_link) { + .ctx = ctx, + .token_blob = data_blob_dup_talloc(link, *token_blob), + }; + if (link->token_blob.data == NULL) { + TALLOC_FREE(link); + return NT_STATUS_NO_MEMORY; + } + + status = dbwrap_do_locked( + ctx->db_ctx, + key, + vfs_offload_token_db_store_fsp_fn, + &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dbwrap_do_locked failed: %s\n", + nt_errstr(status)); + TALLOC_FREE(link); + return status; + } + if (!NT_STATUS_IS_OK(state.status)) { + DBG_DEBUG("vfs_offload_token_db_store_fsp_fn failed: %s\n", + nt_errstr(status)); + TALLOC_FREE(link); + return status; + } + + talloc_set_destructor(link, fsp_token_link_destructor); + return NT_STATUS_OK; +} + +struct vfs_offload_token_db_fetch_fsp_state { + struct files_struct **fsp; + NTSTATUS status; +}; + +static void vfs_offload_token_db_fetch_fsp_fn( + TDB_DATA key, TDB_DATA value, void *private_data) +{ + struct vfs_offload_token_db_fetch_fsp_state *state = private_data; + void *ptr; + + if (value.dsize != sizeof(ptr)) { + DBG_ERR("Bad db entry for token:\n"); + dump_data(1, key.dptr, key.dsize); + state->status = NT_STATUS_INTERNAL_ERROR; + return; + } + + memcpy(&ptr, value.dptr, value.dsize); + *state->fsp = talloc_get_type_abort(ptr, struct files_struct); +} + +NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx, + const DATA_BLOB *token_blob, + files_struct **fsp) +{ + struct vfs_offload_token_db_fetch_fsp_state state = { .fsp = fsp }; + TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length); + NTSTATUS status; + + status = dbwrap_parse_record( + ctx->db_ctx, + key, + vfs_offload_token_db_fetch_fsp_fn, + &state); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Unknown token:\n"); + dump_data(10, token_blob->data, token_blob->length); + return status; + } + return state.status; +} + +NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx, + const files_struct *fsp, + uint32_t fsctl, + DATA_BLOB *token_blob) +{ + size_t len; + + switch (fsctl) { + case FSCTL_DUP_EXTENTS_TO_FILE: + len = 20; + break; + case FSCTL_SRV_REQUEST_RESUME_KEY: + len = 24; + break; + default: + DBG_ERR("Invalid fsctl [%" PRIu32 "]\n", fsctl); + return NT_STATUS_NOT_SUPPORTED; + } + + *token_blob = data_blob_talloc_zero(mem_ctx, len); + if (token_blob->length == 0) { + return NT_STATUS_NO_MEMORY; + } + + /* combine persistent and volatile handles for the resume key */ + SBVAL(token_blob->data, + SMB_VFS_ODX_TOKEN_OFFSET_PFID, + fsp->op->global->open_persistent_id); + SBVAL(token_blob->data, + SMB_VFS_ODX_TOKEN_OFFSET_VFID, + fsp->op->global->open_volatile_id); + SIVAL(token_blob->data, + SMB_VFS_ODX_TOKEN_OFFSET_FSCTL, + fsctl); + + return NT_STATUS_OK; +} + + +NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl, + files_struct *src_fsp, + files_struct *dst_fsp) +{ + NTSTATUS status; + + if (src_fsp->vuid != dst_fsp->vuid) { + DBG_INFO("copy chunk handles not in the same session.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + if (!NT_STATUS_IS_OK(src_fsp->op->status)) { + DBG_INFO("copy chunk source handle invalid: %s\n", + nt_errstr(src_fsp->op->status)); + return NT_STATUS_ACCESS_DENIED; + } + + if (!NT_STATUS_IS_OK(dst_fsp->op->status)) { + DBG_INFO("copy chunk destination handle invalid: %s\n", + nt_errstr(dst_fsp->op->status)); + return NT_STATUS_ACCESS_DENIED; + } + + if (src_fsp->fsp_flags.closing) { + DBG_INFO("copy chunk src handle with closing in progress.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + if (dst_fsp->fsp_flags.closing) { + DBG_INFO("copy chunk dst handle with closing in progress.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + if (src_fsp->fsp_flags.is_directory) { + DBG_INFO("copy chunk no read on src directory handle (%s).\n", + smb_fname_str_dbg(src_fsp->fsp_name)); + return NT_STATUS_ACCESS_DENIED; + } + + if (dst_fsp->fsp_flags.is_directory) { + DBG_INFO("copy chunk no read on dst directory handle (%s).\n", + smb_fname_str_dbg(dst_fsp->fsp_name)); + return NT_STATUS_ACCESS_DENIED; + } + + if (IS_IPC(src_fsp->conn) || IS_IPC(dst_fsp->conn)) { + DBG_INFO("copy chunk no access on IPC$ handle.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + if (IS_PRINT(src_fsp->conn) || IS_PRINT(dst_fsp->conn)) { + DBG_INFO("copy chunk no access on PRINT handle.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + /* + * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request + * The server MUST fail the request with STATUS_ACCESS_DENIED if any of + * the following are true: + * - The Open.GrantedAccess of the destination file does not include + * FILE_WRITE_DATA or FILE_APPEND_DATA. + * + * A non writable dst handle also doesn't make sense for other fsctls. + */ + status = check_any_access_fsp(dst_fsp, FILE_WRITE_DATA|FILE_APPEND_DATA); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("dest handle not writable (%s).\n", + smb_fname_str_dbg(dst_fsp->fsp_name)); + return status; + } + /* + * - The Open.GrantedAccess of the destination file does not include + * FILE_READ_DATA, and the CtlCode is FSCTL_SRV_COPYCHUNK. + */ + if ((fsctl == FSCTL_SRV_COPYCHUNK) && !CHECK_READ_IOCTL(dst_fsp)) { + DBG_INFO("copy chunk no read on dest handle (%s).\n", + smb_fname_str_dbg(dst_fsp->fsp_name)); + return NT_STATUS_ACCESS_DENIED; + } + /* + * - The Open.GrantedAccess of the source file does not include + * FILE_READ_DATA access. + */ + if (!CHECK_READ_SMB2(src_fsp)) { + DBG_INFO("src handle not readable (%s).\n", + smb_fname_str_dbg(src_fsp->fsp_name)); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} diff --git a/source3/modules/offload_token.h b/source3/modules/offload_token.h new file mode 100644 index 0000000..8662d3e --- /dev/null +++ b/source3/modules/offload_token.h @@ -0,0 +1,44 @@ +/* + Unix SMB/Netbios implementation. + Copyright (c) 2017 Ralph Boehme + + 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 . +*/ + +#ifndef _OFFLOAD_TOKEN_H_ +#define _OFFLOAD_TOKEN_H_ + +struct vfs_offload_ctx; +struct req_resume_key_rsp; + +#define SMB_VFS_ODX_TOKEN_OFFSET_PFID 0 +#define SMB_VFS_ODX_TOKEN_OFFSET_VFID 8 +#define SMB_VFS_ODX_TOKEN_OFFSET_FSCTL 16 + +NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx, + struct vfs_offload_ctx **_ctx); +NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx, + const files_struct *fsp, + const DATA_BLOB *token_blob); +NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx, + const DATA_BLOB *token_blob, + files_struct **fsp); +NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx, + const files_struct *fsp, + uint32_t fsctl, + DATA_BLOB *token_blob); +NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl, + files_struct *src_fsp, + files_struct *dst_fsp); +#endif diff --git a/source3/modules/posixacl_xattr.c b/source3/modules/posixacl_xattr.c new file mode 100644 index 0000000..365cdc7 --- /dev/null +++ b/source3/modules/posixacl_xattr.c @@ -0,0 +1,423 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls through xattr + Copyright (c) 2013 Anand Avati + Copyright (c) 2016 Yan, Zheng + + 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/filesys.h" +#include "smbd/smbd.h" +#include "modules/posixacl_xattr.h" + +/* + POSIX ACL Format: + + Size = 4 (header) + N * 8 (entry) + + Offset Size Field (Little Endian) + ------------------------------------- + 0-3 4-byte Version + + 4-5 2-byte Entry-1 tag + 6-7 2-byte Entry-1 perm + 8-11 4-byte Entry-1 id + + 12-13 2-byte Entry-2 tag + 14-15 2-byte Entry-2 perm + 16-19 4-byte Entry-2 id + + ... + + */ + + + +/* private functions */ + +#define ACL_EA_ACCESS "system.posix_acl_access" +#define ACL_EA_DEFAULT "system.posix_acl_default" +#define ACL_EA_VERSION 0x0002 +#define ACL_EA_HEADER_SIZE 4 +#define ACL_EA_ENTRY_SIZE 8 + +#define ACL_EA_SIZE(n) (ACL_EA_HEADER_SIZE + ((n) * ACL_EA_ENTRY_SIZE)) + +static SMB_ACL_T mode_to_smb_acl(mode_t mode, TALLOC_CTX *mem_ctx) +{ + struct smb_acl_t *result; + int count; + + count = 3; + result = sys_acl_init(mem_ctx); + if (!result) { + return NULL; + } + + result->acl = talloc_array(result, struct smb_acl_entry, count); + if (!result->acl) { + errno = ENOMEM; + talloc_free(result); + return NULL; + } + + result->count = count; + + result->acl[0].a_type = SMB_ACL_USER_OBJ; + result->acl[0].a_perm = (mode & S_IRWXU) >> 6; + + result->acl[1].a_type = SMB_ACL_GROUP_OBJ; + result->acl[1].a_perm = (mode & S_IRWXG) >> 3; + + result->acl[2].a_type = SMB_ACL_OTHER; + result->acl[2].a_perm = mode & S_IRWXO; + + return result; +} + +static SMB_ACL_T posixacl_xattr_to_smb_acl(const char *buf, size_t xattr_size, + TALLOC_CTX *mem_ctx) +{ + int count; + int size; + struct smb_acl_entry *smb_ace; + struct smb_acl_t *result; + int i; + int offset; + uint16_t tag; + uint16_t perm; + uint32_t id; + + size = xattr_size; + + if (size < ACL_EA_HEADER_SIZE) { + /* ACL should be at least as big as the header (4 bytes) */ + errno = EINVAL; + return NULL; + } + + /* Version is the first 4 bytes of the ACL */ + if (IVAL(buf, 0) != ACL_EA_VERSION) { + DEBUG(0, ("Unknown ACL EA version: %d\n", + IVAL(buf, 0))); + errno = EINVAL; + return NULL; + } + offset = ACL_EA_HEADER_SIZE; + + size -= ACL_EA_HEADER_SIZE; + if (size % ACL_EA_ENTRY_SIZE) { + /* Size of entries must strictly be a multiple of + size of an ACE (8 bytes) + */ + DEBUG(0, ("Invalid ACL EA size: %d\n", size)); + errno = EINVAL; + return NULL; + } + + count = size / ACL_EA_ENTRY_SIZE; + + result = sys_acl_init(mem_ctx); + if (!result) { + return NULL; + } + + result->acl = talloc_array(result, struct smb_acl_entry, count); + if (!result->acl) { + errno = ENOMEM; + talloc_free(result); + return NULL; + } + + result->count = count; + + smb_ace = result->acl; + + for (i = 0; i < count; i++) { + /* TAG is the first 2 bytes of an entry */ + tag = SVAL(buf, offset); + offset += 2; + + /* PERM is the next 2 bytes of an entry */ + perm = SVAL(buf, offset); + offset += 2; + + /* ID is the last 4 bytes of an entry */ + id = IVAL(buf, offset); + offset += 4; + + switch(tag) { + case ACL_USER: + smb_ace->a_type = SMB_ACL_USER; + break; + case ACL_USER_OBJ: + smb_ace->a_type = SMB_ACL_USER_OBJ; + break; + case ACL_GROUP: + smb_ace->a_type = SMB_ACL_GROUP; + break; + case ACL_GROUP_OBJ: + smb_ace->a_type = SMB_ACL_GROUP_OBJ; + break; + case ACL_OTHER: + smb_ace->a_type = SMB_ACL_OTHER; + break; + case ACL_MASK: + smb_ace->a_type = SMB_ACL_MASK; + break; + default: + DEBUG(0, ("unknown tag type %d\n", (unsigned int) tag)); + errno = EINVAL; + return NULL; + } + + + switch(smb_ace->a_type) { + case SMB_ACL_USER: + smb_ace->info.user.uid = id; + break; + case SMB_ACL_GROUP: + smb_ace->info.group.gid = id; + break; + default: + break; + } + + smb_ace->a_perm = 0; + smb_ace->a_perm |= ((perm & ACL_READ) ? SMB_ACL_READ : 0); + smb_ace->a_perm |= ((perm & ACL_WRITE) ? SMB_ACL_WRITE : 0); + smb_ace->a_perm |= ((perm & ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); + + smb_ace++; + } + + return result; +} + + +static int posixacl_xattr_entry_compare(const void *left, const void *right) +{ + int ret = 0; + uint16_t tag_left, tag_right; + uint32_t id_left, id_right; + + /* + Sorting precedence: + - Smaller TAG values must be earlier. + - Within same TAG, smaller identifiers must be earlier, E.g: + UID 0 entry must be earlier than UID 200 + GID 17 entry must be earlier than GID 19 + */ + + /* TAG is the first element in the entry */ + tag_left = SVAL(left, 0); + tag_right = SVAL(right, 0); + + ret = (tag_left - tag_right); + if (!ret) { + /* ID is the third element in the entry, after two short + integers (tag and perm), i.e at offset 4. + */ + id_left = IVAL(left, 4); + id_right = IVAL(right, 4); + ret = id_left - id_right; + } + + return ret; +} + + +static int smb_acl_to_posixacl_xattr(SMB_ACL_T theacl, char *buf, size_t len) +{ + ssize_t size; + struct smb_acl_entry *smb_ace; + int i; + int count; + uint16_t tag; + uint16_t perm; + uint32_t id; + int offset; + + count = theacl->count; + + size = ACL_EA_SIZE(count); + if (!buf) { + return size; + } + if (len < size) { + return -ERANGE; + } + smb_ace = theacl->acl; + + /* Version is the first 4 bytes of the ACL */ + SIVAL(buf, 0, ACL_EA_VERSION); + offset = ACL_EA_HEADER_SIZE; + + for (i = 0; i < count; i++) { + /* Calculate tag */ + switch(smb_ace->a_type) { + case SMB_ACL_USER: + tag = ACL_USER; + break; + case SMB_ACL_USER_OBJ: + tag = ACL_USER_OBJ; + break; + case SMB_ACL_GROUP: + tag = ACL_GROUP; + break; + case SMB_ACL_GROUP_OBJ: + tag = ACL_GROUP_OBJ; + break; + case SMB_ACL_OTHER: + tag = ACL_OTHER; + break; + case SMB_ACL_MASK: + tag = ACL_MASK; + break; + default: + DEBUG(0, ("Unknown tag value %d\n", + smb_ace->a_type)); + return -EINVAL; + } + + + /* Calculate id */ + switch(smb_ace->a_type) { + case SMB_ACL_USER: + id = smb_ace->info.user.uid; + break; + case SMB_ACL_GROUP: + id = smb_ace->info.group.gid; + break; + default: + id = ACL_UNDEFINED_ID; + break; + } + + /* Calculate perm */ + perm = 0; + perm |= (smb_ace->a_perm & SMB_ACL_READ) ? ACL_READ : 0; + perm |= (smb_ace->a_perm & SMB_ACL_WRITE) ? ACL_WRITE : 0; + perm |= (smb_ace->a_perm & SMB_ACL_EXECUTE) ? ACL_EXECUTE : 0; + + /* TAG is the first 2 bytes of an entry */ + SSVAL(buf, offset, tag); + offset += 2; + + /* PERM is the next 2 bytes of an entry */ + SSVAL(buf, offset, perm); + offset += 2; + + /* ID is the last 4 bytes of an entry */ + SIVAL(buf, offset, id); + offset += 4; + + smb_ace++; + } + + /* Skip the header, sort @count number of 8-byte entries */ + qsort(buf+ACL_EA_HEADER_SIZE, count, ACL_EA_ENTRY_SIZE, + posixacl_xattr_entry_compare); + + return size; +} + +SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + int ret; + int size = ACL_EA_SIZE(20); + char *buf = alloca(size); + const char *name; + + if (type == SMB_ACL_TYPE_ACCESS) { + name = ACL_EA_ACCESS; + } else if (type == SMB_ACL_TYPE_DEFAULT) { + name = ACL_EA_DEFAULT; + } else { + errno = EINVAL; + return NULL; + } + + if (!buf) { + return NULL; + } + + ret = SMB_VFS_FGETXATTR(fsp, name, buf, size); + if (ret < 0 && errno == ERANGE) { + size = SMB_VFS_FGETXATTR(fsp, name, NULL, 0); + if (size > 0) { + buf = alloca(size); + if (!buf) { + return NULL; + } + ret = SMB_VFS_FGETXATTR(fsp, name, buf, size); + } + } + + if (ret > 0) { + return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx); + } + if (ret == 0 || errno == ENOATTR) { + SMB_STRUCT_STAT sbuf; + ret = SMB_VFS_FSTAT(fsp, &sbuf); + if (ret == 0) + return mode_to_smb_acl(sbuf.st_ex_mode, mem_ctx); + } + return NULL; +} + +int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + const char *name = NULL; + char *buf; + ssize_t size; + int ret; + + if (type == SMB_ACL_TYPE_ACCESS) { + name = ACL_EA_ACCESS; + } else if (type == SMB_ACL_TYPE_DEFAULT) { + name = ACL_EA_DEFAULT; + } else { + errno = EINVAL; + return -1; + } + + size = smb_acl_to_posixacl_xattr(theacl, NULL, 0); + buf = alloca(size); + if (!buf) { + return -1; + } + + ret = smb_acl_to_posixacl_xattr(theacl, buf, size); + if (ret < 0) { + errno = -ret; + return -1; + } + + return SMB_VFS_FSETXATTR(fsp, name, buf, size, 0); +} + +int posixacl_xattr_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + return SMB_VFS_FREMOVEXATTR(fsp, ACL_EA_DEFAULT); +} diff --git a/source3/modules/posixacl_xattr.h b/source3/modules/posixacl_xattr.h new file mode 100644 index 0000000..6721538 --- /dev/null +++ b/source3/modules/posixacl_xattr.h @@ -0,0 +1,36 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acl through xattr + Copyright (c) 2013 Anand Avati + Copyright (c) 2016 Yan, Zheng + + 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 . +*/ + +#ifndef __POSIXACL_XATTR_H__ +#define __POSIXACL_XATTR_H__ + +SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx); + +int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl); + +int posixacl_xattr_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp); +#endif diff --git a/source3/modules/test_nfs4_acls.c b/source3/modules/test_nfs4_acls.c new file mode 100644 index 0000000..f47ff9b --- /dev/null +++ b/source3/modules/test_nfs4_acls.c @@ -0,0 +1,1898 @@ +/* + * Unix SMB/CIFS implementation. + * + * Unit test for NFS4 ACL handling + * + * Copyright (C) Christof Schmitt 2019 + * + * 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 "nfs4_acls.c" +#include "librpc/gen_ndr/idmap.h" +#include "idmap_cache.h" +#include + +struct test_sids { + const char *sid_str; + struct unixid unix_id; +} test_sids[] = { + { "S-1-5-2-123-456-789-100", { 1000, ID_TYPE_UID }}, + { "S-1-5-2-123-456-789-101", { 1001, ID_TYPE_GID }}, + { "S-1-5-2-123-456-789-102", { 1002, ID_TYPE_BOTH }}, + { SID_CREATOR_OWNER, { 1003, ID_TYPE_UID }}, + { SID_CREATOR_GROUP, { 1004, ID_TYPE_GID }}, + { "S-1-5-2-123-456-789-103", { 1000, ID_TYPE_GID }}, + { "S-1-5-2-123-456-789-104", { 1005, ID_TYPE_BOTH }}, + { "S-1-5-2-123-456-789-105", { 1006, ID_TYPE_BOTH }}, + { "S-1-5-2-123-456-789-106", { 1007, ID_TYPE_BOTH }}, +}; + +static int group_setup(void **state) +{ + struct dom_sid *sids = NULL; + int i; + + sids = talloc_array(NULL, struct dom_sid, ARRAY_SIZE(test_sids)); + assert_non_null(sids); + + for (i = 0; i < ARRAY_SIZE(test_sids); i++) { + assert_true(dom_sid_parse(test_sids[i].sid_str, &sids[i])); + idmap_cache_set_sid2unixid(&sids[i], &test_sids[i].unix_id); + } + + *state = sids; + + return 0; + +} + +static int group_teardown(void **state) +{ + struct dom_sid *sids = *state; + int i; + + for (i = 0; i < ARRAY_SIZE(test_sids); i++) { + assert_true(idmap_cache_del_sid(&sids[i])); + } + + TALLOC_FREE(sids); + *state = NULL; + + return 0; +} + +/* + * Run this as first test to verify that the id mappings used by other + * tests are available in the cache. + */ +static void test_cached_id_mappings(void **state) +{ + struct dom_sid *sids = *state; + int i; + + for (i = 0; i < ARRAY_SIZE(test_sids); i++) { + struct dom_sid *sid = &sids[i]; + struct unixid *unix_id = &test_sids[i].unix_id; + uid_t uid; + gid_t gid; + + switch(unix_id->type) { + case ID_TYPE_UID: + assert_true(sid_to_uid(sid, &uid)); + assert_int_equal(uid, unix_id->id); + assert_false(sid_to_gid(sid, &gid)); + break; + case ID_TYPE_GID: + assert_false(sid_to_uid(sid, &uid)); + assert_true(sid_to_gid(sid, &gid)); + assert_int_equal(gid, unix_id->id); + break; + case ID_TYPE_BOTH: + assert_true(sid_to_uid(sid, &uid)); + assert_int_equal(uid, unix_id->id); + assert_true(sid_to_gid(sid, &gid)); + assert_int_equal(gid, unix_id->id); + break; + default: + fail_msg("Unknown id type %d\n", unix_id->type); + break; + } + } +} + +static void test_empty_nfs4_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], false, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 0); + assert_null(dacl_aces); + + TALLOC_FREE(frame); +} + +static void test_empty_dacl_to_nfs4(void **state) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, 0, NULL); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, ¶ms, 1001, 1002); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 0); + assert_null(smb_first_ace4(nfs4_acl)); +} + +struct ace_dacl_type_mapping { + uint32_t nfs4_type; + enum security_ace_type dacl_type; +} ace_dacl_type_mapping[] = { + { SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, SEC_ACE_TYPE_ACCESS_ALLOWED }, + { SMB_ACE4_ACCESS_DENIED_ACE_TYPE, SEC_ACE_TYPE_ACCESS_DENIED }, +}; + +static void test_acl_type_nfs4_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(ace_dacl_type_mapping); i++) { + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.uid = 1000, + .aceType = ace_dacl_type_mapping[i].nfs4_type, + .aceFlags = 0, + .aceMask = SMB_ACE4_READ_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[2], &sids[3], false, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 1); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, + ace_dacl_type_mapping[i].dacl_type); + assert_int_equal(dacl_aces[0].flags, 0); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + } + + TALLOC_FREE(frame); +} + +static void test_acl_type_dacl_to_nfs4(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(ace_dacl_type_mapping); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[1]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], &sids[0], + ace_dacl_type_mapping[i].dacl_type, + SEC_FILE_READ_DATA, 0); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, ¶ms, + 101, 102); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, 0); + assert_int_equal(nfs4_ace->aceType, + ace_dacl_type_mapping[i].nfs4_type); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + } + + TALLOC_FREE(frame); +} + +struct ace_flag_mapping_nfs4_to_dacl { + bool is_directory; + uint32_t nfs4_flag; + uint32_t dacl_flag; +} ace_flags_nfs4_to_dacl[] = { + { true, SMB_ACE4_FILE_INHERIT_ACE, + SEC_ACE_FLAG_OBJECT_INHERIT }, + { false, SMB_ACE4_FILE_INHERIT_ACE, + 0 }, + { true, SMB_ACE4_DIRECTORY_INHERIT_ACE, + SEC_ACE_FLAG_CONTAINER_INHERIT }, + { false, SMB_ACE4_DIRECTORY_INHERIT_ACE, + 0 }, + { true, SMB_ACE4_NO_PROPAGATE_INHERIT_ACE, + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT }, + { false, SMB_ACE4_NO_PROPAGATE_INHERIT_ACE, + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT }, + { true, SMB_ACE4_INHERIT_ONLY_ACE, + SEC_ACE_FLAG_INHERIT_ONLY }, + { false, SMB_ACE4_INHERIT_ONLY_ACE, + SEC_ACE_FLAG_INHERIT_ONLY }, + { true, SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, + 0 }, + { false, SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, + 0 }, + { true, SMB_ACE4_FAILED_ACCESS_ACE_FLAG, + 0 }, + { false, SMB_ACE4_FAILED_ACCESS_ACE_FLAG, + 0 }, + { true, SMB_ACE4_INHERITED_ACE, + SEC_ACE_FLAG_INHERITED_ACE }, + { false, SMB_ACE4_INHERITED_ACE, + SEC_ACE_FLAG_INHERITED_ACE }, +}; + +static void test_ace_flags_nfs4_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + SMB_ACE4PROP_T nfs4_ace; + int i; + + for (i = 0; i < ARRAY_SIZE(ace_flags_nfs4_to_dacl); i++) { + struct SMB4ACL_T *nfs4_acl; + bool is_directory; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.uid = 1000, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = ace_flags_nfs4_to_dacl[i].nfs4_flag, + .aceMask = SMB_ACE4_READ_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + is_directory = ace_flags_nfs4_to_dacl[i].is_directory; + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[2], &sids[3], is_directory, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 1); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, + ace_flags_nfs4_to_dacl[i].dacl_flag); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + } + + TALLOC_FREE(frame); +} + +struct ace_flag_mapping_dacl_to_nfs4 { + bool is_directory; + uint32_t dacl_flag; + uint32_t nfs4_flag; +} ace_flags_dacl_to_nfs4[] = { + { true, SEC_ACE_FLAG_OBJECT_INHERIT, + SMB_ACE4_FILE_INHERIT_ACE }, + { false, SEC_ACE_FLAG_OBJECT_INHERIT, + 0 }, + { true, SEC_ACE_FLAG_CONTAINER_INHERIT, + SMB_ACE4_DIRECTORY_INHERIT_ACE }, + { false, SEC_ACE_FLAG_CONTAINER_INHERIT, + 0 }, + { true, SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + SMB_ACE4_NO_PROPAGATE_INHERIT_ACE }, + { false, SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + 0 }, + { true, SEC_ACE_FLAG_INHERIT_ONLY, + SMB_ACE4_INHERIT_ONLY_ACE }, + { false, SEC_ACE_FLAG_INHERIT_ONLY, + 0 }, + { true, SEC_ACE_FLAG_INHERITED_ACE, + SMB_ACE4_INHERITED_ACE }, + { false, SEC_ACE_FLAG_INHERITED_ACE, + SMB_ACE4_INHERITED_ACE }, + { true, SEC_ACE_FLAG_SUCCESSFUL_ACCESS, + 0 }, + { false, SEC_ACE_FLAG_SUCCESSFUL_ACCESS, + 0 }, + { true, SEC_ACE_FLAG_FAILED_ACCESS, + 0 }, + { false, SEC_ACE_FLAG_FAILED_ACCESS, + 0 }, +}; + +static void test_ace_flags_dacl_to_nfs4(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(ace_flags_dacl_to_nfs4); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + bool is_directory; + struct security_ace dacl_aces[1]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + ace_flags_dacl_to_nfs4[i].dacl_flag); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + is_directory = ace_flags_dacl_to_nfs4[i].is_directory; + nfs4_acl = smbacl4_win2nfs4(frame, is_directory, dacl, ¶ms, + 101, 102); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + ace_flags_dacl_to_nfs4[i].nfs4_flag); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + } + + TALLOC_FREE(frame); +} + +struct ace_perm_mapping { + uint32_t nfs4_perm; + uint32_t dacl_perm; +} perm_table_nfs4_to_dacl[] = { + { SMB_ACE4_READ_DATA, SEC_FILE_READ_DATA }, + { SMB_ACE4_LIST_DIRECTORY, SEC_DIR_LIST }, + { SMB_ACE4_WRITE_DATA, SEC_FILE_WRITE_DATA }, + { SMB_ACE4_ADD_FILE, SEC_DIR_ADD_FILE }, + { SMB_ACE4_APPEND_DATA, SEC_FILE_APPEND_DATA }, + { SMB_ACE4_ADD_SUBDIRECTORY, SEC_DIR_ADD_SUBDIR, }, + { SMB_ACE4_READ_NAMED_ATTRS, SEC_FILE_READ_EA }, + { SMB_ACE4_READ_NAMED_ATTRS, SEC_DIR_READ_EA }, + { SMB_ACE4_WRITE_NAMED_ATTRS, SEC_FILE_WRITE_EA }, + { SMB_ACE4_WRITE_NAMED_ATTRS, SEC_DIR_WRITE_EA }, + { SMB_ACE4_EXECUTE, SEC_FILE_EXECUTE }, + { SMB_ACE4_EXECUTE, SEC_DIR_TRAVERSE }, + { SMB_ACE4_DELETE_CHILD, SEC_DIR_DELETE_CHILD }, + { SMB_ACE4_READ_ATTRIBUTES, SEC_FILE_READ_ATTRIBUTE }, + { SMB_ACE4_READ_ATTRIBUTES, SEC_DIR_READ_ATTRIBUTE }, + { SMB_ACE4_WRITE_ATTRIBUTES, SEC_FILE_WRITE_ATTRIBUTE }, + { SMB_ACE4_WRITE_ATTRIBUTES, SEC_DIR_WRITE_ATTRIBUTE }, + { SMB_ACE4_DELETE, SEC_STD_DELETE }, + { SMB_ACE4_READ_ACL, SEC_STD_READ_CONTROL }, + { SMB_ACE4_WRITE_ACL, SEC_STD_WRITE_DAC, }, + { SMB_ACE4_WRITE_OWNER, SEC_STD_WRITE_OWNER }, + { SMB_ACE4_SYNCHRONIZE, SEC_STD_SYNCHRONIZE }, +}; + +static void test_nfs4_permissions_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(perm_table_nfs4_to_dacl); i++) { + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.uid = 1000, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = perm_table_nfs4_to_dacl[i].nfs4_perm, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], false, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 1); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, 0); + assert_int_equal(dacl_aces[0].access_mask, + perm_table_nfs4_to_dacl[i].dacl_perm); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + } + + TALLOC_FREE(frame); +} + +struct ace_perm_mapping_dacl_to_nfs4 { + uint32_t dacl_perm; + uint32_t nfs4_perm; +} perm_table_dacl_to_nfs4[] = { + { SEC_FILE_READ_DATA, SMB_ACE4_READ_DATA, }, + { SEC_DIR_LIST, SMB_ACE4_LIST_DIRECTORY, }, + { SEC_FILE_WRITE_DATA, SMB_ACE4_WRITE_DATA, }, + { SEC_DIR_ADD_FILE, SMB_ACE4_ADD_FILE, }, + { SEC_FILE_APPEND_DATA, SMB_ACE4_APPEND_DATA, }, + { SEC_DIR_ADD_SUBDIR, SMB_ACE4_ADD_SUBDIRECTORY, }, + { SEC_FILE_READ_EA, SMB_ACE4_READ_NAMED_ATTRS, }, + { SEC_DIR_READ_EA, SMB_ACE4_READ_NAMED_ATTRS, }, + { SEC_FILE_WRITE_EA, SMB_ACE4_WRITE_NAMED_ATTRS, }, + { SEC_DIR_WRITE_EA, SMB_ACE4_WRITE_NAMED_ATTRS, }, + { SEC_FILE_EXECUTE, SMB_ACE4_EXECUTE, }, + { SEC_DIR_TRAVERSE, SMB_ACE4_EXECUTE, }, + { SEC_DIR_DELETE_CHILD, SMB_ACE4_DELETE_CHILD, }, + { SEC_FILE_READ_ATTRIBUTE, SMB_ACE4_READ_ATTRIBUTES, }, + { SEC_DIR_READ_ATTRIBUTE, SMB_ACE4_READ_ATTRIBUTES, }, + { SEC_FILE_WRITE_ATTRIBUTE, SMB_ACE4_WRITE_ATTRIBUTES, }, + { SEC_DIR_WRITE_ATTRIBUTE, SMB_ACE4_WRITE_ATTRIBUTES, }, + { SEC_STD_DELETE, SMB_ACE4_DELETE, }, + { SEC_STD_READ_CONTROL, SMB_ACE4_READ_ACL, }, + { SEC_STD_WRITE_DAC, SMB_ACE4_WRITE_ACL, }, + { SEC_STD_WRITE_OWNER, SMB_ACE4_WRITE_OWNER, }, + { SEC_STD_SYNCHRONIZE, SMB_ACE4_SYNCHRONIZE, }, + { SEC_GENERIC_READ, SMB_ACE4_READ_ACL| + SMB_ACE4_READ_DATA| + SMB_ACE4_READ_ATTRIBUTES| + SMB_ACE4_READ_NAMED_ATTRS| + SMB_ACE4_SYNCHRONIZE }, + { SEC_GENERIC_WRITE, SMB_ACE4_WRITE_ACL| + SMB_ACE4_WRITE_DATA| + SMB_ACE4_WRITE_ATTRIBUTES| + SMB_ACE4_WRITE_NAMED_ATTRS| + SMB_ACE4_SYNCHRONIZE }, + { SEC_GENERIC_EXECUTE, SMB_ACE4_READ_ACL| + SMB_ACE4_READ_ATTRIBUTES| + SMB_ACE4_EXECUTE| + SMB_ACE4_SYNCHRONIZE }, + { SEC_GENERIC_ALL, SMB_ACE4_DELETE| + SMB_ACE4_READ_ACL| + SMB_ACE4_WRITE_ACL| + SMB_ACE4_WRITE_OWNER| + SMB_ACE4_SYNCHRONIZE| + SMB_ACE4_WRITE_ATTRIBUTES| + SMB_ACE4_READ_ATTRIBUTES| + SMB_ACE4_EXECUTE| + SMB_ACE4_READ_NAMED_ATTRS| + SMB_ACE4_WRITE_NAMED_ATTRS| + SMB_ACE4_WRITE_DATA| + SMB_ACE4_APPEND_DATA| + SMB_ACE4_READ_DATA| + SMB_ACE4_DELETE_CHILD }, +}; + +static void test_dacl_permissions_to_nfs4(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(perm_table_nfs4_to_dacl); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + struct security_ace dacl_aces[1]; + struct security_acl *dacl; + + init_sec_ace(&dacl_aces[0], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, + perm_table_dacl_to_nfs4[i].dacl_perm, 0); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, ¶ms, + 101, 102); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, 0); + assert_int_equal(nfs4_ace->aceMask, + perm_table_dacl_to_nfs4[i].nfs4_perm); + } + + TALLOC_FREE(frame); +} + +/* + * Create NFS4 ACL with all possible "special" entries. Verify that + * the ones that should be mapped to a DACL are mapped and the other + * ones are ignored. + */ +static void test_special_nfs4_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_OWNER, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_READ_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_GROUP, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_EVERYONE, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_APPEND_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_INTERACTIVE, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_READ_NAMED_ATTRS, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_NETWORK, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_WRITE_NAMED_ATTRS, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_DIALUP, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_EXECUTE, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_BATCH, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_READ_ATTRIBUTES, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_ANONYMOUS, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_WRITE_ATTRIBUTES, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_AUTHENTICATED, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_READ_ACL, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_SERVICE, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = SMB_ACE4_WRITE_ACL, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], false, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 3); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, 0); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + + assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[1].flags, 0); + assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA); + assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[1])); + + assert_int_equal(dacl_aces[2].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[2].flags, 0); + assert_int_equal(dacl_aces[2].access_mask, SEC_FILE_APPEND_DATA); + assert_true(dom_sid_equal(&dacl_aces[2].trustee, &global_sid_World)); + + TALLOC_FREE(frame); +} + +static void test_dacl_to_special_nfs4(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[6]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_dontcare, + .map_full_control = true, + }; + + /* + * global_Sid_World is mapped to EVERYONE. + */ + init_sec_ace(&dacl_aces[0], &global_sid_World, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, 0); + /* + * global_sid_Unix_NFS is ignored. + */ + init_sec_ace(&dacl_aces[1], &global_sid_Unix_NFS, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, 0); + /* + * Anything that maps to owner or owning group with inheritance flags + * is NOT mapped to special owner or special group. + */ + init_sec_ace(&dacl_aces[2], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[3], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + init_sec_ace(&dacl_aces[4], &sids[1], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[5], &sids[1], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, 1000, 1001); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 5); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_EVERYONE); + assert_int_equal(nfs4_ace->aceFlags, 0); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_WRITE_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->who.gid, 1001); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + assert_int_equal(nfs4_ace->who.gid, 1001); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + assert_null(smb_next_ace4(nfs4_ace_container)); + + TALLOC_FREE(frame); +} + +struct creator_ace_flags { + uint32_t dacl_flags; + uint32_t nfs4_flags; +} creator_ace_flags[] = { + { 0, 0 }, + + { SEC_ACE_FLAG_INHERIT_ONLY, 0 }, + + { SEC_ACE_FLAG_CONTAINER_INHERIT, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, + + { SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, + + { SEC_ACE_FLAG_OBJECT_INHERIT, SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, + { SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, + + { SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_OBJECT_INHERIT, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, + + { SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_FILE_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE }, +}; + +static void test_dacl_creator_to_nfs4(void **state) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(creator_ace_flags); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[2]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], &global_sid_Creator_Owner, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + creator_ace_flags[i].dacl_flags); + init_sec_ace(&dacl_aces[1], &global_sid_Creator_Group, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + creator_ace_flags[i].dacl_flags); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, + 101, 102); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + + if (creator_ace_flags[i].nfs4_flags == 0) { + /* + * CREATOR OWNER and CREATOR GROUP not mapped + * in this case. + */ + assert_null(smb_first_ace4(nfs4_acl)); + } else { + assert_int_equal(smb_get_naces(nfs4_acl), 2); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->who.special_id, + SMB_ACE4_WHO_OWNER); + assert_int_equal(nfs4_ace->aceFlags, + creator_ace_flags[i].nfs4_flags); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->who.special_id, + SMB_ACE4_WHO_GROUP); + assert_int_equal(nfs4_ace->aceFlags, + creator_ace_flags[i].nfs4_flags); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + } + } + + TALLOC_FREE(frame); +} + +struct creator_owner_nfs4_to_dacl { + uint32_t special_id; + uint32_t nfs4_ace_flags; + uint32_t dacl_ace_flags; +} creator_owner_nfs4_to_dacl[] = { + { SMB_ACE4_WHO_OWNER, + SMB_ACE4_FILE_INHERIT_ACE, + SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY }, + { SMB_ACE4_WHO_OWNER, + SMB_ACE4_DIRECTORY_INHERIT_ACE, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY }, + { SMB_ACE4_WHO_OWNER, + SMB_ACE4_FILE_INHERIT_ACE|SMB_ACE4_DIRECTORY_INHERIT_ACE, + SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY }, + { SMB_ACE4_WHO_GROUP, + SMB_ACE4_FILE_INHERIT_ACE, + SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY }, + { SMB_ACE4_WHO_GROUP, + SMB_ACE4_DIRECTORY_INHERIT_ACE, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY }, + { SMB_ACE4_WHO_GROUP, + SMB_ACE4_FILE_INHERIT_ACE|SMB_ACE4_DIRECTORY_INHERIT_ACE, + SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY }, +}; + +static void test_nfs4_to_dacl_creator(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(creator_owner_nfs4_to_dacl); i++) { + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces, *creator_dacl_ace; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id + = creator_owner_nfs4_to_dacl[i].special_id, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags + = creator_owner_nfs4_to_dacl[i].nfs4_ace_flags, + .aceMask = SMB_ACE4_READ_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], true, + &dacl_aces, &good_aces)); + assert_non_null(dacl_aces); + + if (creator_owner_nfs4_to_dacl[i].nfs4_ace_flags & + SMB_ACE4_INHERIT_ONLY_ACE) { + /* + * Only one ACE entry for the CREATOR ACE entry. + */ + assert_int_equal(good_aces, 1); + creator_dacl_ace = &dacl_aces[0]; + } else { + /* + * This creates an additional ACE entry for + * the permissions on the current object. + */ + assert_int_equal(good_aces, 2); + + assert_int_equal(dacl_aces[0].type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, 0); + assert_int_equal(dacl_aces[0].access_mask, + SEC_FILE_READ_DATA); + + if (creator_owner_nfs4_to_dacl[i].special_id == + SMB_ACE4_WHO_OWNER) { + assert_true(dom_sid_equal(&dacl_aces[0].trustee, + &sids[0])); + } + + if (creator_owner_nfs4_to_dacl[i].special_id == + SMB_ACE4_WHO_GROUP) { + assert_true(dom_sid_equal(&dacl_aces[0].trustee, + &sids[1])); + } + + creator_dacl_ace = &dacl_aces[1]; + } + + assert_int_equal(creator_dacl_ace->type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(creator_dacl_ace->flags, + creator_owner_nfs4_to_dacl[i].dacl_ace_flags); + assert_int_equal(creator_dacl_ace->access_mask, + SEC_FILE_READ_DATA); + if (creator_owner_nfs4_to_dacl[i].special_id == + SMB_ACE4_WHO_OWNER) { + assert_true(dom_sid_equal(&creator_dacl_ace->trustee, + &global_sid_Creator_Owner)); + } + + if (creator_owner_nfs4_to_dacl[i].special_id == + SMB_ACE4_WHO_GROUP) { + assert_true(dom_sid_equal(&creator_dacl_ace->trustee, + &global_sid_Creator_Group)); + } + } + + TALLOC_FREE(frame); +} + +struct nfs4_to_dacl_map_full_control{ + bool is_dir; + bool config; + bool delete_child_added; +} nfs4_to_dacl_full_control[] = { + { true, true, false }, + { true, false, false }, + { false, true, true }, + { false, false, false }, +}; + +static void test_full_control_nfs4_to_dacl(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(nfs4_to_dacl_full_control); i++) { + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = nfs4_to_dacl_full_control[i].config, + }; + const uint32_t nfs4_ace_mask_except_deletes = + SMB_ACE4_READ_DATA|SMB_ACE4_WRITE_DATA| + SMB_ACE4_APPEND_DATA|SMB_ACE4_READ_NAMED_ATTRS| + SMB_ACE4_WRITE_NAMED_ATTRS|SMB_ACE4_EXECUTE| + SMB_ACE4_READ_ATTRIBUTES|SMB_ACE4_WRITE_ATTRIBUTES| + SMB_ACE4_READ_ACL|SMB_ACE4_WRITE_ACL| + SMB_ACE4_WRITE_OWNER|SMB_ACE4_SYNCHRONIZE; + const uint32_t dacl_ace_mask_except_deletes = + SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA| + SEC_FILE_APPEND_DATA|SEC_FILE_READ_EA| + SEC_FILE_WRITE_EA|SEC_FILE_EXECUTE| + SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE| + SEC_STD_READ_CONTROL|SEC_STD_WRITE_DAC| + SEC_STD_WRITE_OWNER|SEC_STD_SYNCHRONIZE; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.uid = 1000, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = 0, + .aceMask = nfs4_ace_mask_except_deletes, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true( + smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], + nfs4_to_dacl_full_control[i].is_dir, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 1); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, 0); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + if (nfs4_to_dacl_full_control[i].delete_child_added) { + assert_int_equal(dacl_aces[0].access_mask, + dacl_ace_mask_except_deletes| + SEC_DIR_DELETE_CHILD); + } else { + assert_int_equal(dacl_aces[0].access_mask, + dacl_ace_mask_except_deletes); + } + } + + TALLOC_FREE(frame); +} + +struct acedup_settings { + enum smbacl4_acedup_enum setting; +} acedup_settings[] = { + { e_dontcare }, + { e_reject }, + { e_ignore }, + { e_merge }, +}; + +static void test_dacl_to_nfs4_acedup_settings(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(acedup_settings); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[2]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = acedup_settings[i].setting, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[1], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, + 101, 102); + + switch(params.acedup) { + case e_dontcare: + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 2); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, + SMB_ACE4_WRITE_DATA); + break; + + case e_reject: + assert_null(nfs4_acl); + assert_int_equal(errno, EINVAL); + break; + + case e_ignore: + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + break; + + case e_merge: + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, + SMB_ACE4_READ_DATA| + SMB_ACE4_WRITE_DATA); + break; + + default: + fail_msg("Unexpected value for acedup: %d\n", + params.acedup); + }; + } + + TALLOC_FREE(frame); +} + +struct acedup_match { + int sid_idx1; + enum security_ace_type type1; + uint32_t ace_mask1; + uint8_t flag1; + int sid_idx2; + enum security_ace_type type2; + uint32_t ace_mask2; + uint8_t flag2; + bool match; +} acedup_match[] = { + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + true }, + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 1, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + false }, + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SEC_ACE_TYPE_ACCESS_DENIED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + false }, + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + true }, + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT, + false }, + { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + 5, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT, + false }, +}; + +static void test_dacl_to_nfs4_acedup_match(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + for (i = 0; i < ARRAY_SIZE(acedup_match); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[2]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_ignore, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], + &sids[acedup_match[i].sid_idx1], + acedup_match[i].type1, + acedup_match[i].ace_mask1, + acedup_match[i].flag1); + init_sec_ace(&dacl_aces[1], + &sids[acedup_match[i].sid_idx2], + acedup_match[i].type2, + acedup_match[i].ace_mask2, + acedup_match[i].flag2); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, + 101, 102); + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + + if (acedup_match[i].match) { + assert_int_equal(smb_get_naces(nfs4_acl), 1); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + } else { + assert_int_equal(smb_get_naces(nfs4_acl), 2); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->who.uid, 1000); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + assert_null(smb_next_ace4(nfs4_ace_container)); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + } + } + + TALLOC_FREE(frame); +} + +static void test_dacl_to_nfs4_config_special(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[6]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_special, + .do_chown = true, + .acedup = e_dontcare, + .map_full_control = true, + }; + + /* + * global_sid_Creator_Owner or global_sid_Special_Group is NOT mapped + * to SMB_ACE4_ID_SPECIAL. + */ + init_sec_ace(&dacl_aces[0], &global_sid_Creator_Owner, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[1], &global_sid_Creator_Group, + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT); + /* + * Anything that maps to owner or owning group with inheritance flags + * IS mapped to special owner or special group. + */ + init_sec_ace(&dacl_aces[2], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[3], &sids[0], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + init_sec_ace(&dacl_aces[4], &sids[1], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_OBJECT_INHERIT); + init_sec_ace(&dacl_aces[5], &sids[1], + SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, + SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, 1000, 1001); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), 6); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->who.uid, 1003); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, 0); + assert_int_equal(nfs4_ace->aceFlags, + SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_DIRECTORY_INHERIT_ACE); + assert_int_equal(nfs4_ace->who.gid, 1004); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_WRITE_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_OWNER); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_OWNER); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_FILE_INHERIT_ACE); + assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_GROUP); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL); + assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_DIRECTORY_INHERIT_ACE| + SMB_ACE4_INHERIT_ONLY_ACE); + assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_GROUP); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + assert_null(smb_next_ace4(nfs4_ace_container)); + + TALLOC_FREE(frame); +} + +static void test_nfs4_to_dacl_config_special(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_special, + .do_chown = true, + .acedup = e_dontcare, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + /* + * In config mode special, this is not mapped to Creator Owner + */ + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_OWNER, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = SMB_ACE4_FILE_INHERIT_ACE, + .aceMask = SMB_ACE4_READ_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + /* + * In config mode special, this is not mapped to Creator Group + */ + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = SMB_ACE4_ID_SPECIAL, + .who.special_id = SMB_ACE4_WHO_GROUP, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = SMB_ACE4_DIRECTORY_INHERIT_ACE, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], true, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 2); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, SEC_ACE_FLAG_OBJECT_INHERIT); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0])); + + assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[1].flags, SEC_ACE_FLAG_CONTAINER_INHERIT); + assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA); + assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[1])); + + TALLOC_FREE(frame); +} + +struct nfs_to_dacl_idmap_both { + uint32_t nfs4_flags; + uint32_t nfs4_id; + struct dom_sid *sid; +}; + +static void test_nfs4_to_dacl_idmap_type_both(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + struct nfs_to_dacl_idmap_both nfs_to_dacl_idmap_both[] = { + { 0, 1002, &sids[2] }, + { SMB_ACE4_IDENTIFIER_GROUP, 1002, &sids[2] }, + { 0, 1005, &sids[6] }, + { SMB_ACE4_IDENTIFIER_GROUP, 1005, &sids[6] }, + }; + + for (i = 0; i < ARRAY_SIZE(nfs_to_dacl_idmap_both); i++) { + struct SMB4ACL_T *nfs4_acl; + struct security_ace *dacl_aces; + SMB_ACE4PROP_T nfs4_ace; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = nfs_to_dacl_idmap_both[i].nfs4_flags, + .aceMask = SMB_ACE4_READ_DATA, + }; + + if (nfs_to_dacl_idmap_both[i].nfs4_flags & + SMB_ACE4_IDENTIFIER_GROUP) { + nfs4_ace.who.gid = nfs_to_dacl_idmap_both[i].nfs4_id; + } else { + nfs4_ace.who.uid = nfs_to_dacl_idmap_both[i].nfs4_id; + } + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[2], &sids[2], + false, &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 1); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, + SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, 0); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, + nfs_to_dacl_idmap_both[i].sid)); + } + + TALLOC_FREE(frame); +} + +struct dacl_to_nfs4_idmap_both { + struct dom_sid *sid; + uint32_t dacl_flags; + uint32_t nfs4_flags; + uint32_t nfs4_ace_flags; + uint32_t nfs4_id; + int num_nfs4_aces; +}; + +/* + * IDMAP_TYPE_BOTH always creates group entries. + */ +static void test_dacl_to_nfs4_idmap_type_both(void **state) +{ + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + int i; + + struct dacl_to_nfs4_idmap_both dacl_to_nfs4_idmap_both[] = { + { &sids[2], 0, + SMB_ACE4_ID_SPECIAL, SMB_ACE4_IDENTIFIER_GROUP, SMB_ACE4_WHO_GROUP, + 2 }, + { &sids[2], SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SMB_ACE4_IDENTIFIER_GROUP|SMB_ACE4_FILE_INHERIT_ACE, 1002, + 1 }, + { &sids[6], 0, + 0, SMB_ACE4_IDENTIFIER_GROUP, 1005, + 1 }, + { &sids[6], SEC_ACE_FLAG_OBJECT_INHERIT, + 0, SMB_ACE4_IDENTIFIER_GROUP|SMB_ACE4_FILE_INHERIT_ACE, 1005, + 1 }, + }; + + for (i = 0; i < ARRAY_SIZE(dacl_to_nfs4_idmap_both); i++) { + struct SMB4ACL_T *nfs4_acl; + struct SMB4ACE_T *nfs4_ace_container; + SMB_ACE4PROP_T *nfs4_ace; + struct security_ace dacl_aces[1]; + struct security_acl *dacl; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_merge, + .map_full_control = true, + }; + + init_sec_ace(&dacl_aces[0], dacl_to_nfs4_idmap_both[i].sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_READ_DATA, + dacl_to_nfs4_idmap_both[i].dacl_flags); + dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, + ARRAY_SIZE(dacl_aces), dacl_aces); + assert_non_null(dacl); + + nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, ¶ms, + 1002, 1002); + + assert_non_null(nfs4_acl); + assert_int_equal(smbacl4_get_controlflags(nfs4_acl), + SEC_DESC_SELF_RELATIVE); + assert_int_equal(smb_get_naces(nfs4_acl), + dacl_to_nfs4_idmap_both[i].num_nfs4_aces); + + nfs4_ace_container = smb_first_ace4(nfs4_acl); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, + dacl_to_nfs4_idmap_both[i].nfs4_flags); + assert_int_equal(nfs4_ace->aceFlags, + dacl_to_nfs4_idmap_both[i].nfs4_ace_flags); + if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) { + assert_int_equal(nfs4_ace->who.special_id, + dacl_to_nfs4_idmap_both[i].nfs4_id); + } else if (nfs4_ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) { + assert_int_equal(nfs4_ace->who.gid, + dacl_to_nfs4_idmap_both[i].nfs4_id); + } else { + assert_int_equal(nfs4_ace->who.uid, + dacl_to_nfs4_idmap_both[i].nfs4_id); + } + assert_int_equal(nfs4_ace->aceType, + SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + + if (dacl_to_nfs4_idmap_both[i].num_nfs4_aces == 2) { + nfs4_ace_container = smb_next_ace4(nfs4_ace_container); + assert_non_null(nfs4_ace_container); + + nfs4_ace = smb_get_ace4(nfs4_ace_container); + assert_int_equal(nfs4_ace->flags, + dacl_to_nfs4_idmap_both[i].nfs4_flags); + assert_int_equal(nfs4_ace->aceFlags, + dacl_to_nfs4_idmap_both[i].nfs4_ace_flags & + ~SMB_ACE4_IDENTIFIER_GROUP); + if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) { + assert_int_equal(nfs4_ace->who.special_id, + SMB_ACE4_WHO_OWNER); + } else { + assert_int_equal(nfs4_ace->who.uid, + dacl_to_nfs4_idmap_both[i].nfs4_id); + } + assert_int_equal(nfs4_ace->aceType, + SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE); + assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA); + } + } + + TALLOC_FREE(frame); +} + +static void test_nfs4_to_dacl_remove_duplicate(void **state) +{ + + struct dom_sid *sids = *state; + TALLOC_CTX *frame = talloc_stackframe(); + struct SMB4ACL_T *nfs4_acl; + SMB_ACE4PROP_T nfs4_ace; + struct security_ace *dacl_aces; + int good_aces; + struct smbacl4_vfs_params params = { + .mode = e_simple, + .do_chown = true, + .acedup = e_dontcare, + .map_full_control = true, + }; + + nfs4_acl = smb_create_smb4acl(frame); + assert_non_null(nfs4_acl); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.uid = 1002, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = SMB_ACE4_INHERITED_ACE, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.gid = 1002, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_INHERITED_ACE, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.gid = 1002, + .aceType = SMB_ACE4_ACCESS_DENIED_ACE_TYPE, + .aceFlags = SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_INHERITED_ACE, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + nfs4_ace = (SMB_ACE4PROP_T) { + .flags = 0, + .who.gid = 1002, + .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, + .aceFlags = SMB_ACE4_IDENTIFIER_GROUP| + SMB_ACE4_INHERITED_ACE, + .aceMask = SMB_ACE4_WRITE_DATA, + }; + assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace)); + + assert_true(smbacl4_nfs42win(frame, ¶ms, nfs4_acl, + &sids[0], &sids[1], true, + &dacl_aces, &good_aces)); + + assert_int_equal(good_aces, 2); + assert_non_null(dacl_aces); + + assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED); + assert_int_equal(dacl_aces[0].flags, SEC_ACE_FLAG_INHERITED_ACE); + assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_WRITE_DATA); + assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[2])); + + assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_DENIED); + assert_int_equal(dacl_aces[1].flags, SEC_ACE_FLAG_INHERITED_ACE); + assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA); + assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[2])); + + TALLOC_FREE(frame); +} + +int main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_cached_id_mappings), + cmocka_unit_test(test_empty_nfs4_to_dacl), + cmocka_unit_test(test_empty_dacl_to_nfs4), + cmocka_unit_test(test_acl_type_nfs4_to_dacl), + cmocka_unit_test(test_acl_type_dacl_to_nfs4), + cmocka_unit_test(test_ace_flags_nfs4_to_dacl), + cmocka_unit_test(test_ace_flags_dacl_to_nfs4), + cmocka_unit_test(test_nfs4_permissions_to_dacl), + cmocka_unit_test(test_dacl_permissions_to_nfs4), + cmocka_unit_test(test_special_nfs4_to_dacl), + cmocka_unit_test(test_dacl_to_special_nfs4), + cmocka_unit_test(test_dacl_creator_to_nfs4), + cmocka_unit_test(test_nfs4_to_dacl_creator), + cmocka_unit_test(test_full_control_nfs4_to_dacl), + cmocka_unit_test(test_dacl_to_nfs4_acedup_settings), + cmocka_unit_test(test_dacl_to_nfs4_acedup_match), + cmocka_unit_test(test_dacl_to_nfs4_config_special), + cmocka_unit_test(test_nfs4_to_dacl_config_special), + cmocka_unit_test(test_nfs4_to_dacl_idmap_type_both), + cmocka_unit_test(test_dacl_to_nfs4_idmap_type_both), + cmocka_unit_test(test_nfs4_to_dacl_remove_duplicate), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + if (argc != 2) { + print_error("Usage: %s smb.conf\n", argv[0]); + exit(1); + } + + /* + * Initialize enough of the Samba internals to have the + * mappings tests work. + */ + talloc_stackframe(); + lp_load_global(argv[1]); + + return cmocka_run_group_tests(tests, group_setup, group_teardown); +} diff --git a/source3/modules/test_vfs_full_audit.c b/source3/modules/test_vfs_full_audit.c new file mode 100644 index 0000000..4a12e46 --- /dev/null +++ b/source3/modules/test_vfs_full_audit.c @@ -0,0 +1,49 @@ +/* + * Unix SMB/CIFS implementation. + * + * Unit test for entries in vfs_full_audit arrays. + * + * Copyright (C) Jeremy Allison 2020 + * + * 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 . + */ + +/* Needed for static build to complete... */ +#include "includes.h" +#include "smbd/smbd.h" +NTSTATUS vfs_full_audit_init(TALLOC_CTX *ctx); + +#include "vfs_full_audit.c" +#include + +static void test_full_audit_array(void **state) +{ + unsigned i; + + for (i=0; i. + */ + +#include "vfs_gpfs.c" +#include + +static void test_share_deny_mapping(void **state) +{ + assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_NONE), + GPFS_DENY_READ|GPFS_DENY_WRITE|GPFS_DENY_DELETE); + assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_READ), + GPFS_DENY_WRITE|GPFS_DENY_DELETE); + assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_WRITE), + GPFS_DENY_READ|GPFS_DENY_DELETE); + assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_DELETE), + GPFS_DENY_READ|GPFS_DENY_WRITE); + assert_int_equal(vfs_gpfs_share_access_to_deny( + FILE_SHARE_READ|FILE_SHARE_DELETE), + GPFS_DENY_WRITE); + assert_int_equal(vfs_gpfs_share_access_to_deny( + FILE_SHARE_WRITE|FILE_SHARE_DELETE), + GPFS_DENY_READ); + assert_int_equal(vfs_gpfs_share_access_to_deny( + FILE_SHARE_READ|FILE_SHARE_WRITE), + 0); /* GPFS limitation, cannot deny only delete. */ +} + +#ifdef HAVE_KERNEL_OPLOCKS_LINUX +static void test_gpfs_lease_mapping(void **state) +{ + assert_int_equal(lease_type_to_gpfs(F_RDLCK), GPFS_LEASE_READ); + assert_int_equal(lease_type_to_gpfs(F_WRLCK), GPFS_LEASE_WRITE); + assert_int_equal(lease_type_to_gpfs(F_UNLCK), GPFS_LEASE_NONE); +} +#endif /* #ifdef HAVE_KERNEL_OPLOCKS_LINUX */ + +static void test_gpfs_winattrs_to_dosmode(void **state) +{ + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_ARCHIVE), + FILE_ATTRIBUTE_ARCHIVE); + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_READONLY), + FILE_ATTRIBUTE_READONLY); + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_HIDDEN), + FILE_ATTRIBUTE_HIDDEN); + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_OFFLINE), + FILE_ATTRIBUTE_OFFLINE); + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_SPARSE_FILE), + FILE_ATTRIBUTE_SPARSE); + assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_SYSTEM), + FILE_ATTRIBUTE_SYSTEM); +} + +static void test_dosmode_to_gpfs_winattrs(void **state) +{ + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_ARCHIVE), + GPFS_WINATTR_ARCHIVE); + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_HIDDEN), + GPFS_WINATTR_HIDDEN); + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_OFFLINE), + GPFS_WINATTR_OFFLINE); + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_READONLY), + GPFS_WINATTR_READONLY); + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_SPARSE), + GPFS_WINATTR_SPARSE_FILE); + assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_SYSTEM), + GPFS_WINATTR_SYSTEM); +} + +int main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_share_deny_mapping), +#ifdef HAVE_KERNEL_OPLOCKS_LINUX + cmocka_unit_test(test_gpfs_lease_mapping), +#endif /* #ifdef HAVE_KERNEL_OPLOCKS_LINUX */ + cmocka_unit_test(test_gpfs_winattrs_to_dosmode), + cmocka_unit_test(test_dosmode_to_gpfs_winattrs), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source3/modules/test_vfs_posixacl.c b/source3/modules/test_vfs_posixacl.c new file mode 100644 index 0000000..19e7d98 --- /dev/null +++ b/source3/modules/test_vfs_posixacl.c @@ -0,0 +1,171 @@ +/* + * Unix SMB/CIFS implementation. + * + * Unit test for vfs_posixacl + * + * Copyright (C) Christof Schmitt 2020 + * + * 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 "vfs_posixacl.c" +#include + +static void smb_acl_add_entry(struct smb_acl_t * smb_acl, + SMB_ACL_TAG_T tag, uint32_t id, + bool read, bool write, bool execute) +{ + int ret; + struct smb_acl_entry *smb_acl_entry = NULL; + SMB_ACL_PERMSET_T smb_permset = NULL; + + ret = sys_acl_create_entry(&smb_acl, &smb_acl_entry); + assert_int_equal(ret, 0); + + ret = sys_acl_set_tag_type(smb_acl_entry, tag); + assert_int_equal(ret, 0); + + if (tag == SMB_ACL_USER || tag == SMB_ACL_GROUP) { + ret = sys_acl_set_qualifier(smb_acl_entry, &id); + assert_int_equal(ret, 0); + } + + ret = sys_acl_get_permset(smb_acl_entry, &smb_permset); + assert_int_equal(ret, 0); + + if (read) { + ret = sys_acl_add_perm(smb_permset, SMB_ACL_READ); + assert_int_equal(ret, 0); + } + + if (write) { + ret = sys_acl_add_perm(smb_permset, SMB_ACL_WRITE); + assert_int_equal(ret, 0); + } + + if (execute) { + ret = sys_acl_add_perm(smb_permset, SMB_ACL_EXECUTE); + assert_int_equal(ret, 0); + } + + ret = sys_acl_set_permset(smb_acl_entry, smb_permset); + assert_int_equal(ret, 0); +} + +static void acl_check_entry(acl_entry_t acl_entry, SMB_ACL_TAG_T tag, + uint32_t id, + bool read, bool write, bool execute) +{ + int ret; + acl_permset_t acl_permset = NULL; + acl_tag_t acl_tag; + + ret = acl_get_permset(acl_entry, &acl_permset); + assert_int_equal(ret, 0); + + ret = acl_get_tag_type(acl_entry, &acl_tag); + assert_int_equal(ret, 0); + assert_int_equal(acl_tag, tag); + + if (tag == ACL_USER || tag == ACL_GROUP) { + uint32_t *id_p; + + id_p = acl_get_qualifier(acl_entry); + assert_non_null(id_p); + assert_int_equal(*id_p, id); + } + +#ifdef HAVE_ACL_GET_PERM_NP + ret = acl_get_perm_np(acl_permset, ACL_READ); +#else + ret = acl_get_perm(acl_permset, ACL_READ); +#endif + assert_int_equal(ret, read ? 1 : 0); + +#ifdef HAVE_ACL_GET_PERM_NP + ret = acl_get_perm_np(acl_permset, ACL_WRITE); +#else + ret = acl_get_perm(acl_permset, ACL_WRITE); +#endif + assert_int_equal(ret, write ? 1 : 0); + +#ifdef HAVE_ACL_GET_PERM_NP + ret = acl_get_perm_np(acl_permset, ACL_EXECUTE); +#else + ret = acl_get_perm(acl_permset, ACL_EXECUTE); +#endif + assert_int_equal(ret, execute ? 1 : 0); +} + +static void test_smb_acl_to_posix_simple_acl(void **state) +{ + TALLOC_CTX *mem_ctx = talloc_stackframe(); + struct smb_acl_t *smb_acl = NULL; + acl_t acl = NULL; + acl_entry_t acl_entry = NULL; + int ret; + + smb_acl = sys_acl_init(mem_ctx); + assert_non_null(smb_acl); + + smb_acl_add_entry(smb_acl, SMB_ACL_USER_OBJ, 0, false, true, false); + smb_acl_add_entry(smb_acl, SMB_ACL_GROUP_OBJ, 0, true, false, false); + smb_acl_add_entry(smb_acl, SMB_ACL_OTHER, 0, false, false, true); + + acl = smb_acl_to_posix(smb_acl); + assert_non_null(acl); + + ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); + assert_int_equal(ret, 1); + acl_check_entry(acl_entry, ACL_USER_OBJ, 0, false, true, false); + + ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); + assert_int_equal(ret, 1); + acl_check_entry(acl_entry, ACL_GROUP_OBJ, 0, true, false, false); + + ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); + assert_int_equal(ret, 1); + acl_check_entry(acl_entry, ACL_OTHER, 0, false, false, true); + + ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); + assert_int_equal(ret, 0); + + ret = acl_free(acl); + assert_int_equal(ret, 0); + + TALLOC_FREE(mem_ctx); +} + +int main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_smb_acl_to_posix_simple_acl), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + if (argc != 2) { + print_error("Usage: %s smb.conf\n", argv[0]); + exit(1); + } + + /* + * Initialize enough of the Samba internals to have the + * mappings tests work. + */ + talloc_stackframe(); + lp_load_global(argv[1]); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source3/modules/util_reparse.c b/source3/modules/util_reparse.c new file mode 100644 index 0000000..45cacbd --- /dev/null +++ b/source3/modules/util_reparse.c @@ -0,0 +1,84 @@ +/* + * Unix SMB/CIFS implementation. + * Utility functions for reparse points. + * + * Copyright (C) Jeremy Allison 2018 + * + * 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 "util_reparse.h" + +NTSTATUS fsctl_get_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **out_data, + uint32_t max_out_len, + uint32_t *out_len) +{ + DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp)); + return NT_STATUS_NOT_A_REPARSE_POINT; +} + +static NTSTATUS check_reparse_data_buffer( + const uint8_t *in_data, size_t in_len) +{ + uint16_t reparse_data_length; + + if (in_len == 0) { + DBG_DEBUG("in_len=0\n"); + return NT_STATUS_INVALID_BUFFER_SIZE; + } + if (in_len < 8) { + DBG_DEBUG("in_len=%zu\n", in_len); + return NT_STATUS_IO_REPARSE_DATA_INVALID; + } + + reparse_data_length = PULL_LE_U16(in_data, 4); + + if (reparse_data_length != (in_len - 8)) { + DBG_DEBUG("in_len=%zu, reparse_data_length=%"PRIu16"\n", + in_len, + reparse_data_length); + return NT_STATUS_IO_REPARSE_DATA_INVALID; + } + + return NT_STATUS_OK; +} + +NTSTATUS fsctl_set_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + const uint8_t *in_data, + uint32_t in_len) +{ + NTSTATUS status; + + DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp)); + + status = check_reparse_data_buffer(in_data, in_len); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_NOT_A_REPARSE_POINT; +} + +NTSTATUS fsctl_del_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + const uint8_t *in_data, + uint32_t in_len) +{ + DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp)); + return NT_STATUS_NOT_A_REPARSE_POINT; +} diff --git a/source3/modules/util_reparse.h b/source3/modules/util_reparse.h new file mode 100644 index 0000000..ffdb7b2 --- /dev/null +++ b/source3/modules/util_reparse.h @@ -0,0 +1,40 @@ +/* + * Unix SMB/CIFS implementation. + * Utility functions for reparse points. + * + * Copyright (C) Jeremy Allison 2018 + * + * 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 . + */ + +#ifndef __UTIL_REPARSE_H__ +#define __UTIL_REPARSE_H__ + +NTSTATUS fsctl_get_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **out_data, + uint32_t max_out_len, + uint32_t *out_len); + +NTSTATUS fsctl_set_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + const uint8_t *in_data, + uint32_t in_len); + +NTSTATUS fsctl_del_reparse_point(struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + const uint8_t *in_data, + uint32_t in_len); + +#endif /* __UTIL_REPARSE_H__ */ diff --git a/source3/modules/vfs_acl_common.c b/source3/modules/vfs_acl_common.c new file mode 100644 index 0000000..e04b672 --- /dev/null +++ b/source3/modules/vfs_acl_common.c @@ -0,0 +1,1181 @@ +/* + * Store Windows ACLs in data store - common functions. + * #included into modules/vfs_acl_xattr.c and modules/vfs_acl_tdb.c + * + * Copyright (C) Volker Lendecke, 2008 + * Copyright (C) Jeremy Allison, 2009 + * Copyright (C) Ralph Böhme, 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 . + */ + +#include "includes.h" +#include "vfs_acl_common.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/ndr_xattr.h" +#include "../libcli/security/security.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "../lib/util/bitmap.h" +#include "passdb/lookup_sid.h" + +#include +#include + +static NTSTATUS create_acl_blob(const struct security_descriptor *psd, + DATA_BLOB *pblob, + uint16_t hash_type, + uint8_t hash[XATTR_SD_HASH_SIZE]); + +#define HASH_SECURITY_INFO (SECINFO_OWNER | \ + SECINFO_GROUP | \ + SECINFO_DACL | \ + SECINFO_SACL) + +bool init_acl_common_config(vfs_handle_struct *handle, + const char *module_name) +{ + struct acl_common_config *config = NULL; + const struct enum_list *default_acl_style_list = NULL; + + default_acl_style_list = get_default_acl_style_list(); + + config = talloc_zero(handle->conn, struct acl_common_config); + if (config == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return false; + } + + config->ignore_system_acls = lp_parm_bool(SNUM(handle->conn), + module_name, + "ignore system acls", + false); + config->default_acl_style = lp_parm_enum(SNUM(handle->conn), + module_name, + "default acl style", + default_acl_style_list, + DEFAULT_ACL_POSIX); + + SMB_VFS_HANDLE_SET_DATA(handle, config, NULL, + struct acl_common_config, + return false); + + return true; +} + + +/******************************************************************* + Hash a security descriptor. +*******************************************************************/ + +static NTSTATUS hash_blob_sha256(DATA_BLOB blob, + uint8_t *hash) +{ + int rc; + + ZERO_ARRAY_LEN(hash, XATTR_SD_HASH_SIZE); + + rc = gnutls_hash_fast(GNUTLS_DIG_SHA256, + blob.data, + blob.length, + hash); + if (rc < 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Hash a security descriptor. +*******************************************************************/ + +static NTSTATUS hash_sd_sha256(struct security_descriptor *psd, + uint8_t *hash) +{ + DATA_BLOB blob; + NTSTATUS status; + + memset(hash, '\0', XATTR_SD_HASH_SIZE); + status = create_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return hash_blob_sha256(blob, hash); +} + +/******************************************************************* + Parse out a struct security_descriptor from a DATA_BLOB. +*******************************************************************/ + +static NTSTATUS parse_acl_blob(const DATA_BLOB *pblob, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc, + uint16_t *p_hash_type, + uint16_t *p_version, + uint8_t hash[XATTR_SD_HASH_SIZE], + uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE]) +{ + struct xattr_NTACL xacl; + enum ndr_err_code ndr_err; + size_t sd_size; + TALLOC_CTX *frame = talloc_stackframe(); + + ndr_err = ndr_pull_struct_blob(pblob, frame, &xacl, + (ndr_pull_flags_fn_t)ndr_pull_xattr_NTACL); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_INFO("ndr_pull_xattr_NTACL failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(frame); + return ndr_map_error2ntstatus(ndr_err); + } + + *p_version = xacl.version; + + switch (xacl.version) { + case 1: + *ppdesc = make_sec_desc(mem_ctx, SD_REVISION, + xacl.info.sd->type | SEC_DESC_SELF_RELATIVE, + xacl.info.sd->owner_sid, + xacl.info.sd->group_sid, + xacl.info.sd->sacl, + xacl.info.sd->dacl, + &sd_size); + /* No hash - null out. */ + *p_hash_type = XATTR_SD_HASH_TYPE_NONE; + memset(hash, '\0', XATTR_SD_HASH_SIZE); + break; + case 2: + *ppdesc = make_sec_desc(mem_ctx, SD_REVISION, + xacl.info.sd_hs2->sd->type | SEC_DESC_SELF_RELATIVE, + xacl.info.sd_hs2->sd->owner_sid, + xacl.info.sd_hs2->sd->group_sid, + xacl.info.sd_hs2->sd->sacl, + xacl.info.sd_hs2->sd->dacl, + &sd_size); + /* No hash - null out. */ + *p_hash_type = XATTR_SD_HASH_TYPE_NONE; + memset(hash, '\0', XATTR_SD_HASH_SIZE); + break; + case 3: + *ppdesc = make_sec_desc(mem_ctx, SD_REVISION, + xacl.info.sd_hs3->sd->type | SEC_DESC_SELF_RELATIVE, + xacl.info.sd_hs3->sd->owner_sid, + xacl.info.sd_hs3->sd->group_sid, + xacl.info.sd_hs3->sd->sacl, + xacl.info.sd_hs3->sd->dacl, + &sd_size); + *p_hash_type = xacl.info.sd_hs3->hash_type; + /* Current version 3 (if no sys acl hash available). */ + memcpy(hash, xacl.info.sd_hs3->hash, XATTR_SD_HASH_SIZE); + break; + case 4: + *ppdesc = make_sec_desc(mem_ctx, SD_REVISION, + xacl.info.sd_hs4->sd->type | SEC_DESC_SELF_RELATIVE, + xacl.info.sd_hs4->sd->owner_sid, + xacl.info.sd_hs4->sd->group_sid, + xacl.info.sd_hs4->sd->sacl, + xacl.info.sd_hs4->sd->dacl, + &sd_size); + *p_hash_type = xacl.info.sd_hs4->hash_type; + /* Current version 4. */ + memcpy(hash, xacl.info.sd_hs4->hash, XATTR_SD_HASH_SIZE); + memcpy(sys_acl_hash, xacl.info.sd_hs4->sys_acl_hash, XATTR_SD_HASH_SIZE); + break; + default: + TALLOC_FREE(frame); + return NT_STATUS_REVISION_MISMATCH; + } + + TALLOC_FREE(frame); + + return (*ppdesc != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY; +} + +/******************************************************************* + Create a DATA_BLOB from a hash of the security descriptor storead at + the system layer and the NT ACL we wish to preserve +*******************************************************************/ + +static NTSTATUS create_acl_blob(const struct security_descriptor *psd, + DATA_BLOB *pblob, + uint16_t hash_type, + uint8_t hash[XATTR_SD_HASH_SIZE]) +{ + struct xattr_NTACL xacl; + struct security_descriptor_hash_v3 sd_hs3; + enum ndr_err_code ndr_err; + TALLOC_CTX *ctx = talloc_tos(); + + ZERO_STRUCT(xacl); + ZERO_STRUCT(sd_hs3); + + xacl.version = 3; + xacl.info.sd_hs3 = &sd_hs3; + xacl.info.sd_hs3->sd = discard_const_p(struct security_descriptor, psd); + xacl.info.sd_hs3->hash_type = hash_type; + memcpy(&xacl.info.sd_hs3->hash[0], hash, XATTR_SD_HASH_SIZE); + + ndr_err = ndr_push_struct_blob( + pblob, ctx, &xacl, + (ndr_push_flags_fn_t)ndr_push_xattr_NTACL); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_INFO("ndr_push_xattr_NTACL failed: %s\n", + ndr_errstr(ndr_err)); + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Create a DATA_BLOB from a hash of the security descriptors + (system and NT) stored at the system layer and the NT ACL we wish + to preserve. +*******************************************************************/ + +static NTSTATUS create_sys_acl_blob(const struct security_descriptor *psd, + DATA_BLOB *pblob, + uint16_t hash_type, + uint8_t hash[XATTR_SD_HASH_SIZE], + const char *description, + uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE]) +{ + struct xattr_NTACL xacl; + struct security_descriptor_hash_v4 sd_hs4; + enum ndr_err_code ndr_err; + TALLOC_CTX *ctx = talloc_tos(); + + ZERO_STRUCT(xacl); + ZERO_STRUCT(sd_hs4); + + xacl.version = 4; + xacl.info.sd_hs4 = &sd_hs4; + xacl.info.sd_hs4->sd = discard_const_p(struct security_descriptor, psd); + xacl.info.sd_hs4->hash_type = hash_type; + memcpy(&xacl.info.sd_hs4->hash[0], hash, XATTR_SD_HASH_SIZE); + xacl.info.sd_hs4->description = description; + memcpy(&xacl.info.sd_hs4->sys_acl_hash[0], sys_acl_hash, XATTR_SD_HASH_SIZE); + + ndr_err = ndr_push_struct_blob( + pblob, ctx, &xacl, + (ndr_push_flags_fn_t)ndr_push_xattr_NTACL); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_INFO("ndr_push_xattr_NTACL failed: %s\n", + ndr_errstr(ndr_err)); + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Add in 3 inheritable components for a non-inheritable directory ACL. + CREATOR_OWNER/CREATOR_GROUP/WORLD. +*******************************************************************/ + +static NTSTATUS add_directory_inheritable_components(vfs_handle_struct *handle, + const char *name, + SMB_STRUCT_STAT *psbuf, + struct security_descriptor *psd) +{ + struct connection_struct *conn = handle->conn; + int num_aces = (psd->dacl ? psd->dacl->num_aces : 0); + struct smb_filename smb_fname; + enum security_ace_type acltype; + uint32_t access_mask; + mode_t dir_mode; + mode_t file_mode; + mode_t mode; + struct security_ace *new_ace_list; + + if (psd->dacl) { + new_ace_list = talloc_zero_array(psd->dacl, + struct security_ace, + num_aces + 3); + } else { + /* + * make_sec_acl() at the bottom of this function + * duplicates new_ace_list + */ + new_ace_list = talloc_zero_array(talloc_tos(), + struct security_ace, + num_aces + 3); + } + + if (new_ace_list == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Fake a quick smb_filename. */ + ZERO_STRUCT(smb_fname); + smb_fname.st = *psbuf; + smb_fname.base_name = discard_const_p(char, name); + + dir_mode = unix_mode(conn, + FILE_ATTRIBUTE_DIRECTORY, &smb_fname, NULL); + file_mode = unix_mode(conn, + FILE_ATTRIBUTE_ARCHIVE, &smb_fname, NULL); + + mode = dir_mode | file_mode; + + DBG_DEBUG("directory %s, mode = 0%o\n", name, (unsigned int)mode); + + if (num_aces) { + memcpy(new_ace_list, psd->dacl->aces, + num_aces * sizeof(struct security_ace)); + } + access_mask = map_canon_ace_perms(SNUM(conn), &acltype, + mode & 0700, false); + + init_sec_ace(&new_ace_list[num_aces], + &global_sid_Creator_Owner, + acltype, + access_mask, + SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY); + access_mask = map_canon_ace_perms(SNUM(conn), &acltype, + (mode << 3) & 0700, false); + init_sec_ace(&new_ace_list[num_aces+1], + &global_sid_Creator_Group, + acltype, + access_mask, + SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY); + access_mask = map_canon_ace_perms(SNUM(conn), &acltype, + (mode << 6) & 0700, false); + init_sec_ace(&new_ace_list[num_aces+2], + &global_sid_World, + acltype, + access_mask, + SEC_ACE_FLAG_CONTAINER_INHERIT| + SEC_ACE_FLAG_OBJECT_INHERIT| + SEC_ACE_FLAG_INHERIT_ONLY); + if (psd->dacl) { + psd->dacl->aces = new_ace_list; + psd->dacl->num_aces += 3; + psd->dacl->size += new_ace_list[num_aces].size + + new_ace_list[num_aces+1].size + + new_ace_list[num_aces+2].size; + } else { + psd->dacl = make_sec_acl(psd, + NT4_ACL_REVISION, + 3, + new_ace_list); + if (psd->dacl == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + return NT_STATUS_OK; +} + +/** + * Validate an ACL blob + * + * This validates an ACL blob against the underlying filesystem ACL. If this + * function returns NT_STATUS_OK ppsd can be + * + * 1. the ACL from the blob (psd_from_fs=false), or + * 2. the ACL from the fs (psd_from_fs=true), or + * 3. NULL (!) + * + * If the return value is anything else then NT_STATUS_OK, ppsd is set to NULL + * and psd_from_fs set to false. + * + * Returning the underlying filesystem ACL in case no. 2 is really just an + * optimisation, because some validations have to fetch the filesystem ACL as + * part of the validation, so we already have it available and callers might + * need it as well. + **/ +static NTSTATUS validate_nt_acl_blob(TALLOC_CTX *mem_ctx, + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + const DATA_BLOB *blob, + struct security_descriptor **ppsd, + bool *psd_is_from_fs) +{ + NTSTATUS status; + uint16_t hash_type = XATTR_SD_HASH_TYPE_NONE; + uint16_t xattr_version = 0; + uint8_t hash[XATTR_SD_HASH_SIZE]; + uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE]; + uint8_t hash_tmp[XATTR_SD_HASH_SIZE]; + uint8_t sys_acl_hash_tmp[XATTR_SD_HASH_SIZE]; + struct security_descriptor *psd = NULL; + struct security_descriptor *psd_blob = NULL; + struct security_descriptor *psd_fs = NULL; + char *sys_acl_blob_description = NULL; + DATA_BLOB sys_acl_blob = { 0 }; + struct acl_common_config *config = NULL; + + *ppsd = NULL; + *psd_is_from_fs = false; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return NT_STATUS_UNSUCCESSFUL); + + status = parse_acl_blob(blob, + mem_ctx, + &psd_blob, + &hash_type, + &xattr_version, + &hash[0], + &sys_acl_hash[0]); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("parse_acl_blob returned %s\n", nt_errstr(status)); + goto fail; + } + + /* determine which type of xattr we got */ + switch (xattr_version) { + case 1: + case 2: + /* These xattr types are unilateral, they do not + * require confirmation of the hash. In particular, + * the NTVFS file server uses version 1, but + * 'samba-tool ntacl' can set these as well */ + *ppsd = psd_blob; + return NT_STATUS_OK; + case 3: + case 4: + if (config->ignore_system_acls) { + *ppsd = psd_blob; + return NT_STATUS_OK; + } + + break; + default: + DBG_DEBUG("ACL blob revision mismatch (%u) for file %s\n", + (unsigned int)hash_type, smb_fname->base_name); + TALLOC_FREE(psd_blob); + return NT_STATUS_OK; + } + + /* determine which type of xattr we got */ + if (hash_type != XATTR_SD_HASH_TYPE_SHA256) { + DBG_DEBUG("ACL blob hash type (%u) unexpected for file %s\n", + (unsigned int)hash_type, smb_fname->base_name); + TALLOC_FREE(psd_blob); + return NT_STATUS_OK; + } + + /* determine which type of xattr we got */ + switch (xattr_version) { + case 4: + { + int ret; + /* Get the full underlying sd, then hash. */ + ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, + fsp, + mem_ctx, + &sys_acl_blob_description, + &sys_acl_blob); + /* If we fail to get the ACL blob (for some reason) then this + * is not fatal, we just work based on the NT ACL only */ + if (ret == 0) { + status = hash_blob_sha256(sys_acl_blob, sys_acl_hash_tmp); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + TALLOC_FREE(sys_acl_blob_description); + TALLOC_FREE(sys_acl_blob.data); + + if (memcmp(&sys_acl_hash[0], &sys_acl_hash_tmp[0], + XATTR_SD_HASH_SIZE) == 0) { + /* Hash matches, return blob sd. */ + DBG_DEBUG("blob hash matches for file %s\n", + smb_fname->base_name); + *ppsd = psd_blob; + return NT_STATUS_OK; + } + } + + /* Otherwise, fall though and see if the NT ACL hash matches */ + FALL_THROUGH; + } + case 3: + /* Get the full underlying sd for the hash + or to return as backup. */ + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, + fsp, + HASH_SECURITY_INFO, + mem_ctx, + &psd_fs); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("get_next_acl for file %s returned %s\n", + smb_fname->base_name, nt_errstr(status)); + goto fail; + } + + status = hash_sd_sha256(psd_fs, hash_tmp); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(psd_blob); + *ppsd = psd_fs; + *psd_is_from_fs = true; + return NT_STATUS_OK; + } + + if (memcmp(&hash[0], &hash_tmp[0], XATTR_SD_HASH_SIZE) == 0) { + /* Hash matches, return blob sd. */ + DBG_DEBUG("blob hash matches for file %s\n", + smb_fname->base_name); + *ppsd = psd_blob; + return NT_STATUS_OK; + } + + /* Hash doesn't match, return underlying sd. */ + DBG_DEBUG("blob hash does not match for file %s - returning " + "file system SD mapping.\n", + smb_fname->base_name); + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("acl for blob hash for %s is:\n", + smb_fname->base_name); + NDR_PRINT_DEBUG(security_descriptor, psd_fs); + } + + TALLOC_FREE(psd_blob); + *ppsd = psd_fs; + *psd_is_from_fs = true; + } + + return NT_STATUS_OK; + +fail: + TALLOC_FREE(psd); + TALLOC_FREE(psd_blob); + TALLOC_FREE(psd_fs); + TALLOC_FREE(sys_acl_blob_description); + TALLOC_FREE(sys_acl_blob.data); + return status; +} + +/******************************************************************* + Pull a DATA_BLOB from an xattr given an fsp. + If the hash doesn't match, or doesn't exist - return the underlying + filesystem sd. +*******************************************************************/ + +NTSTATUS fget_nt_acl_common( + NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + DATA_BLOB blob = data_blob_null; + NTSTATUS status; + struct security_descriptor *psd = NULL; + const struct smb_filename *smb_fname = fsp->fsp_name; + bool psd_is_from_fs = false; + struct acl_common_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return NT_STATUS_UNSUCCESSFUL); + + DBG_DEBUG("name=%s\n", smb_fname->base_name); + + status = fget_acl_blob_fn(mem_ctx, handle, fsp, &blob); + if (NT_STATUS_IS_OK(status)) { + status = validate_nt_acl_blob(mem_ctx, + handle, + fsp, + smb_fname, + &blob, + &psd, + &psd_is_from_fs); + TALLOC_FREE(blob.data); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("ACL validation for [%s] failed\n", + smb_fname->base_name); + goto fail; + } + } + + if (psd == NULL) { + /* Get the full underlying sd, as we failed to get the + * blob for the hash, or the revision/hash type wasn't + * known */ + + if (config->ignore_system_acls) { + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + status = make_default_filesystem_acl( + mem_ctx, + config->default_acl_style, + smb_fname->base_name, + &fsp->fsp_name->st, + &psd); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } else { + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, + fsp, + security_info, + mem_ctx, + &psd); + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("get_next_acl for file %s " + "returned %s\n", + smb_fname->base_name, + nt_errstr(status)); + goto fail; + } + + psd_is_from_fs = true; + } + } + + if (psd_is_from_fs) { + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + /* + * We're returning the underlying ACL from the + * filesystem. If it's a directory, and has no + * inheritable ACE entries we have to fake them. + */ + + if (fsp->fsp_flags.is_directory && + !sd_has_inheritable_components(psd, true)) { + status = add_directory_inheritable_components( + handle, + smb_fname->base_name, + &fsp->fsp_name->st, + psd); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + /* + * The underlying POSIX module always sets the + * ~SEC_DESC_DACL_PROTECTED bit, as ACLs can't be inherited in + * this way under POSIX. Remove it for Windows-style ACLs. + */ + psd->type &= ~SEC_DESC_DACL_PROTECTED; + } + + if (!(security_info & SECINFO_OWNER)) { + psd->owner_sid = NULL; + } + if (!(security_info & SECINFO_GROUP)) { + psd->group_sid = NULL; + } + if (!(security_info & SECINFO_DACL)) { + psd->type &= ~SEC_DESC_DACL_PRESENT; + psd->dacl = NULL; + } + if (!(security_info & SECINFO_SACL)) { + psd->type &= ~SEC_DESC_SACL_PRESENT; + psd->sacl = NULL; + } + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("returning acl for %s is:\n", + smb_fname->base_name); + NDR_PRINT_DEBUG(security_descriptor, psd); + } + + *ppdesc = psd; + + return NT_STATUS_OK; + +fail: + TALLOC_FREE(psd); + return status; +} + +/********************************************************************* + Set the underlying ACL (e.g. POSIX ACLS, POSIX owner, etc) +*********************************************************************/ +static NTSTATUS set_underlying_acl(vfs_handle_struct *handle, files_struct *fsp, + struct security_descriptor *psd, + uint32_t security_info_sent, + bool chown_needed) +{ + NTSTATUS status; + const struct security_token *token = NULL; + struct dom_sid_buf buf; + + status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + return status; + } + + /* We got access denied here. If we're already root, + or we didn't need to do a chown, or the fsp isn't + open with WRITE_OWNER access, just return. */ + if (get_current_uid(handle->conn) == 0 || !chown_needed) { + return NT_STATUS_ACCESS_DENIED; + } + status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Only allow take-ownership, not give-ownership. That's the way Windows + * implements SEC_STD_WRITE_OWNER. MS-FSA 2.1.5.16 just states: If + * InputBuffer.OwnerSid is not a valid owner SID for a file in the + * objectstore, as determined in an implementation specific manner, the + * object store MUST return STATUS_INVALID_OWNER. + */ + token = get_current_nttok(fsp->conn); + if (!security_token_is_sid(token, psd->owner_sid)) { + return NT_STATUS_INVALID_OWNER; + } + + DBG_DEBUG("overriding chown on file %s for sid %s\n", + fsp_str_dbg(fsp), + dom_sid_str_buf(psd->owner_sid, &buf)); + + /* Ok, we failed to chown and we have + SEC_STD_WRITE_OWNER access - override. */ + become_root(); + status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + unbecome_root(); + + return status; +} + +/********************************************************************* + Store a v3 security descriptor +*********************************************************************/ +static NTSTATUS store_v3_blob( + NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + vfs_handle_struct *handle, files_struct *fsp, + struct security_descriptor *psd, + struct security_descriptor *pdesc_next, + uint8_t hash[XATTR_SD_HASH_SIZE]) +{ + NTSTATUS status; + DATA_BLOB blob; + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("storing xattr sd for file %s\n", + fsp_str_dbg(fsp)); + NDR_PRINT_DEBUG( + security_descriptor, + discard_const_p(struct security_descriptor, psd)); + + if (pdesc_next != NULL) { + DBG_DEBUG("storing xattr sd based on \n"); + NDR_PRINT_DEBUG( + security_descriptor, + discard_const_p(struct security_descriptor, + pdesc_next)); + } else { + DBG_DEBUG("ignoring underlying sd\n"); + } + } + status = create_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("create_acl_blob failed\n"); + return status; + } + + status = store_acl_blob_fsp_fn(handle, fsp, &blob); + return status; +} + +/********************************************************************* + Store a security descriptor given an fsp. +*********************************************************************/ + +NTSTATUS fset_nt_acl_common( + NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + const char *module_name, + vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *orig_psd) +{ + NTSTATUS status; + int ret; + DATA_BLOB blob, sys_acl_blob; + struct security_descriptor *pdesc_next = NULL; + struct security_descriptor *psd = NULL; + uint8_t hash[XATTR_SD_HASH_SIZE]; + uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE]; + bool chown_needed = false; + char *sys_acl_description; + TALLOC_CTX *frame = talloc_stackframe(); + bool ignore_file_system_acl = lp_parm_bool( + SNUM(handle->conn), module_name, "ignore system acls", false); + struct acl_common_fsp_ext *ext = NULL; + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("incoming sd for file %s\n", fsp_str_dbg(fsp)); + NDR_PRINT_DEBUG(security_descriptor, + discard_const_p(struct security_descriptor, orig_psd)); + } + + status = fget_nt_acl_common(fget_acl_blob_fn, handle, fsp, + SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL|SECINFO_SACL, + frame, + &psd); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + psd->revision = orig_psd->revision; + if (security_info_sent & SECINFO_DACL) { + psd->type = orig_psd->type; + /* All our SD's are self relative. */ + psd->type |= SEC_DESC_SELF_RELATIVE; + } + + if ((security_info_sent & SECINFO_OWNER) && (orig_psd->owner_sid != NULL)) { + if (!dom_sid_equal(orig_psd->owner_sid, psd->owner_sid)) { + /* We're changing the owner. */ + chown_needed = true; + } + psd->owner_sid = orig_psd->owner_sid; + } + if ((security_info_sent & SECINFO_GROUP) && (orig_psd->group_sid != NULL)) { + if (!dom_sid_equal(orig_psd->group_sid, psd->group_sid)) { + /* We're changing the group. */ + chown_needed = true; + } + psd->group_sid = orig_psd->group_sid; + } + if (security_info_sent & SECINFO_DACL) { + if (security_descriptor_with_ms_nfs(orig_psd)) { + /* + * If the sd contains a MS NFS SID, do + * nothing, it's a chmod() request from OS X + * with AAPL context. + */ + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + psd->dacl = orig_psd->dacl; + psd->type |= SEC_DESC_DACL_PRESENT; + } + if (security_info_sent & SECINFO_SACL) { + psd->sacl = orig_psd->sacl; + psd->type |= SEC_DESC_SACL_PRESENT; + } + + ext = VFS_ADD_FSP_EXTENSION(handle, + fsp, + struct acl_common_fsp_ext, + NULL); + ext->setting_nt_acl = true; + + if (ignore_file_system_acl) { + if (chown_needed) { + /* send only ownership stuff to lower layer */ + security_info_sent &= (SECINFO_OWNER | SECINFO_GROUP); + status = set_underlying_acl(handle, fsp, psd, + security_info_sent, true); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + ZERO_ARRAY(hash); + status = store_v3_blob(store_acl_blob_fsp_fn, handle, fsp, psd, + NULL, hash); + goto done; + } + + status = set_underlying_acl(handle, fsp, psd, security_info_sent, + chown_needed); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Get the full underlying sd, then hash. */ + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, + fsp, + HASH_SECURITY_INFO, + frame, + &pdesc_next); + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = hash_sd_sha256(pdesc_next, hash); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Get the full underlying sd, then hash. */ + ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, + fsp, + frame, + &sys_acl_description, + &sys_acl_blob); + + /* If we fail to get the ACL blob (for some reason) then this + * is not fatal, we just work based on the NT ACL only */ + if (ret != 0) { + status = store_v3_blob(store_acl_blob_fsp_fn, handle, fsp, psd, + pdesc_next, hash); + + goto done; + } + + status = hash_blob_sha256(sys_acl_blob, sys_acl_hash); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (DEBUGLEVEL >= 10) { + DBG_DEBUG("storing xattr sd for file %s based on system ACL\n", + fsp_str_dbg(fsp)); + NDR_PRINT_DEBUG(security_descriptor, + discard_const_p(struct security_descriptor, psd)); + + DBG_DEBUG("storing hash in xattr sd based on system ACL and:\n"); + NDR_PRINT_DEBUG(security_descriptor, + discard_const_p(struct security_descriptor, pdesc_next)); + } + + /* We store hashes of both the sys ACL blob and the NT + * security descriptor mapped from that ACL so as to improve + * our chances against some inadvertent change breaking the + * hash used */ + status = create_sys_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash, + sys_acl_description, sys_acl_hash); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("create_sys_acl_blob failed\n"); + goto done; + } + + status = store_acl_blob_fsp_fn(handle, fsp, &blob); + +done: + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + TALLOC_FREE(frame); + return status; +} + +static int acl_common_remove_object(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool is_directory) +{ + connection_struct *conn = handle->conn; + struct file_id id; + files_struct *fsp = NULL; + int ret = 0; + struct smb_filename *full_fname = NULL; + struct smb_filename *local_fname = NULL; + struct smb_filename *parent_dir_fname = NULL; + int saved_errno = 0; + struct smb_filename *saved_dir_fname = NULL; + NTSTATUS status; + + saved_dir_fname = vfs_GetWd(talloc_tos(),conn); + if (saved_dir_fname == NULL) { + saved_errno = errno; + goto out; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + goto out; + } + + status = SMB_VFS_PARENT_PATHNAME(conn, + talloc_tos(), + full_fname, + &parent_dir_fname, + &local_fname); + if (!NT_STATUS_IS_OK(status)) { + saved_errno = map_errno_from_nt_status(status); + goto out; + } + + DBG_DEBUG("removing %s %s\n", is_directory ? "directory" : "file", + smb_fname_str_dbg(full_fname)); + + /* cd into the parent dir to pin it. */ + ret = vfs_ChDir(conn, parent_dir_fname); + if (ret == -1) { + saved_errno = errno; + goto out; + } + + /* Must use lstat here. */ + ret = SMB_VFS_LSTAT(conn, local_fname); + if (ret == -1) { + saved_errno = errno; + goto out; + } + + /* Ensure we have this file open with DELETE access. */ + id = vfs_file_id_from_sbuf(conn, &local_fname->st); + for (fsp = file_find_di_first(conn->sconn, id, true); fsp; + fsp = file_find_di_next(fsp, true)) { + if (fsp->access_mask & DELETE_ACCESS && + fsp->fsp_flags.delete_on_close) + { + /* We did open this for delete, + * allow the delete as root. + */ + break; + } + } + + if (!fsp) { + DBG_DEBUG("%s %s not an open file\n", + is_directory ? "directory" : "file", + smb_fname_str_dbg(full_fname)); + saved_errno = EACCES; + goto out; + } + + become_root(); + if (is_directory) { + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + } else { + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + } + unbecome_root(); + + if (ret == -1) { + saved_errno = errno; + } + + out: + + TALLOC_FREE(parent_dir_fname); + TALLOC_FREE(full_fname); + + if (saved_dir_fname) { + vfs_ChDir(conn, saved_dir_fname); + TALLOC_FREE(saved_dir_fname); + } + if (saved_errno) { + errno = saved_errno; + } + return ret; +} + +int rmdir_acl_common(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + int ret; + + /* Try the normal rmdir first. */ + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + if (ret == 0) { + return 0; + } + if (errno == EACCES || errno == EPERM) { + /* Failed due to access denied, + see if we need to root override. */ + return acl_common_remove_object(handle, + dirfsp, + smb_fname, + true); + } + + DBG_DEBUG("unlink of %s failed %s\n", + smb_fname->base_name, + strerror(errno)); + return -1; +} + +int unlink_acl_common(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + + /* Try the normal unlink first. */ + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + if (ret == 0) { + return 0; + } + if (errno == EACCES || errno == EPERM) { + /* Failed due to access denied, + see if we need to root override. */ + + /* Don't do anything fancy for streams. */ + if (smb_fname->stream_name) { + return -1; + } + return acl_common_remove_object(handle, + dirfsp, + smb_fname, + false); + } + + DBG_DEBUG("unlink of %s failed %s\n", + smb_fname->base_name, + strerror(errno)); + return -1; +} + +int fchmod_acl_module_common(struct vfs_handle_struct *handle, + struct files_struct *fsp, mode_t mode) +{ + if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES + || fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH) { + /* Only allow this on POSIX opens. */ + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + } + return 0; +} diff --git a/source3/modules/vfs_acl_common.h b/source3/modules/vfs_acl_common.h new file mode 100644 index 0000000..8d3475f --- /dev/null +++ b/source3/modules/vfs_acl_common.h @@ -0,0 +1,91 @@ +/* + * Store Windows ACLs in data store - common functions. + * + * Copyright (C) Volker Lendecke, 2008 + * Copyright (C) Jeremy Allison, 2009 + * Copyright (C) Ralph Böhme, 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 . + */ + +#ifndef __VFS_ACL_COMMON_H__ +#define __VFS_ACL_COMMON_H__ + +#include "smbd/proto.h" + +struct acl_common_config { + bool ignore_system_acls; + enum default_acl_style default_acl_style; + char *security_acl_xattr_name; +}; + +struct acl_common_fsp_ext { + bool setting_nt_acl; +}; + +bool init_acl_common_config(vfs_handle_struct *handle, + const char *module_name); + +int rmdir_acl_common(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname); +int unlink_acl_common(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags); +int fchmod_acl_module_common(struct vfs_handle_struct *handle, + struct files_struct *fsp, mode_t mode); +int chmod_acl_acl_module_common(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + mode_t mode); +NTSTATUS get_nt_acl_common_at( + NTSTATUS (*get_acl_blob_at_fn)(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + DATA_BLOB *pblob), + vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc); + +NTSTATUS fget_nt_acl_common( + NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc); + +NTSTATUS fset_nt_acl_common( + NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob), + const char *module_name, + vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *orig_psd); + + + +#endif diff --git a/source3/modules/vfs_acl_tdb.c b/source3/modules/vfs_acl_tdb.c new file mode 100644 index 0000000..bccb1ab --- /dev/null +++ b/source3/modules/vfs_acl_tdb.c @@ -0,0 +1,387 @@ +/* + * Store Windows ACLs in a tdb. + * + * Copyright (C) Volker Lendecke, 2008 + * Copyright (C) Jeremy Allison, 2008 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/xattr.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "auth.h" +#include "util_tdb.h" +#include "vfs_acl_common.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define ACL_MODULE_NAME "acl_tdb" + +static unsigned int ref_count; +static struct db_context *acl_db; + +/******************************************************************* + Open acl_db if not already open, increment ref count. +*******************************************************************/ + +static bool acl_tdb_init(void) +{ + char *dbname; + + if (acl_db) { + ref_count++; + return true; + } + + dbname = state_path(talloc_tos(), "file_ntacls.tdb"); + + if (dbname == NULL) { + errno = ENOSYS; + return false; + } + + become_root(); + acl_db = db_open(NULL, dbname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600, + DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + unbecome_root(); + + if (acl_db == NULL) { +#if defined(ENOTSUP) + errno = ENOTSUP; +#else + errno = ENOSYS; +#endif + TALLOC_FREE(dbname); + return false; + } + + ref_count++; + TALLOC_FREE(dbname); + return true; +} + +/******************************************************************* + Lower ref count and close acl_db if zero. +*******************************************************************/ + +static void disconnect_acl_tdb(struct vfs_handle_struct *handle) +{ + SMB_VFS_NEXT_DISCONNECT(handle); + ref_count--; + if (ref_count == 0) { + TALLOC_FREE(acl_db); + } +} + +/******************************************************************* + Delete the tdb acl record for a file +*******************************************************************/ + +static NTSTATUS acl_tdb_delete(vfs_handle_struct *handle, + struct db_context *db, + SMB_STRUCT_STAT *psbuf) +{ + NTSTATUS status; + struct file_id id = vfs_file_id_from_sbuf(handle->conn, psbuf); + uint8_t id_buf[16]; + + /* For backwards compatibility only store the dev/inode. */ + push_file_id_16((char *)id_buf, &id); + + status = dbwrap_delete(db, make_tdb_data(id_buf, sizeof(id_buf))); + return status; +} + +/******************************************************************* + Pull a security descriptor from an fsp into a DATA_BLOB from a tdb store. +*******************************************************************/ + +static NTSTATUS fget_acl_blob(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob) +{ + uint8_t id_buf[16]; + TDB_DATA data; + struct file_id id; + struct db_context *db = acl_db; + NTSTATUS status = NT_STATUS_OK; + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + id = vfs_file_id_from_sbuf(handle->conn, &fsp->fsp_name->st); + + /* For backwards compatibility only store the dev/inode. */ + push_file_id_16((char *)id_buf, &id); + + status = dbwrap_fetch(db, + ctx, + make_tdb_data(id_buf, sizeof(id_buf)), + &data); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + pblob->data = data.dptr; + pblob->length = data.dsize; + + DBG_DEBUG("returned %u bytes from file %s\n", + (unsigned int)data.dsize, + fsp_str_dbg(fsp)); + + if (pblob->length == 0 || pblob->data == NULL) { + return NT_STATUS_NOT_FOUND; + } + return NT_STATUS_OK; +} + +/******************************************************************* + Store a DATA_BLOB into a tdb record given an fsp pointer. +*******************************************************************/ + +static NTSTATUS store_acl_blob_fsp(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob) +{ + uint8_t id_buf[16]; + struct file_id id; + TDB_DATA data = { .dptr = pblob->data, .dsize = pblob->length }; + struct db_context *db = acl_db; + NTSTATUS status; + + DEBUG(10,("store_acl_blob_fsp: storing blob length %u on file %s\n", + (unsigned int)pblob->length, fsp_str_dbg(fsp))); + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + id = vfs_file_id_from_sbuf(handle->conn, &fsp->fsp_name->st); + + /* For backwards compatibility only store the dev/inode. */ + push_file_id_16((char *)id_buf, &id); + + status = dbwrap_store( + db, make_tdb_data(id_buf, sizeof(id_buf)), data, 0); + return status; +} + +/********************************************************************* + On unlinkat we need to delete the tdb record (if using tdb). +*********************************************************************/ + +static int unlinkat_acl_tdb(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *smb_fname_tmp = NULL; + struct db_context *db = acl_db; + int ret = -1; + + smb_fname_tmp = cp_smb_filename_nostream(talloc_tos(), smb_fname); + if (smb_fname_tmp == NULL) { + errno = ENOMEM; + goto out; + } + + ret = vfs_stat(handle->conn, smb_fname_tmp); + if (ret == -1) { + goto out; + } + + if (flags & AT_REMOVEDIR) { + ret = rmdir_acl_common(handle, + dirfsp, + smb_fname_tmp); + } else { + ret = unlink_acl_common(handle, + dirfsp, + smb_fname_tmp, + flags); + } + + if (ret == -1) { + goto out; + } + + acl_tdb_delete(handle, db, &smb_fname_tmp->st); + out: + return ret; +} + +/******************************************************************* + Handle opening the storage tdb if so configured. +*******************************************************************/ + +static int connect_acl_tdb(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + bool ok; + struct acl_common_config *config = NULL; + + if (ret < 0) { + return ret; + } + + if (!acl_tdb_init()) { + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + ok = init_acl_common_config(handle, ACL_MODULE_NAME); + if (!ok) { + DBG_ERR("init_acl_common_config failed\n"); + return -1; + } + + /* Ensure we have the parameters correct if we're + * using this module. */ + DEBUG(2,("connect_acl_tdb: setting 'inherit acls = true' " + "'dos filemode = true' and " + "'force unknown acl user = true' for service %s\n", + service )); + + lp_do_parameter(SNUM(handle->conn), "inherit acls", "true"); + lp_do_parameter(SNUM(handle->conn), "dos filemode", "true"); + lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true"); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + if (config->ignore_system_acls) { + mode_t create_mask = lp_create_mask(SNUM(handle->conn)); + char *create_mask_str = NULL; + + if ((create_mask & 0666) != 0666) { + create_mask |= 0666; + create_mask_str = talloc_asprintf(handle, "0%o", + create_mask); + if (create_mask_str == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return -1; + } + + DBG_NOTICE("setting 'create mask = %s'\n", create_mask_str); + + lp_do_parameter (SNUM(handle->conn), + "create mask", create_mask_str); + + TALLOC_FREE(create_mask_str); + } + + DBG_NOTICE("setting 'directory mask = 0777', " + "'store dos attributes = yes' and all " + "'map ...' options to 'no'\n"); + + lp_do_parameter(SNUM(handle->conn), "directory mask", "0777"); + lp_do_parameter(SNUM(handle->conn), "map archive", "no"); + lp_do_parameter(SNUM(handle->conn), "map hidden", "no"); + lp_do_parameter(SNUM(handle->conn), "map readonly", "no"); + lp_do_parameter(SNUM(handle->conn), "map system", "no"); + lp_do_parameter(SNUM(handle->conn), "store dos attributes", + "yes"); + } + + return 0; +} + +/********************************************************************* + Remove a Windows ACL - we're setting the underlying POSIX ACL. +*********************************************************************/ + +static int sys_acl_set_fd_tdb(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct acl_common_fsp_ext *ext = (struct acl_common_fsp_ext *) + VFS_FETCH_FSP_EXTENSION(handle, fsp); + struct db_context *db = acl_db; + NTSTATUS status; + int ret; + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, + fsp, + type, + theacl); + if (ret == -1) { + return -1; + } + + if (ext != NULL && ext->setting_nt_acl) { + return 0; + } + + acl_tdb_delete(handle, db, &fsp->fsp_name->st); + return 0; +} + +static NTSTATUS acl_tdb_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + status = fget_nt_acl_common(fget_acl_blob, handle, fsp, + security_info, mem_ctx, ppdesc); + return status; +} + +static NTSTATUS acl_tdb_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + NTSTATUS status; + status = fset_nt_acl_common(fget_acl_blob, store_acl_blob_fsp, + ACL_MODULE_NAME, + handle, fsp, security_info_sent, psd); + return status; +} + +static struct vfs_fn_pointers vfs_acl_tdb_fns = { + .connect_fn = connect_acl_tdb, + .disconnect_fn = disconnect_acl_tdb, + .unlinkat_fn = unlinkat_acl_tdb, + .fchmod_fn = fchmod_acl_module_common, + .fget_nt_acl_fn = acl_tdb_fget_nt_acl, + .fset_nt_acl_fn = acl_tdb_fset_nt_acl, + .sys_acl_set_fd_fn = sys_acl_set_fd_tdb +}; + +static_decl_vfs; +NTSTATUS vfs_acl_tdb_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "acl_tdb", + &vfs_acl_tdb_fns); +} diff --git a/source3/modules/vfs_acl_xattr.c b/source3/modules/vfs_acl_xattr.c new file mode 100644 index 0000000..1a3ab34 --- /dev/null +++ b/source3/modules/vfs_acl_xattr.c @@ -0,0 +1,552 @@ +/* + * Store Windows ACLs in xattrs. + * + * Copyright (C) Volker Lendecke, 2008 + * Copyright (C) Jeremy Allison, 2008 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/xattr.h" +#include "auth.h" +#include "vfs_acl_common.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/tevent_unix.h" + +/* Pull in the common functions. */ +#define ACL_MODULE_NAME "acl_xattr" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/******************************************************************* + Pull a security descriptor into a DATA_BLOB from a xattr. +*******************************************************************/ + +static ssize_t getxattr_do(vfs_handle_struct *handle, + files_struct *fsp, + const char *xattr_name, + uint8_t *val, + size_t size) +{ + ssize_t sizeret; + int saved_errno = 0; + + become_root(); + sizeret = SMB_VFS_FGETXATTR(fsp, xattr_name, val, size); + if (sizeret == -1) { + saved_errno = errno; + } + unbecome_root(); + + if (saved_errno != 0) { + errno = saved_errno; + } + + return sizeret; +} + +static NTSTATUS fget_acl_blob(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob) +{ + size_t size = 4096; + uint8_t *val = NULL; + uint8_t *tmp; + ssize_t sizeret; + + ZERO_STRUCTP(pblob); + + again: + + tmp = talloc_realloc(ctx, val, uint8_t, size); + if (tmp == NULL) { + TALLOC_FREE(val); + return NT_STATUS_NO_MEMORY; + } + val = tmp; + + sizeret = + getxattr_do(handle, fsp, XATTR_NTACL_NAME, val, size); + + if (sizeret >= 0) { + pblob->data = val; + pblob->length = sizeret; + return NT_STATUS_OK; + } + + if (errno != ERANGE) { + goto err; + } + + /* Too small, try again. */ + sizeret = + getxattr_do(handle, fsp, XATTR_NTACL_NAME, NULL, 0); + if (sizeret < 0) { + goto err; + } + + if (size < sizeret) { + size = sizeret; + } + + if (size > 65536) { + /* Max ACL size is 65536 bytes. */ + errno = ERANGE; + goto err; + } + + goto again; + err: + /* Real error - exit here. */ + TALLOC_FREE(val); + return map_nt_error_from_unix(errno); +} + +/******************************************************************* + Store a DATA_BLOB into an xattr given an fsp pointer. +*******************************************************************/ + +static NTSTATUS store_acl_blob_fsp(vfs_handle_struct *handle, + files_struct *fsp, + DATA_BLOB *pblob) +{ + int ret; + int saved_errno = 0; + + DEBUG(10,("store_acl_blob_fsp: storing blob length %u on file %s\n", + (unsigned int)pblob->length, fsp_str_dbg(fsp))); + + become_root(); + ret = SMB_VFS_FSETXATTR(fsp, XATTR_NTACL_NAME, + pblob->data, pblob->length, 0); + if (ret) { + saved_errno = errno; + } + unbecome_root(); + if (ret) { + DEBUG(5, ("store_acl_blob_fsp: setting attr failed for file %s" + "with error %s\n", + fsp_str_dbg(fsp), + strerror(saved_errno) )); + errno = saved_errno; + return map_nt_error_from_unix(saved_errno); + } + return NT_STATUS_OK; +} + +/********************************************************************* + Remove a Windows ACL - we're setting the underlying POSIX ACL. +*********************************************************************/ + +static int sys_acl_set_fd_xattr(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct acl_common_fsp_ext *ext = (struct acl_common_fsp_ext *) + VFS_FETCH_FSP_EXTENSION(handle, fsp); + int ret; + + ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, + fsp, + type, + theacl); + if (ret == -1) { + return -1; + } + + if (ext != NULL && ext->setting_nt_acl) { + return 0; + } + + become_root(); + SMB_VFS_FREMOVEXATTR(fsp, XATTR_NTACL_NAME); + unbecome_root(); + + return 0; +} + +static int connect_acl_xattr(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + const char *security_acl_xattr_name = NULL; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + bool ok; + struct acl_common_config *config = NULL; + + if (ret < 0) { + return ret; + } + + ok = init_acl_common_config(handle, ACL_MODULE_NAME); + if (!ok) { + DBG_ERR("init_acl_common_config failed\n"); + return -1; + } + + /* Ensure we have the parameters correct if we're + * using this module. */ + DEBUG(2,("connect_acl_xattr: setting 'inherit acls = true' " + "'dos filemode = true' and " + "'force unknown acl user = true' for service %s\n", + service )); + + lp_do_parameter(SNUM(handle->conn), "inherit acls", "true"); + lp_do_parameter(SNUM(handle->conn), "dos filemode", "true"); + lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true"); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + if (config->ignore_system_acls) { + mode_t create_mask = lp_create_mask(SNUM(handle->conn)); + char *create_mask_str = NULL; + + if ((create_mask & 0666) != 0666) { + create_mask |= 0666; + create_mask_str = talloc_asprintf(handle, "0%o", + create_mask); + if (create_mask_str == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return -1; + } + + DBG_NOTICE("setting 'create mask = %s'\n", create_mask_str); + + lp_do_parameter (SNUM(handle->conn), + "create mask", create_mask_str); + + TALLOC_FREE(create_mask_str); + } + + DBG_NOTICE("setting 'directory mask = 0777', " + "'store dos attributes = yes' and all " + "'map ...' options to 'no'\n"); + + lp_do_parameter(SNUM(handle->conn), "directory mask", "0777"); + lp_do_parameter(SNUM(handle->conn), "map archive", "no"); + lp_do_parameter(SNUM(handle->conn), "map hidden", "no"); + lp_do_parameter(SNUM(handle->conn), "map readonly", "no"); + lp_do_parameter(SNUM(handle->conn), "map system", "no"); + lp_do_parameter(SNUM(handle->conn), "store dos attributes", + "yes"); + } + + security_acl_xattr_name = lp_parm_const_string(SNUM(handle->conn), + "acl_xattr", + "security_acl_name", + NULL); + if (security_acl_xattr_name != NULL) { + config->security_acl_xattr_name = talloc_strdup(config, security_acl_xattr_name); + if (config->security_acl_xattr_name == NULL) { + return -1; + } + } + + return 0; +} + +static int acl_xattr_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + + if (flags & AT_REMOVEDIR) { + ret = rmdir_acl_common(handle, + dirfsp, + smb_fname); + } else { + ret = unlink_acl_common(handle, + dirfsp, + smb_fname, + flags); + } + return ret; +} + +static NTSTATUS acl_xattr_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + status = fget_nt_acl_common(fget_acl_blob, handle, fsp, + security_info, mem_ctx, ppdesc); + return status; +} + +static NTSTATUS acl_xattr_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + NTSTATUS status; + status = fset_nt_acl_common(fget_acl_blob, store_acl_blob_fsp, + ACL_MODULE_NAME, + handle, fsp, security_info_sent, psd); + return status; +} + +struct acl_xattr_getxattrat_state { + struct vfs_aio_state aio_state; + ssize_t xattr_size; + uint8_t *xattr_value; +}; + +static void acl_xattr_getxattrat_done(struct tevent_req *subreq); + +static struct tevent_req *acl_xattr_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct acl_xattr_getxattrat_state *state = NULL; + struct acl_common_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return NULL); + + req = tevent_req_create(mem_ctx, &state, + struct acl_xattr_getxattrat_state); + if (req == NULL) { + return NULL; + } + + if (strequal(xattr_name, config->security_acl_xattr_name)) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return tevent_req_post(req, ev); + } + if (config->security_acl_xattr_name != NULL && + strequal(xattr_name, XATTR_NTACL_NAME)) + { + xattr_name = config->security_acl_xattr_name; + } + + subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state, + ev, + handle, + dirfsp, + smb_fname, + xattr_name, + alloc_hint); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, acl_xattr_getxattrat_done, req); + + return req; +} + +static void acl_xattr_getxattrat_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct acl_xattr_getxattrat_state *state = tevent_req_data( + req, struct acl_xattr_getxattrat_state); + + state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq, + &state->aio_state, + state, + &state->xattr_value); + TALLOC_FREE(subreq); + if (state->xattr_size == -1) { + tevent_req_error(req, state->aio_state.error); + return; + } + + tevent_req_done(req); +} + +static ssize_t acl_xattr_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct acl_xattr_getxattrat_state *state = tevent_req_data( + req, struct acl_xattr_getxattrat_state); + ssize_t xattr_size; + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *aio_state = state->aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +static ssize_t acl_xattr_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + void *value, + size_t size) +{ + struct acl_common_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + if (strequal(name, config->security_acl_xattr_name)) { + errno = EACCES; + return -1; + } + if (config->security_acl_xattr_name != NULL && + strequal(name, XATTR_NTACL_NAME)) + { + name = config->security_acl_xattr_name; + } + + return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); +} + +static ssize_t acl_xattr_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + char *listbuf, + size_t bufsize) +{ + struct acl_common_config *config = NULL; + ssize_t size; + char *p = NULL; + size_t nlen, consumed; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + size = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, listbuf, bufsize); + if (size < 0) { + return -1; + } + + p = listbuf; + while (p - listbuf < size) { + nlen = strlen(p) + 1; + if (strequal(p, config->security_acl_xattr_name)) { + break; + } + p += nlen; + } + if (p - listbuf >= size) { + /* No match */ + return size; + } + + /* + * The consumed helper variable just makes the math + * a bit more digestible. + */ + consumed = p - listbuf; + if (consumed + nlen < size) { + /* If not the last name move, else just skip */ + memmove(p, p + nlen, size - consumed - nlen); + } + size -= nlen; + + return size; +} + +static int acl_xattr_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + struct acl_common_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + if (strequal(name, config->security_acl_xattr_name)) { + errno = EACCES; + return -1; + } + if (config->security_acl_xattr_name != NULL && + strequal(name, XATTR_NTACL_NAME)) + { + name = config->security_acl_xattr_name; + } + + return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); +} + +static int acl_xattr_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + const void *value, + size_t size, + int flags) +{ + struct acl_common_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct acl_common_config, + return -1); + + if (strequal(name, config->security_acl_xattr_name)) { + errno = EACCES; + return -1; + } + if (config->security_acl_xattr_name != NULL && + strequal(name, XATTR_NTACL_NAME)) + { + name = config->security_acl_xattr_name; + } + + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); +} + +static struct vfs_fn_pointers vfs_acl_xattr_fns = { + .connect_fn = connect_acl_xattr, + .unlinkat_fn = acl_xattr_unlinkat, + .fchmod_fn = fchmod_acl_module_common, + .fget_nt_acl_fn = acl_xattr_fget_nt_acl, + .fset_nt_acl_fn = acl_xattr_fset_nt_acl, + .sys_acl_set_fd_fn = sys_acl_set_fd_xattr, + .getxattrat_send_fn = acl_xattr_getxattrat_send, + .getxattrat_recv_fn = acl_xattr_getxattrat_recv, + .fgetxattr_fn = acl_xattr_fgetxattr, + .flistxattr_fn = acl_xattr_flistxattr, + .fremovexattr_fn = acl_xattr_fremovexattr, + .fsetxattr_fn = acl_xattr_fsetxattr, +}; + +static_decl_vfs; +NTSTATUS vfs_acl_xattr_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "acl_xattr", + &vfs_acl_xattr_fns); +} diff --git a/source3/modules/vfs_afsacl.c b/source3/modules/vfs_afsacl.c new file mode 100644 index 0000000..3dc80d3 --- /dev/null +++ b/source3/modules/vfs_afsacl.c @@ -0,0 +1,1085 @@ +/* + * Convert AFS acls to NT acls and vice versa. + * + * Copyright (C) Volker Lendecke, 2003 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "../librpc/gen_ndr/lsa.h" +#include "../libcli/security/security.h" +#include "../libcli/security/dom_sid.h" +#include "passdb.h" +#include "lib/afs/afs_settoken.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#include +#include +#include +#include + +#define MAXSIZE 2049 + +extern const struct dom_sid global_sid_World; +extern const struct dom_sid global_sid_Builtin_Administrators; +extern const struct dom_sid global_sid_Builtin_Backup_Operators; +extern const struct dom_sid global_sid_Authenticated_Users; +extern const struct dom_sid global_sid_NULL; + +static char space_replacement = '%'; + +/* Do we expect SIDs as pts names? */ +static bool sidpts; + +struct afs_ace { + bool positive; + char *name; + struct dom_sid sid; + enum lsa_SidType type; + uint32_t rights; + struct afs_ace *next; +}; + +struct afs_acl { + TALLOC_CTX *ctx; + int type; + int num_aces; + struct afs_ace *acelist; +}; + +struct afs_iob { + char *in, *out; + uint16_t in_size, out_size; +}; + + +static bool init_afs_acl(struct afs_acl *acl) +{ + ZERO_STRUCT(*acl); + acl->ctx = talloc_init("afs_acl"); + if (acl->ctx == NULL) { + DEBUG(10, ("Could not init afs_acl\n")); + return false; + } + return true; +} + +static void free_afs_acl(struct afs_acl *acl) +{ + if (acl->ctx != NULL) + talloc_destroy(acl->ctx); + acl->ctx = NULL; + acl->num_aces = 0; + acl->acelist = NULL; +} + +static struct afs_ace *clone_afs_ace(TALLOC_CTX *mem_ctx, struct afs_ace *ace) +{ + struct afs_ace *result = talloc(mem_ctx, struct afs_ace); + + if (result == NULL) + return NULL; + + *result = *ace; + + result->next = NULL; + result->name = talloc_strdup(mem_ctx, ace->name); + + if (result->name == NULL) { + return NULL; + } + + return result; +} + +static struct afs_ace *new_afs_ace(TALLOC_CTX *mem_ctx, + bool positive, + const char *name, uint32_t rights) +{ + struct dom_sid sid; + enum lsa_SidType type; + struct afs_ace *result; + + if (strcmp(name, "system:administrators") == 0) { + + sid_copy(&sid, &global_sid_Builtin_Administrators); + type = SID_NAME_ALIAS; + + } else if (strcmp(name, "system:anyuser") == 0) { + + sid_copy(&sid, &global_sid_World); + type = SID_NAME_ALIAS; + + } else if (strcmp(name, "system:authuser") == 0) { + + sid_copy(&sid, &global_sid_Authenticated_Users); + type = SID_NAME_WKN_GRP; + + } else if (strcmp(name, "system:backup") == 0) { + + sid_copy(&sid, &global_sid_Builtin_Backup_Operators); + type = SID_NAME_ALIAS; + + } else if (sidpts) { + /* All PTS users/groups are expressed as SIDs */ + + sid_copy(&sid, &global_sid_NULL); + type = SID_NAME_UNKNOWN; + + if (string_to_sid(&sid, name)) { + const char *user, *domain; + /* We have to find the type, look up the SID */ + lookup_sid(talloc_tos(), &sid, + &domain, &user, &type); + } + + } else { + + const char *domain, *uname; + char *p; + + p = strchr_m(name, *lp_winbind_separator()); + if (p != NULL) { + *p = '\\'; + } + + if (!lookup_name(talloc_tos(), name, LOOKUP_NAME_ALL, + &domain, &uname, &sid, &type)) { + DEBUG(10, ("Could not find AFS user %s\n", name)); + + sid_copy(&sid, &global_sid_NULL); + type = SID_NAME_UNKNOWN; + + } + } + + result = talloc(mem_ctx, struct afs_ace); + + if (result == NULL) { + DEBUG(0, ("Could not talloc AFS ace\n")); + return NULL; + } + + result->name = talloc_strdup(mem_ctx, name); + if (result->name == NULL) { + DEBUG(0, ("Could not talloc AFS ace name\n")); + return NULL; + } + + result->sid = sid; + result->type = type; + + result->positive = positive; + result->rights = rights; + + return result; +} + +static void add_afs_ace(struct afs_acl *acl, + bool positive, + const char *name, uint32_t rights) +{ + struct afs_ace *ace; + + for (ace = acl->acelist; ace != NULL; ace = ace->next) { + if ((ace->positive == positive) && + (strequal(ace->name, name))) { + ace->rights |= rights; + return; + } + } + + ace = new_afs_ace(acl->ctx, positive, name, rights); + + ace->next = acl->acelist; + acl->acelist = ace; + + acl->num_aces += 1; + + DEBUG(10, ("add_afs_ace: Added %s entry for %s with rights %d\n", + ace->positive?"positive":"negative", + ace->name, ace->rights)); +} + +/* AFS ACLs in string form are a long string of fields delimited with \n. + * + * First line: Number of positive entries + * Second line: Number of negative entries + * Third and following lines: The entries themselves + * + * An ACE is a line of two fields, delimited by \t. + * + * First field: Name + * Second field: Rights + */ + +static bool parse_afs_acl(struct afs_acl *acl, const char *acl_str) +{ + int nplus, nminus; + int aces; + + char str[MAXSIZE]; + char *p = str; + + strlcpy(str, acl_str, MAXSIZE); + + if (sscanf(p, "%d", &nplus) != 1) + return false; + + DEBUG(10, ("Found %d positive entries\n", nplus)); + + if ((p = strchr(p, '\n')) == NULL) + return false; + p += 1; + + if (sscanf(p, "%d", &nminus) != 1) + return false; + + DEBUG(10, ("Found %d negative entries\n", nminus)); + + if ((p = strchr(p, '\n')) == NULL) + return false; + p += 1; + + for (aces = nplus+nminus; aces > 0; aces--) + { + + const char *namep; + fstring name; + uint32_t rights; + char *space; + + namep = p; + + if ((p = strchr(p, '\t')) == NULL) + return false; + *p = '\0'; + p += 1; + + if (sscanf(p, "%d", &rights) != 1) + return false; + + if ((p = strchr(p, '\n')) == NULL) + return false; + p += 1; + + fstrcpy(name, namep); + + while ((space = strchr_m(name, space_replacement)) != NULL) + *space = ' '; + + add_afs_ace(acl, nplus>0, name, rights); + + nplus -= 1; + } + + return true; +} + +static bool unparse_afs_acl(struct afs_acl *acl, char *acl_str) +{ + /* TODO: String length checks!!!! */ + + int positives = 0; + int negatives = 0; + fstring line; + struct afs_ace *ace = acl->acelist; + + *acl_str = 0; + + while (ace != NULL) { + if (ace->positive) + positives++; + else + negatives++; + ace = ace->next; + } + + fstr_sprintf(line, "%d\n", positives); + if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) { + return false; + } + + fstr_sprintf(line, "%d\n", negatives); + if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) { + return false; + } + + ace = acl->acelist; + + while (ace != NULL) { + fstr_sprintf(line, "%s\t%d\n", ace->name, ace->rights); + if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) { + return false; + } + ace = ace->next; + } + return true; +} + +static uint32_t afs_to_nt_file_rights(uint32_t rights) +{ + uint32_t result = 0; + + if (rights & PRSFS_READ) + result |= FILE_READ_DATA | FILE_READ_EA | + FILE_EXECUTE | FILE_READ_ATTRIBUTES | + READ_CONTROL_ACCESS | SYNCHRONIZE_ACCESS; + + if (rights & PRSFS_WRITE) + result |= FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | + FILE_WRITE_EA | FILE_APPEND_DATA; + + if (rights & PRSFS_LOCK) + result |= WRITE_OWNER_ACCESS; + + if (rights & PRSFS_DELETE) + result |= DELETE_ACCESS; + + return result; +} + +static void afs_to_nt_dir_rights(uint32_t afs_rights, uint32_t *nt_rights, + uint8_t *flag) +{ + *nt_rights = 0; + *flag = SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT; + + if (afs_rights & PRSFS_INSERT) + *nt_rights |= FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY; + + if (afs_rights & PRSFS_LOOKUP) + *nt_rights |= FILE_READ_DATA | FILE_READ_EA | + FILE_EXECUTE | FILE_READ_ATTRIBUTES | + READ_CONTROL_ACCESS | SYNCHRONIZE_ACCESS; + + if (afs_rights & PRSFS_WRITE) + *nt_rights |= FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | + FILE_APPEND_DATA | FILE_WRITE_EA; + + if ((afs_rights & (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE)) == + (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE)) + *nt_rights |= FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | + GENERIC_WRITE_ACCESS; + + if (afs_rights & PRSFS_DELETE) + *nt_rights |= DELETE_ACCESS; + + if (afs_rights & PRSFS_ADMINISTER) + *nt_rights |= FILE_DELETE_CHILD | WRITE_DAC_ACCESS | + WRITE_OWNER_ACCESS; + + if ( (afs_rights & PRSFS_LOOKUP) == + (afs_rights & (PRSFS_LOOKUP|PRSFS_READ)) ) { + /* Only lookup right */ + *flag = SEC_ACE_FLAG_CONTAINER_INHERIT; + } +} + +#define AFS_FILE_RIGHTS (PRSFS_READ|PRSFS_WRITE|PRSFS_LOCK) +#define AFS_DIR_RIGHTS (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE|PRSFS_ADMINISTER) + +static void split_afs_acl(struct afs_acl *acl, + struct afs_acl *dir_acl, + struct afs_acl *file_acl) +{ + struct afs_ace *ace; + + init_afs_acl(dir_acl); + init_afs_acl(file_acl); + + for (ace = acl->acelist; ace != NULL; ace = ace->next) { + if (ace->rights & AFS_FILE_RIGHTS) { + add_afs_ace(file_acl, ace->positive, ace->name, + ace->rights & AFS_FILE_RIGHTS); + } + + if (ace->rights & AFS_DIR_RIGHTS) { + add_afs_ace(dir_acl, ace->positive, ace->name, + ace->rights & AFS_DIR_RIGHTS); + } + } +} + +static bool same_principal(struct afs_ace *x, struct afs_ace *y) +{ + return ( (x->positive == y->positive) && + (dom_sid_compare(&x->sid, &y->sid) == 0) ); +} + +static void merge_afs_acls(struct afs_acl *dir_acl, + struct afs_acl *file_acl, + struct afs_acl *target) +{ + struct afs_ace *ace; + + init_afs_acl(target); + + for (ace = dir_acl->acelist; ace != NULL; ace = ace->next) { + struct afs_ace *file_ace; + bool found = false; + + for (file_ace = file_acl->acelist; + file_ace != NULL; + file_ace = file_ace->next) { + if (!same_principal(ace, file_ace)) + continue; + + add_afs_ace(target, ace->positive, ace->name, + ace->rights | file_ace->rights); + found = true; + break; + } + if (!found) + add_afs_ace(target, ace->positive, ace->name, + ace->rights); + } + + for (ace = file_acl->acelist; ace != NULL; ace = ace->next) { + struct afs_ace *dir_ace; + bool already_seen = false; + + for (dir_ace = dir_acl->acelist; + dir_ace != NULL; + dir_ace = dir_ace->next) { + if (!same_principal(ace, dir_ace)) + continue; + already_seen = true; + break; + } + if (!already_seen) + add_afs_ace(target, ace->positive, ace->name, + ace->rights); + } +} + +#define PERMS_READ 0x001200a9 +#define PERMS_CHANGE 0x001301bf +#define PERMS_FULL 0x001f01ff + +static struct static_dir_ace_mapping { + uint8_t type; + uint8_t flags; + uint32_t mask; + uint32_t afs_rights; +} ace_mappings[] = { + + /* Full control */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT, + PERMS_FULL, 127 /* rlidwka */ }, + + /* Change (write) */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT, + PERMS_CHANGE, 63 /* rlidwk */ }, + + /* Read (including list folder content) */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT, + PERMS_READ, 9 /* rl */ }, + + /* Read without list folder content -- same as "l" */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT, + 0x00120089, 8 /* l */ }, + + /* some stupid workaround for preventing fallbacks */ + { 0, 0x3, 0x0012019F, 9 /* rl */ }, + { 0, 0x13, PERMS_FULL, 127 /* full */ }, + + /* read, delete and execute access plus synchronize */ + { 0, 0x3, 0x001300A9, 9 /* should be rdl, set to rl */}, + /* classical read list */ + { 0, 0x13, 0x001200A9, 9 /* rl */}, + /* almost full control, no delete */ + { 0, 0x13, PERMS_CHANGE, 63 /* rwidlk */}, + + /* List folder */ + { 0, SEC_ACE_FLAG_CONTAINER_INHERIT, + PERMS_READ, 8 /* l */ }, + + /* FULL without inheritance -- in all cases here we also get + the corresponding INHERIT_ONLY ACE in the same ACL */ + { 0, 0, PERMS_FULL, 127 /* rlidwka */ }, + + /* FULL inherit only -- counterpart to previous one */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY, + PERMS_FULL | SEC_GENERIC_WRITE, 127 /* rlidwka */ }, + + /* CHANGE without inheritance -- in all cases here we also get + the corresponding INHERIT_ONLY ACE in the same ACL */ + { 0, 0, PERMS_CHANGE, 63 /* rlidwk */ }, + + /* CHANGE inherit only -- counterpart to previous one */ + { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY, + PERMS_CHANGE | SEC_GENERIC_WRITE, 63 /* rlidwk */ }, + + /* End marker, hopefully there's no afs right 9999 :-) */ + { 0, 0, 0, 9999 } +}; + +static uint32_t nt_to_afs_dir_rights(const char *filename, const struct security_ace *ace) +{ + uint32_t result = 0; + uint32_t rights = ace->access_mask; + uint8_t flags = ace->flags; + + struct static_dir_ace_mapping *m; + + for (m = &ace_mappings[0]; m->afs_rights != 9999; m++) { + if ( (ace->type == m->type) && + (ace->flags == m->flags) && + (ace->access_mask == m->mask) ) + return m->afs_rights; + } + + DEBUG(1, ("AFSACL FALLBACK: 0x%X 0x%X 0x%X %s %X\n", + ace->type, ace->flags, ace->access_mask, filename, rights)); + + if (rights & (GENERIC_ALL_ACCESS|WRITE_DAC_ACCESS)) { + result |= PRSFS_READ | PRSFS_WRITE | PRSFS_INSERT | + PRSFS_LOOKUP | PRSFS_DELETE | PRSFS_LOCK | + PRSFS_ADMINISTER; + } + + if (rights & (GENERIC_READ_ACCESS|FILE_READ_DATA)) { + result |= PRSFS_LOOKUP; + if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) { + result |= PRSFS_READ; + } + } + + if (rights & (GENERIC_WRITE_ACCESS|FILE_WRITE_DATA)) { + result |= PRSFS_INSERT | PRSFS_DELETE; + if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) { + result |= PRSFS_WRITE | PRSFS_LOCK; + } + } + + return result; +} + +static uint32_t nt_to_afs_file_rights(const char *filename, const struct security_ace *ace) +{ + uint32_t result = 0; + uint32_t rights = ace->access_mask; + + if (rights & (GENERIC_READ_ACCESS|FILE_READ_DATA)) { + result |= PRSFS_READ; + } + + if (rights & (GENERIC_WRITE_ACCESS|FILE_WRITE_DATA)) { + result |= PRSFS_WRITE | PRSFS_LOCK; + } + + return result; +} + +static size_t afs_to_nt_acl_common(struct afs_acl *afs_acl, + SMB_STRUCT_STAT *psbuf, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + struct security_ace *nt_ace_list; + struct dom_sid owner_sid, group_sid; + struct security_acl *psa = NULL; + int good_aces; + size_t sd_size; + + struct afs_ace *afs_ace; + + uid_to_sid(&owner_sid, psbuf->st_ex_uid); + gid_to_sid(&group_sid, psbuf->st_ex_gid); + + if (afs_acl->num_aces) { + nt_ace_list = talloc_array(mem_ctx, struct security_ace, afs_acl->num_aces); + + if (nt_ace_list == NULL) + return 0; + } else { + nt_ace_list = NULL; + } + + afs_ace = afs_acl->acelist; + good_aces = 0; + + while (afs_ace != NULL) { + uint32_t nt_rights; + uint8_t flag = SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT; + + if (afs_ace->type == SID_NAME_UNKNOWN) { + DEBUG(10, ("Ignoring unknown name %s\n", + afs_ace->name)); + afs_ace = afs_ace->next; + continue; + } + + if (S_ISDIR(psbuf->st_ex_mode)) + afs_to_nt_dir_rights(afs_ace->rights, &nt_rights, + &flag); + else + nt_rights = afs_to_nt_file_rights(afs_ace->rights); + + init_sec_ace(&nt_ace_list[good_aces++], &(afs_ace->sid), + SEC_ACE_TYPE_ACCESS_ALLOWED, nt_rights, flag); + afs_ace = afs_ace->next; + } + + psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION, + good_aces, nt_ace_list); + if (psa == NULL) + return 0; + + *ppdesc = make_sec_desc(mem_ctx, SD_REVISION, + SEC_DESC_SELF_RELATIVE, + (security_info & SECINFO_OWNER) + ? &owner_sid : NULL, + (security_info & SECINFO_GROUP) + ? &group_sid : NULL, + NULL, psa, &sd_size); + + return sd_size; +} + +static size_t afs_to_nt_acl(struct afs_acl *afs_acl, + struct connection_struct *conn, + struct smb_filename *smb_fname, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + int ret; + + /* + * We can directly use SMB_VFS_STAT here, as if this was a + * POSIX call on a symlink, we've already refused it. + * For a Windows acl mapped call on a symlink, we want to follow + * it. + */ + /* Get the stat struct for the owner info. */ + ret = SMB_VFS_STAT(conn, smb_fname); + if (ret == -1) { + return 0; + } + + return afs_to_nt_acl_common(afs_acl, &smb_fname->st, security_info, + mem_ctx, ppdesc); +} + +static size_t afs_fto_nt_acl(struct afs_acl *afs_acl, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + SMB_STRUCT_STAT sbuf; + + if (fsp_get_pathref_fd(fsp) == -1) { + /* Get the stat struct for the owner info. */ + return afs_to_nt_acl(afs_acl, fsp->conn, fsp->fsp_name, + security_info, mem_ctx, ppdesc); + } + + if(SMB_VFS_FSTAT(fsp, &sbuf) != 0) { + return 0; + } + + return afs_to_nt_acl_common(afs_acl, &sbuf, security_info, + mem_ctx, ppdesc); +} + +static bool mappable_sid(const struct dom_sid *sid) +{ + struct dom_sid domain_sid; + + if (dom_sid_compare(sid, &global_sid_Builtin_Administrators) == 0) + return true; + + if (dom_sid_compare(sid, &global_sid_World) == 0) + return true; + + if (dom_sid_compare(sid, &global_sid_Authenticated_Users) == 0) + return true; + + if (dom_sid_compare(sid, &global_sid_Builtin_Backup_Operators) == 0) + return true; + + string_to_sid(&domain_sid, "S-1-5-21"); + + if (dom_sid_compare_domain(sid, &domain_sid) == 0) + return true; + + return false; +} + +static bool nt_to_afs_acl(const char *filename, + uint32_t security_info_sent, + const struct security_descriptor *psd, + uint32_t (*nt_to_afs_rights)(const char *filename, + const struct security_ace *ace), + struct afs_acl *afs_acl) +{ + const struct security_acl *dacl; + int i; + + /* Currently we *only* look at the dacl */ + + if (((security_info_sent & SECINFO_DACL) == 0) || + (psd->dacl == NULL)) + return true; + + if (!init_afs_acl(afs_acl)) + return false; + + dacl = psd->dacl; + + for (i = 0; i < dacl->num_aces; i++) { + const struct security_ace *ace = &(dacl->aces[i]); + const char *dom_name, *name; + enum lsa_SidType name_type; + char *p; + + if (ace->type != SEC_ACE_TYPE_ACCESS_ALLOWED) { + /* First cut: Only positive ACEs */ + return false; + } + + if (!mappable_sid(&ace->trustee)) { + struct dom_sid_buf buf; + DEBUG(10, ("Ignoring unmappable SID %s\n", + dom_sid_str_buf(&ace->trustee, &buf))); + continue; + } + + if (dom_sid_compare(&ace->trustee, + &global_sid_Builtin_Administrators) == 0) { + + name = "system:administrators"; + + } else if (dom_sid_compare(&ace->trustee, + &global_sid_World) == 0) { + + name = "system:anyuser"; + + } else if (dom_sid_compare(&ace->trustee, + &global_sid_Authenticated_Users) == 0) { + + name = "system:authuser"; + + } else if (dom_sid_compare(&ace->trustee, + &global_sid_Builtin_Backup_Operators) + == 0) { + + name = "system:backup"; + + } else { + + if (!lookup_sid(talloc_tos(), &ace->trustee, + &dom_name, &name, &name_type)) { + struct dom_sid_buf buf; + DEBUG(1, ("AFSACL: Could not lookup SID %s on file %s\n", + dom_sid_str_buf(&ace->trustee, &buf), + filename)); + continue; + } + + if ( (name_type == SID_NAME_USER) || + (name_type == SID_NAME_DOM_GRP) || + (name_type == SID_NAME_ALIAS) ) { + char *tmp; + tmp = talloc_asprintf(talloc_tos(), "%s%s%s", + dom_name, lp_winbind_separator(), + name); + if (tmp == NULL) { + return false; + } + if (!strlower_m(tmp)) { + return false; + } + name = tmp; + } + + if (sidpts) { + struct dom_sid_buf buf; + /* Expect all users/groups in pts as SIDs */ + name = talloc_strdup( + talloc_tos(), + dom_sid_str_buf(&ace->trustee, &buf)); + if (name == NULL) { + return false; + } + } + } + + while ((p = strchr_m(name, ' ')) != NULL) + *p = space_replacement; + + add_afs_ace(afs_acl, true, name, + nt_to_afs_rights(filename, ace)); + } + + return true; +} + +static bool afs_get_afs_acl(const char *filename, struct afs_acl *acl) +{ + struct afs_iob iob; + + int ret; + + char space[MAXSIZE]; + + DEBUG(5, ("afs_get_afs_acl: %s\n", filename)); + + iob.in_size = 0; + iob.out_size = MAXSIZE; + iob.in = iob.out = space; + + ret = afs_syscall(AFSCALL_PIOCTL, filename, VIOCGETAL, + (char *)&iob, 0); + + if (ret) { + DEBUG(1, ("got error from PIOCTL: %d\n", ret)); + return false; + } + + if (!init_afs_acl(acl)) + return false; + + if (!parse_afs_acl(acl, space)) { + DEBUG(1, ("Could not parse AFS acl\n")); + free_afs_acl(acl); + return false; + } + + return true; +} + +/* For setting an AFS ACL we have to take care of the ACEs we could + * not properly map to SIDs. Merge all of them into the new ACL. */ + +static void merge_unknown_aces(struct afs_acl *src, struct afs_acl *dst) +{ + struct afs_ace *ace; + + for (ace = src->acelist; ace != NULL; ace = ace->next) + { + struct afs_ace *copy; + + if (ace->type != SID_NAME_UNKNOWN) { + DEBUG(10, ("Not merging known ACE for %s\n", + ace->name)); + continue; + } + + DEBUG(10, ("Merging unknown ACE for %s\n", ace->name)); + + copy = clone_afs_ace(dst->ctx, ace); + + if (copy == NULL) { + DEBUG(0, ("Could not clone ACE for %s\n", ace->name)); + continue; + } + + copy->next = dst->acelist; + dst->acelist = copy; + dst->num_aces += 1; + } +} + +static NTSTATUS afs_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + struct afs_acl old_afs_acl, new_afs_acl; + struct afs_acl dir_acl, file_acl; + char acl_string[MAXSIZE]; + struct afs_iob iob; + int ret = -1; + char *name = NULL; + const char *fileacls; + + fileacls = lp_parm_const_string(SNUM(handle->conn), "afsacl", "fileacls", + "yes"); + + sidpts = lp_parm_bool(SNUM(handle->conn), "afsacl", "sidpts", false); + + ZERO_STRUCT(old_afs_acl); + ZERO_STRUCT(new_afs_acl); + ZERO_STRUCT(dir_acl); + ZERO_STRUCT(file_acl); + + name = talloc_strdup(talloc_tos(), fsp->fsp_name->base_name); + if (!name) { + return NT_STATUS_NO_MEMORY; + } + + if (!fsp->fsp_flags.is_directory) { + /* We need to get the name of the directory containing the + * file, this is where the AFS acls live */ + char *p = strrchr(name, '/'); + if (p != NULL) { + *p = '\0'; + } else { + name = talloc_strdup(talloc_tos(), "."); + if (!name) { + return NT_STATUS_NO_MEMORY; + } + } + } + + if (!afs_get_afs_acl(name, &old_afs_acl)) { + DEBUG(3, ("Could not get old ACL of %s\n", fsp_str_dbg(fsp))); + goto done; + } + + split_afs_acl(&old_afs_acl, &dir_acl, &file_acl); + + if (fsp->fsp_flags.is_directory) { + + if (!strequal(fileacls, "yes")) { + /* Throw away file acls, we depend on the + * inheritance ACEs that also give us file + * permissions */ + free_afs_acl(&file_acl); + } + + free_afs_acl(&dir_acl); + if (!nt_to_afs_acl(fsp->fsp_name->base_name, + security_info_sent, psd, + nt_to_afs_dir_rights, &dir_acl)) + goto done; + } else { + if (strequal(fileacls, "no")) { + ret = -1; + goto done; + } + + if (strequal(fileacls, "ignore")) { + ret = 0; + goto done; + } + + free_afs_acl(&file_acl); + if (!nt_to_afs_acl(fsp->fsp_name->base_name, + security_info_sent, psd, + nt_to_afs_file_rights, &file_acl)) + goto done; + } + + merge_afs_acls(&dir_acl, &file_acl, &new_afs_acl); + + merge_unknown_aces(&old_afs_acl, &new_afs_acl); + + unparse_afs_acl(&new_afs_acl, acl_string); + + iob.in = acl_string; + iob.in_size = 1+strlen(iob.in); + iob.out = NULL; + iob.out_size = 0; + + DEBUG(10, ("trying to set acl '%s' on file %s\n", iob.in, name)); + + ret = afs_syscall(AFSCALL_PIOCTL, name, VIOCSETAL, (char *)&iob, 0); + + if (ret != 0) { + DEBUG(10, ("VIOCSETAL returned %d\n", ret)); + } + + done: + free_afs_acl(&dir_acl); + free_afs_acl(&file_acl); + free_afs_acl(&old_afs_acl); + free_afs_acl(&new_afs_acl); + + return (ret == 0) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED; +} + +static NTSTATUS afsacl_fget_nt_acl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + struct afs_acl acl; + size_t sd_size; + + DEBUG(5, ("afsacl_fget_nt_acl: %s\n", fsp_str_dbg(fsp))); + + sidpts = lp_parm_bool(SNUM(fsp->conn), "afsacl", "sidpts", false); + + if (!afs_get_afs_acl(fsp->fsp_name->base_name, &acl)) { + return NT_STATUS_ACCESS_DENIED; + } + + sd_size = afs_fto_nt_acl(&acl, fsp, security_info, mem_ctx, ppdesc); + + free_afs_acl(&acl); + + return (sd_size != 0) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED; +} + +static NTSTATUS afsacl_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + return afs_set_nt_acl(handle, fsp, security_info_sent, psd); +} + +static int afsacl_connect(vfs_handle_struct *handle, + const char *service, + const char *user) +{ + const char *spc; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + spc = lp_parm_const_string(SNUM(handle->conn), "afsacl", "space", "%"); + + if (spc != NULL) + space_replacement = spc[0]; + + return 0; +} + +/* We don't have a linear form of the AFS ACL yet */ +static int afsacl_sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) +{ + errno = ENOSYS; + return -1; +} + +static struct vfs_fn_pointers vfs_afsacl_fns = { + .connect_fn = afsacl_connect, + .fget_nt_acl_fn = afsacl_fget_nt_acl, + .fset_nt_acl_fn = afsacl_fset_nt_acl, + .sys_acl_blob_get_fd_fn = afsacl_sys_acl_blob_get_fd +}; + +static_decl_vfs; +NTSTATUS vfs_afsacl_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "afsacl", + &vfs_afsacl_fns); +} diff --git a/source3/modules/vfs_aio_fork.c b/source3/modules/vfs_aio_fork.c new file mode 100644 index 0000000..87dcbdd --- /dev/null +++ b/source3/modules/vfs_aio_fork.c @@ -0,0 +1,936 @@ +/* + * Simulate the Posix AIO using mmap/fork + * + * Copyright (C) Volker Lendecke 2008 + * Copyright (C) Jeremy Allison 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "system/shmem.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "lib/async_req/async_sock.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/sys_rw.h" +#include "lib/util/sys_rw_data.h" +#include "lib/util/msghdr.h" +#include "smbprofile.h" +#include "lib/global_contexts.h" + +#if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS) +# error Can not pass file descriptors +#endif + +#undef recvmsg + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +struct aio_child_list; + +struct aio_fork_config { + bool erratic_testing_mode; + struct aio_child_list *children; +}; + +struct mmap_area { + size_t size; + void *ptr; +}; + +static int mmap_area_destructor(struct mmap_area *area) +{ + munmap(discard_const(area->ptr), area->size); + return 0; +} + +static struct mmap_area *mmap_area_init(TALLOC_CTX *mem_ctx, size_t size) +{ + struct mmap_area *result; + int fd; + + result = talloc(mem_ctx, struct mmap_area); + if (result == NULL) { + DEBUG(0, ("talloc failed\n")); + goto fail; + } + + fd = open("/dev/zero", O_RDWR); + if (fd == -1) { + DEBUG(3, ("open(\"/dev/zero\") failed: %s\n", + strerror(errno))); + goto fail; + } + + result->ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, fd, 0); + close(fd); + if (result->ptr == MAP_FAILED) { + DEBUG(1, ("mmap failed: %s\n", strerror(errno))); + goto fail; + } + + result->size = size; + talloc_set_destructor(result, mmap_area_destructor); + + return result; + +fail: + TALLOC_FREE(result); + return NULL; +} + +enum cmd_type { + READ_CMD, + WRITE_CMD, + FSYNC_CMD +}; + +static const char *cmd_type_str(enum cmd_type cmd) +{ + const char *result; + + switch (cmd) { + case READ_CMD: + result = "READ"; + break; + case WRITE_CMD: + result = "WRITE"; + break; + case FSYNC_CMD: + result = "FSYNC"; + break; + default: + result = ""; + break; + } + return result; +} + +struct rw_cmd { + size_t n; + off_t offset; + enum cmd_type cmd; + bool erratic_testing_mode; +}; + +struct rw_ret { + ssize_t size; + int ret_errno; + uint64_t duration; +}; + +struct aio_child_list; + +struct aio_child { + struct aio_child *prev, *next; + struct aio_child_list *list; + pid_t pid; + int sockfd; + struct mmap_area *map; + bool dont_delete; /* Marked as in use since last cleanup */ + bool busy; +}; + +struct aio_child_list { + struct aio_child *children; + struct tevent_timer *cleanup_event; +}; + +static ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) +{ + struct iovec iov[1]; + struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 }; + ssize_t n; + size_t bufsize = msghdr_prep_recv_fds(NULL, NULL, 0, 1); + uint8_t buf[bufsize]; + + msghdr_prep_recv_fds(&msg, buf, bufsize, 1); + + iov[0].iov_base = (void *)ptr; + iov[0].iov_len = nbytes; + + do { + n = recvmsg(fd, &msg, 0); + } while ((n == -1) && (errno == EINTR)); + + if (n <= 0) { + return n; + } + + { + size_t num_fds = msghdr_extract_fds(&msg, NULL, 0); + int fds[num_fds]; + + msghdr_extract_fds(&msg, fds, num_fds); + + if (num_fds != 1) { + size_t i; + + for (i=0; icleanup_event); + + for (child = list->children; child != NULL; child = next) { + next = child->next; + + if (child->busy) { + DEBUG(10, ("child %d currently active\n", + (int)child->pid)); + continue; + } + + if (child->dont_delete) { + DEBUG(10, ("Child %d was active since last cleanup\n", + (int)child->pid)); + child->dont_delete = false; + continue; + } + + DEBUG(10, ("Child %d idle for more than 30 seconds, " + "deleting\n", (int)child->pid)); + + TALLOC_FREE(child); + child = next; + } + + if (list->children != NULL) { + /* + * Re-schedule the next cleanup round + */ + list->cleanup_event = tevent_add_timer(global_event_context(), list, + timeval_add(&now, 30, 0), + aio_child_cleanup, list); + + } +} + +static struct aio_child_list *init_aio_children(struct vfs_handle_struct *handle) +{ + struct aio_fork_config *config; + struct aio_child_list *children; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct aio_fork_config, + return NULL); + + if (config->children == NULL) { + config->children = talloc_zero(config, struct aio_child_list); + if (config->children == NULL) { + return NULL; + } + } + children = config->children; + + /* + * Regardless of whether the child_list had been around or not, make + * sure that we have a cleanup timed event. This timed event will + * delete itself when it finds that no children are around anymore. + */ + + if (children->cleanup_event == NULL) { + children->cleanup_event = + tevent_add_timer(global_event_context(), children, + timeval_current_ofs(30, 0), + aio_child_cleanup, children); + if (children->cleanup_event == NULL) { + TALLOC_FREE(config->children); + return NULL; + } + } + + return children; +} + +static void aio_child_loop(int sockfd, struct mmap_area *map) +{ + while (true) { + int fd = -1; + ssize_t ret; + struct rw_cmd cmd_struct; + struct rw_ret ret_struct; + struct timespec start, end; + + ret = read_fd(sockfd, &cmd_struct, sizeof(cmd_struct), &fd); + if (ret != sizeof(cmd_struct)) { + DEBUG(10, ("read_fd returned %d: %s\n", (int)ret, + strerror(errno))); + exit(1); + } + + DEBUG(10, ("aio_child_loop: %s %d bytes at %d from fd %d\n", + cmd_type_str(cmd_struct.cmd), + (int)cmd_struct.n, (int)cmd_struct.offset, fd)); + + if (cmd_struct.erratic_testing_mode) { + /* + * For developer testing, we want erratic behaviour for + * async I/O times + */ + uint8_t randval; + unsigned msecs; + /* + * use generate_random_buffer, we just forked from a + * common parent state + */ + generate_random_buffer(&randval, sizeof(randval)); + msecs = (randval%20)+1; + DEBUG(10, ("delaying for %u msecs\n", msecs)); + smb_msleep(msecs); + } + + ZERO_STRUCT(ret_struct); + + PROFILE_TIMESTAMP(&start); + + switch (cmd_struct.cmd) { + case READ_CMD: + ret_struct.size = sys_pread_full( + fd, discard_const(map->ptr), cmd_struct.n, + cmd_struct.offset); +#if 0 +/* This breaks "make test" when run with aio_fork module. */ +#ifdef DEVELOPER + ret_struct.size = MAX(1, ret_struct.size * 0.9); +#endif +#endif + break; + case WRITE_CMD: + ret_struct.size = sys_pwrite_full( + fd, discard_const(map->ptr), cmd_struct.n, + cmd_struct.offset); + break; + case FSYNC_CMD: + ret_struct.size = fsync(fd); + break; + default: + ret_struct.size = -1; + errno = EINVAL; + } + + PROFILE_TIMESTAMP(&end); + ret_struct.duration = nsec_time_diff(&end, &start); + DEBUG(10, ("aio_child_loop: syscall returned %d\n", + (int)ret_struct.size)); + + if (ret_struct.size == -1) { + ret_struct.ret_errno = errno; + } + + /* + * Close the fd before telling our parent we're done. The + * parent might close and re-open the file very quickly, and + * with system-level share modes (GPFS) we would get an + * unjustified SHARING_VIOLATION. + */ + close(fd); + + ret = write_data(sockfd, (char *)&ret_struct, + sizeof(ret_struct)); + if (ret != sizeof(ret_struct)) { + DEBUG(10, ("could not write ret_struct: %s\n", + strerror(errno))); + exit(2); + } + } +} + +static int aio_child_destructor(struct aio_child *child) +{ + char c=0; + + SMB_ASSERT(!child->busy); + + DEBUG(10, ("aio_child_destructor: removing child %d on fd %d\n", + (int)child->pid, child->sockfd)); + + /* + * closing the sockfd makes the child not return from recvmsg() on RHEL + * 5.5 so instead force the child to exit by writing bad data to it + */ + sys_write_v(child->sockfd, &c, sizeof(c)); + close(child->sockfd); + DLIST_REMOVE(child->list->children, child); + return 0; +} + +/* + * We have to close all fd's in open files, we might incorrectly hold a system + * level share mode on a file. + */ + +static struct files_struct *close_fsp_fd(struct files_struct *fsp, + void *private_data) +{ + if ((fsp->fh != NULL) && (fsp_get_pathref_fd(fsp) != -1)) { + close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + } + return NULL; +} + +static int create_aio_child(struct smbd_server_connection *sconn, + struct aio_child_list *children, + size_t map_size, + struct aio_child **presult) +{ + struct aio_child *result; + int fdpair[2]; + int ret; + + fdpair[0] = fdpair[1] = -1; + + result = talloc_zero(children, struct aio_child); + if (result == NULL) { + return ENOMEM; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) { + ret = errno; + DEBUG(10, ("socketpair() failed: %s\n", strerror(errno))); + goto fail; + } + + DEBUG(10, ("fdpair = %d/%d\n", fdpair[0], fdpair[1])); + + result->map = mmap_area_init(result, map_size); + if (result->map == NULL) { + ret = errno; + DEBUG(0, ("Could not create mmap area\n")); + goto fail; + } + + result->pid = fork(); + if (result->pid == -1) { + ret = errno; + DEBUG(0, ("fork failed: %s\n", strerror(errno))); + goto fail; + } + + if (result->pid == 0) { + close(fdpair[0]); + result->sockfd = fdpair[1]; + files_forall(sconn, close_fsp_fd, NULL); + aio_child_loop(result->sockfd, result->map); + } + + DEBUG(10, ("Child %d created with sockfd %d\n", + (int)result->pid, fdpair[0])); + + result->sockfd = fdpair[0]; + close(fdpair[1]); + + result->list = children; + DLIST_ADD(children->children, result); + + talloc_set_destructor(result, aio_child_destructor); + + *presult = result; + + return 0; + + fail: + if (fdpair[0] != -1) close(fdpair[0]); + if (fdpair[1] != -1) close(fdpair[1]); + TALLOC_FREE(result); + + return ret; +} + +static int get_idle_child(struct vfs_handle_struct *handle, + struct aio_child **pchild) +{ + struct aio_child_list *children; + struct aio_child *child; + + children = init_aio_children(handle); + if (children == NULL) { + return ENOMEM; + } + + for (child = children->children; child != NULL; child = child->next) { + if (!child->busy) { + break; + } + } + + if (child == NULL) { + int ret; + + DEBUG(10, ("no idle child found, creating new one\n")); + + ret = create_aio_child(handle->conn->sconn, children, + 128*1024, &child); + if (ret != 0) { + DEBUG(10, ("create_aio_child failed: %s\n", + strerror(errno))); + return ret; + } + } + + child->dont_delete = true; + child->busy = true; + + *pchild = child; + return 0; +} + +struct aio_fork_pread_state { + struct aio_child *child; + size_t n; + void *data; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void aio_fork_pread_done(struct tevent_req *subreq); + +static struct tevent_req *aio_fork_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct aio_fork_pread_state *state; + struct rw_cmd cmd; + ssize_t written; + int err; + struct aio_fork_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct aio_fork_config, + return NULL); + + req = tevent_req_create(mem_ctx, &state, struct aio_fork_pread_state); + if (req == NULL) { + return NULL; + } + state->n = n; + state->data = data; + + if (n > 128*1024) { + /* TODO: support variable buffers */ + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + err = get_idle_child(handle, &state->child); + if (err != 0) { + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + ZERO_STRUCT(cmd); + cmd.n = n; + cmd.offset = offset; + cmd.cmd = READ_CMD; + cmd.erratic_testing_mode = config->erratic_testing_mode; + + DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp), + (int)state->child->pid)); + + /* + * Not making this async. We're writing into an empty unix + * domain socket. This should never block. + */ + written = write_fd(state->child->sockfd, &cmd, sizeof(cmd), + fsp_get_io_fd(fsp)); + if (written == -1) { + err = errno; + + TALLOC_FREE(state->child); + + DEBUG(10, ("write_fd failed: %s\n", strerror(err))); + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + subreq = read_packet_send(state, ev, state->child->sockfd, + sizeof(struct rw_ret), NULL, NULL); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(state->child); /* we sent sth down */ + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, aio_fork_pread_done, req); + return req; +} + +static void aio_fork_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct aio_fork_pread_state *state = tevent_req_data( + req, struct aio_fork_pread_state); + ssize_t nread; + uint8_t *buf; + int err; + struct rw_ret *retbuf; + + nread = read_packet_recv(subreq, talloc_tos(), &buf, &err); + TALLOC_FREE(subreq); + if (nread == -1) { + TALLOC_FREE(state->child); + tevent_req_error(req, err); + return; + } + + retbuf = (struct rw_ret *)buf; + state->ret = retbuf->size; + state->vfs_aio_state.error = retbuf->ret_errno; + state->vfs_aio_state.duration = retbuf->duration; + + if ((size_t)state->ret > state->n) { + tevent_req_error(req, EIO); + state->child->busy = false; + return; + } + memcpy(state->data, state->child->map->ptr, state->ret); + + state->child->busy = false; + + tevent_req_done(req); +} + +static ssize_t aio_fork_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct aio_fork_pread_state *state = tevent_req_data( + req, struct aio_fork_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct aio_fork_pwrite_state { + struct aio_child *child; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void aio_fork_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *aio_fork_pwrite_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct aio_fork_pwrite_state *state; + struct rw_cmd cmd; + ssize_t written; + int err; + struct aio_fork_config *config; + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct aio_fork_config, + return NULL); + + req = tevent_req_create(mem_ctx, &state, struct aio_fork_pwrite_state); + if (req == NULL) { + return NULL; + } + + if (n > 128*1024) { + /* TODO: support variable buffers */ + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + err = get_idle_child(handle, &state->child); + if (err != 0) { + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + memcpy(state->child->map->ptr, data, n); + + ZERO_STRUCT(cmd); + cmd.n = n; + cmd.offset = offset; + cmd.cmd = WRITE_CMD; + cmd.erratic_testing_mode = config->erratic_testing_mode; + + DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp), + (int)state->child->pid)); + + /* + * Not making this async. We're writing into an empty unix + * domain socket. This should never block. + */ + written = write_fd(state->child->sockfd, &cmd, sizeof(cmd), + fsp_get_io_fd(fsp)); + if (written == -1) { + err = errno; + + TALLOC_FREE(state->child); + + DEBUG(10, ("write_fd failed: %s\n", strerror(err))); + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + subreq = read_packet_send(state, ev, state->child->sockfd, + sizeof(struct rw_ret), NULL, NULL); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(state->child); /* we sent sth down */ + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, aio_fork_pwrite_done, req); + return req; +} + +static void aio_fork_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct aio_fork_pwrite_state *state = tevent_req_data( + req, struct aio_fork_pwrite_state); + ssize_t nread; + uint8_t *buf; + int err; + struct rw_ret *retbuf; + + nread = read_packet_recv(subreq, talloc_tos(), &buf, &err); + TALLOC_FREE(subreq); + if (nread == -1) { + TALLOC_FREE(state->child); + tevent_req_error(req, err); + return; + } + + state->child->busy = false; + + retbuf = (struct rw_ret *)buf; + state->ret = retbuf->size; + state->vfs_aio_state.error = retbuf->ret_errno; + state->vfs_aio_state.duration = retbuf->duration; + tevent_req_done(req); +} + +static ssize_t aio_fork_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct aio_fork_pwrite_state *state = tevent_req_data( + req, struct aio_fork_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct aio_fork_fsync_state { + struct aio_child *child; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void aio_fork_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *aio_fork_fsync_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp) +{ + struct tevent_req *req, *subreq; + struct aio_fork_fsync_state *state; + struct rw_cmd cmd; + ssize_t written; + int err; + struct aio_fork_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct aio_fork_config, + return NULL); + + req = tevent_req_create(mem_ctx, &state, struct aio_fork_fsync_state); + if (req == NULL) { + return NULL; + } + + err = get_idle_child(handle, &state->child); + if (err != 0) { + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + ZERO_STRUCT(cmd); + cmd.cmd = FSYNC_CMD; + cmd.erratic_testing_mode = config->erratic_testing_mode; + + DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp), + (int)state->child->pid)); + + /* + * Not making this async. We're writing into an empty unix + * domain socket. This should never block. + */ + written = write_fd(state->child->sockfd, &cmd, sizeof(cmd), + fsp_get_io_fd(fsp)); + if (written == -1) { + err = errno; + + TALLOC_FREE(state->child); + + DEBUG(10, ("write_fd failed: %s\n", strerror(err))); + tevent_req_error(req, err); + return tevent_req_post(req, ev); + } + + subreq = read_packet_send(state, ev, state->child->sockfd, + sizeof(struct rw_ret), NULL, NULL); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(state->child); /* we sent sth down */ + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, aio_fork_fsync_done, req); + return req; +} + +static void aio_fork_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct aio_fork_fsync_state *state = tevent_req_data( + req, struct aio_fork_fsync_state); + ssize_t nread; + uint8_t *buf; + int err; + struct rw_ret *retbuf; + + nread = read_packet_recv(subreq, talloc_tos(), &buf, &err); + TALLOC_FREE(subreq); + if (nread == -1) { + TALLOC_FREE(state->child); + tevent_req_error(req, err); + return; + } + + state->child->busy = false; + + retbuf = (struct rw_ret *)buf; + state->ret = retbuf->size; + state->vfs_aio_state.error = retbuf->ret_errno; + state->vfs_aio_state.duration = retbuf->duration; + tevent_req_done(req); +} + +static int aio_fork_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct aio_fork_fsync_state *state = tevent_req_data( + req, struct aio_fork_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int aio_fork_connect(vfs_handle_struct *handle, const char *service, + const char *user) +{ + int ret; + struct aio_fork_config *config; + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + config = talloc_zero(handle->conn, struct aio_fork_config); + if (!config) { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0, ("talloc_zero() failed\n")); + return -1; + } + + config->erratic_testing_mode = lp_parm_bool(SNUM(handle->conn), "vfs_aio_fork", + "erratic_testing_mode", false); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct aio_fork_config, + return -1); + + return 0; +} + +static struct vfs_fn_pointers vfs_aio_fork_fns = { + .connect_fn = aio_fork_connect, + .pread_send_fn = aio_fork_pread_send, + .pread_recv_fn = aio_fork_pread_recv, + .pwrite_send_fn = aio_fork_pwrite_send, + .pwrite_recv_fn = aio_fork_pwrite_recv, + .fsync_send_fn = aio_fork_fsync_send, + .fsync_recv_fn = aio_fork_fsync_recv, +}; + +static_decl_vfs; +NTSTATUS vfs_aio_fork_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "aio_fork", &vfs_aio_fork_fns); +} diff --git a/source3/modules/vfs_aio_pthread.c b/source3/modules/vfs_aio_pthread.c new file mode 100644 index 0000000..b099a6b --- /dev/null +++ b/source3/modules/vfs_aio_pthread.c @@ -0,0 +1,538 @@ +/* + * Simulate Posix AIO using pthreads. + * + * Based on the aio_fork work from Volker and Volker's pthreadpool library. + * + * Copyright (C) Volker Lendecke 2008 + * Copyright (C) Jeremy Allison 2012 + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "system/shmem.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "../lib/pthreadpool/pthreadpool_tevent.h" +#ifdef HAVE_LINUX_FALLOC_H +#include +#endif + +#if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS) + +/* + * We must have openat() to do any thread-based + * asynchronous opens. We also must be using + * thread-specific credentials (Linux-only + * for now). + */ + +struct aio_open_private_data { + struct aio_open_private_data *prev, *next; + /* Inputs. */ + int dir_fd; + bool opened_dir_fd; + int flags; + mode_t mode; + uint64_t mid; + bool in_progress; + struct smb_filename *fsp_name; + struct smb_filename *smb_fname; + connection_struct *conn; + struct smbXsrv_connection *xconn; + const struct security_unix_token *ux_tok; + uint64_t initial_allocation_size; + /* Returns. */ + int ret_fd; + int ret_errno; +}; + +/* List of outstanding requests we have. */ +static struct aio_open_private_data *open_pd_list; + +static void aio_open_do(struct aio_open_private_data *opd); +static void opd_free(struct aio_open_private_data *opd); + +/************************************************************************ + Find the open private data by mid. +***********************************************************************/ + +static struct aio_open_private_data *find_open_private_data_by_mid(uint64_t mid) +{ + struct aio_open_private_data *opd; + + for (opd = open_pd_list; opd != NULL; opd = opd->next) { + if (opd->mid == mid) { + return opd; + } + } + + return NULL; +} + +/************************************************************************ + Callback when an open completes. +***********************************************************************/ + +static void aio_open_handle_completion(struct tevent_req *subreq) +{ + struct aio_open_private_data *opd = + tevent_req_callback_data(subreq, + struct aio_open_private_data); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + + /* + * We're no longer in flight. Remove the + * destructor used to preserve opd so + * a talloc_free actually removes it. + */ + talloc_set_destructor(opd, NULL); + + if (opd->conn == NULL) { + /* + * We were shutdown closed in flight. No one + * wants the result, and state has been reparented + * to the NULL context, so just free it so we + * don't leak memory. + */ + DBG_NOTICE("aio open request for %s abandoned in flight\n", + opd->fsp_name->base_name); + if (opd->ret_fd != -1) { + close(opd->ret_fd); + opd->ret_fd = -1; + } + /* + * Find outstanding event and reschedule so the client + * gets an error message return from the open. + */ + schedule_deferred_open_message_smb(opd->xconn, opd->mid); + opd_free(opd); + return; + } + + if (ret != 0) { + bool ok; + + if (ret != EAGAIN) { + smb_panic("aio_open_handle_completion"); + /* notreached. */ + return; + } + /* + * Make sure we run as the user again + */ + ok = change_to_user_and_service(opd->conn, opd->conn->vuid); + if (!ok) { + smb_panic("Can't change to user"); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + aio_open_do(opd); + } + + DEBUG(10,("aio_open_handle_completion: mid %llu " + "for file %s completed\n", + (unsigned long long)opd->mid, + opd->fsp_name->base_name)); + + opd->in_progress = false; + + /* Find outstanding event and reschedule. */ + if (!schedule_deferred_open_message_smb(opd->xconn, opd->mid)) { + /* + * Outstanding event didn't exist or was + * cancelled. Free up the fd and throw + * away the result. + */ + if (opd->ret_fd != -1) { + close(opd->ret_fd); + opd->ret_fd = -1; + } + opd_free(opd); + } +} + +/***************************************************************** + The core of the async open code - the worker function. Note we + use the new openat() system call to avoid any problems with + current working directory changes plus we change credentials + on the thread to prevent any security race conditions. +*****************************************************************/ + +static void aio_open_worker(void *private_data) +{ + struct aio_open_private_data *opd = + (struct aio_open_private_data *)private_data; + + /* Become the correct credential on this thread. */ + if (set_thread_credentials(opd->ux_tok->uid, + opd->ux_tok->gid, + (size_t)opd->ux_tok->ngroups, + opd->ux_tok->groups) != 0) { + opd->ret_fd = -1; + opd->ret_errno = errno; + return; + } + + aio_open_do(opd); +} + +static void aio_open_do(struct aio_open_private_data *opd) +{ + opd->ret_fd = openat(opd->dir_fd, + opd->smb_fname->base_name, + opd->flags, + opd->mode); + + if (opd->ret_fd == -1) { + opd->ret_errno = errno; + } else { + /* Create was successful. */ + opd->ret_errno = 0; + +#if defined(HAVE_LINUX_FALLOCATE) + /* + * See if we can set the initial + * allocation size. We don't record + * the return for this as it's an + * optimization - the upper layer + * will also do this for us once + * the open returns. + */ + if (opd->initial_allocation_size) { + (void)fallocate(opd->ret_fd, + FALLOC_FL_KEEP_SIZE, + 0, + (off_t)opd->initial_allocation_size); + } +#endif + } +} + +/************************************************************************ + Open private data teardown. +***********************************************************************/ + +static void opd_free(struct aio_open_private_data *opd) +{ + if (opd->opened_dir_fd && opd->dir_fd != -1) { + close(opd->dir_fd); + } + DLIST_REMOVE(open_pd_list, opd); + TALLOC_FREE(opd); +} + +/************************************************************************ + Create and initialize a private data struct for async open. +***********************************************************************/ + +static struct aio_open_private_data *create_private_open_data( + TALLOC_CTX *ctx, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const files_struct *fsp, + int flags, + mode_t mode) +{ + struct aio_open_private_data *opd = talloc_zero(ctx, + struct aio_open_private_data); + + if (!opd) { + return NULL; + } + + *opd = (struct aio_open_private_data) { + .dir_fd = -1, + .ret_fd = -1, + .ret_errno = EINPROGRESS, + .flags = flags, + .mode = mode, + .mid = fsp->mid, + .in_progress = true, + .conn = fsp->conn, + /* + * TODO: In future we need a proper algorithm + * to find the correct connection for a fsp. + * For now we only have one connection, so this is correct... + */ + .xconn = fsp->conn->sconn->client->connections, + .initial_allocation_size = fsp->initial_allocation_size, + }; + + /* Copy our current credentials. */ + opd->ux_tok = copy_unix_token(opd, get_current_utok(fsp->conn)); + if (opd->ux_tok == NULL) { + opd_free(opd); + return NULL; + } + + /* + * Copy the full fsp_name and smb_fname which is the basename. + */ + opd->smb_fname = cp_smb_filename(opd, smb_fname); + if (opd->smb_fname == NULL) { + opd_free(opd); + return NULL; + } + + opd->fsp_name = cp_smb_filename(opd, fsp->fsp_name); + if (opd->fsp_name == NULL) { + opd_free(opd); + return NULL; + } + + if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) { + opd->dir_fd = fsp_get_pathref_fd(dirfsp); + } else { +#if defined(O_DIRECTORY) + opd->dir_fd = open(".", O_RDONLY|O_DIRECTORY); +#else + opd->dir_fd = open(".", O_RDONLY); +#endif + opd->opened_dir_fd = true; + } + if (opd->dir_fd == -1) { + opd_free(opd); + return NULL; + } + + DLIST_ADD_END(open_pd_list, opd); + return opd; +} + +static int opd_inflight_destructor(struct aio_open_private_data *opd) +{ + /* + * Setting conn to NULL allows us to + * discover the connection was torn + * down which kills the fsp that owns + * opd. + */ + DBG_NOTICE("aio open request for %s cancelled\n", + opd->fsp_name->base_name); + opd->conn = NULL; + /* Don't let opd go away. */ + return -1; +} + +/***************************************************************** + Setup an async open. +*****************************************************************/ + +static int open_async(const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const files_struct *fsp, + int flags, + mode_t mode) +{ + struct aio_open_private_data *opd = NULL; + struct tevent_req *subreq = NULL; + + /* + * Allocate off fsp->conn, not NULL or fsp. As we're going + * async fsp will get talloc_free'd when we return + * EINPROGRESS/NT_STATUS_MORE_PROCESSING_REQUIRED. A new fsp + * pointer gets allocated on every re-run of the + * open code path. Allocating on fsp->conn instead + * of NULL allows use to get notified via destructor + * if the conn is force-closed or we shutdown. + * opd is always safely freed in all codepath so no + * memory leaks. + */ + opd = create_private_open_data(fsp->conn, + dirfsp, + smb_fname, + fsp, + flags, + mode); + if (opd == NULL) { + DEBUG(10, ("open_async: Could not create private data.\n")); + return -1; + } + + subreq = pthreadpool_tevent_job_send(opd, + fsp->conn->sconn->ev_ctx, + fsp->conn->sconn->pool, + aio_open_worker, opd); + if (subreq == NULL) { + opd_free(opd); + return -1; + } + tevent_req_set_callback(subreq, aio_open_handle_completion, opd); + + DEBUG(5,("open_async: mid %llu created for file %s\n", + (unsigned long long)opd->mid, + opd->fsp_name->base_name)); + + /* + * Add a destructor to protect us from connection + * teardown whilst the open thread is in flight. + */ + talloc_set_destructor(opd, opd_inflight_destructor); + + /* Cause the calling code to reschedule us. */ + errno = EINPROGRESS; /* Maps to NT_STATUS_MORE_PROCESSING_REQUIRED. */ + return -1; +} + +/***************************************************************** + Look for a matching SMB2 mid. If we find it we're rescheduled, + just return the completed open. +*****************************************************************/ + +static bool find_completed_open(files_struct *fsp, + int *p_fd, + int *p_errno) +{ + struct aio_open_private_data *opd; + + opd = find_open_private_data_by_mid(fsp->mid); + if (!opd) { + return false; + } + + if (opd->in_progress) { + DEBUG(0,("find_completed_open: mid %llu " + "still in progress for " + "file %s. PANIC !\n", + (unsigned long long)opd->mid, + opd->fsp_name->base_name)); + /* Disaster ! This is an open timeout. Just panic. */ + smb_panic("find_completed_open - in_progress\n"); + /* notreached. */ + return false; + } + + *p_fd = opd->ret_fd; + *p_errno = opd->ret_errno; + + DEBUG(5,("find_completed_open: mid %llu returning " + "fd = %d, errno = %d (%s) " + "for file %s\n", + (unsigned long long)opd->mid, + opd->ret_fd, + opd->ret_errno, + strerror(opd->ret_errno), + smb_fname_str_dbg(fsp->fsp_name))); + + /* Now we can free the opd. */ + opd_free(opd); + return true; +} + +/***************************************************************** + The core open function. Only go async on O_CREAT|O_EXCL + opens to prevent any race conditions. +*****************************************************************/ + +static int aio_pthread_openat_fn(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + int my_errno = 0; + int fd = -1; + bool aio_allow_open = lp_parm_bool( + SNUM(handle->conn), "aio_pthread", "aio open", false); + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + if (is_named_stream(smb_fname)) { + /* Don't handle stream opens. */ + errno = ENOENT; + return -1; + } + + if (fsp->conn->sconn->pool == NULL) { + /* + * a threadpool is required for async support + */ + aio_allow_open = false; + } + + if (fsp->conn->sconn->client != NULL && + fsp->conn->sconn->client->server_multi_channel_enabled) { + /* + * This module is not compatible with multi channel yet. + */ + aio_allow_open = false; + } + + if (fsp->fsp_flags.is_pathref) { + /* Use SMB_VFS_NEXT_OPENAT() to call openat() with O_PATH. */ + aio_allow_open = false; + } + + if (!(how->flags & O_CREAT)) { + /* Only creates matter. */ + aio_allow_open = false; + } + + if (!(how->flags & O_EXCL)) { + /* Only creates with O_EXCL matter. */ + aio_allow_open = false; + } + + if (!aio_allow_open) { + /* aio opens turned off. */ + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + /* + * See if this is a reentrant call - i.e. is this a + * restart of an existing open that just completed. + */ + + if (find_completed_open(fsp, + &fd, + &my_errno)) { + errno = my_errno; + return fd; + } + + /* Ok, it's a create exclusive call - pass it to a thread helper. */ + return open_async(dirfsp, smb_fname, fsp, how->flags, how->mode); +} +#endif + +static struct vfs_fn_pointers vfs_aio_pthread_fns = { +#if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS) + .openat_fn = aio_pthread_openat_fn, +#endif +}; + +static_decl_vfs; +NTSTATUS vfs_aio_pthread_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "aio_pthread", &vfs_aio_pthread_fns); +} diff --git a/source3/modules/vfs_aixacl.c b/source3/modules/vfs_aixacl.c new file mode 100644 index 0000000..0628fae --- /dev/null +++ b/source3/modules/vfs_aixacl.c @@ -0,0 +1,131 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls + Copyright (C) Jim McDonough 2006 + + 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/filesys.h" +#include "smbd/smbd.h" +#include "vfs_aixacl_util.h" + +SMB_ACL_T aixacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + + struct acl *file_acl = (struct acl *)NULL; + struct smb_acl_t *result = (struct smb_acl_t *)NULL; + + int rc = 0; + uid_t user_id; + + /* AIX has no DEFAULT */ + if ( type == SMB_ACL_TYPE_DEFAULT ) { + return NULL; + } + + /* Get the acl using fstatacl */ + + DEBUG(10,("Entering AIX sys_acl_get_fd\n")); + DEBUG(10,("fd is %d\n",fsp_get_io_fd(fsp))); + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + + if(file_acl == NULL) { + errno=ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_fd is %d\n",errno)); + return(NULL); + } + + memset(file_acl,0,BUFSIZ); + + rc = fstatacl(fsp_get_io_fd(fsp),0,file_acl,BUFSIZ); + if( (rc == -1) && (errno == ENOSPC)) { + struct acl *new_acl = SMB_MALLOC(file_acl->acl_len + sizeof(struct acl)); + if( new_acl == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + return NULL; + } + file_acl = new_acl; + rc = fstatacl(fsp_get_io_fd(fsp),0,file_acl,file_acl->acl_len + sizeof(struct acl)); + if( rc == -1) { + DEBUG(0,("fstatacl returned %d with errno %d\n",rc,errno)); + SAFE_FREE(file_acl); + return(NULL); + } + } + + DEBUG(10,("Got facl and returned it\n")); + + result = aixacl_to_smbacl(file_acl, mem_ctx); + SAFE_FREE(file_acl); + return result; + + /*errno = ENOTSUP; + return NULL;*/ +} + +int aixacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct acl *file_acl = NULL; + unsigned int rc; + + file_acl = aixacl_smb_to_aixacl(type, theacl); + if (!file_acl) + return -1; + + if (fsp->fsp_flags.is_pathref) { + /* + * This is no longer a handle based call. + */ + return chacl(fsp->fsp_name->base_name, + file_acl, + file_acl->acl_len); + } + + rc = fchacl(fsp_get_io_fd(fsp),file_acl,file_acl->acl_len); + DEBUG(10,("errno is %d\n",errno)); + DEBUG(10,("return code is %d\n",rc)); + SAFE_FREE(file_acl); + DEBUG(10,("Exiting aixacl_sys_acl_set_fd\n")); + + return rc; +} + +int aixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + return 0; /* otherwise you can't set acl at upper level */ +} + +static struct vfs_fn_pointers vfs_aixacl_fns = { + .sys_acl_get_fd_fn = aixacl_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = aixacl_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = aixacl_sys_acl_delete_def_fd, +}; + +static_decl_vfs; +NTSTATUS vfs_aixacl_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "aixacl", + &vfs_aixacl_fns); +} diff --git a/source3/modules/vfs_aixacl.h b/source3/modules/vfs_aixacl.h new file mode 100644 index 0000000..f9fe3f8 --- /dev/null +++ b/source3/modules/vfs_aixacl.h @@ -0,0 +1,34 @@ +/* + Copyright (C) Bjoern Jacke 2022 + + 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 . +*/ + +#ifndef __VFS_AIXACL_H__ +#define __VFS_AIXACL_H__ + +SMB_ACL_T aixacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx); + +int aixacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T acl_d); + +int aixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp); + +#endif diff --git a/source3/modules/vfs_aixacl2.c b/source3/modules/vfs_aixacl2.c new file mode 100644 index 0000000..923b54f --- /dev/null +++ b/source3/modules/vfs_aixacl2.c @@ -0,0 +1,480 @@ +/* + * Convert JFS2 NFS4/AIXC acls to NT acls and vice versa. + * + * Copyright (C) Volker Lendecke, 2006 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "nfs4_acls.h" +#include "vfs_aixacl_util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define AIXACL2_MODULE_NAME "aixacl2" + +typedef union aixjfs2_acl_t { + nfs4_acl_int_t jfs2_acl[1]; + aixc_acl_t aixc_acl[1]; +}AIXJFS2_ACL_T; + +static int32_t aixacl2_getlen(AIXJFS2_ACL_T *acl, acl_type_t *type) +{ + int32_t len; + + if(type->u64 == ACL_NFS4) { + len = acl->jfs2_acl[0].aclLength; + } + else { + if(type->u64 == ACL_AIXC) { + len = acl->aixc_acl[0].acl_len; + } else { + DEBUG(0,("aixacl2_getlen:unknown type:%d\n",type->u64)); + return False; + } + } + DEBUG(10,("aixacl2_getlen:%d\n",len)); + return len; +} + +static AIXJFS2_ACL_T *aixjfs2_getacl_alloc(const char *fname, acl_type_t *type) +{ + AIXJFS2_ACL_T *acl; + size_t len = 200; + mode_t mode; + int ret; + uint64_t ctl_flag=0; + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_tos(); + acl = (AIXJFS2_ACL_T *)TALLOC_SIZE(mem_ctx, len); + if (acl == NULL) { + errno = ENOMEM; + return NULL; + } + + if(type->u64 == ACL_ANY) { + ctl_flag = ctl_flag | GET_ACLINFO_ONLY; + } + + ret = aclx_get((char *)fname, ctl_flag, type, acl, &len, &mode); + if ((ret != 0) && (errno == ENOSPC)) { + len = aixacl2_getlen(acl, type) + sizeof(AIXJFS2_ACL_T); + DEBUG(10,("aixjfs2_getacl_alloc - acl_len:%d\n",len)); + + acl = (AIXJFS2_ACL_T *)TALLOC_SIZE(mem_ctx, len); + if (acl == NULL) { + errno = ENOMEM; + return NULL; + } + + ret = aclx_get((char *)fname, ctl_flag, type, acl, &len, &mode); + } + if (ret != 0) { + DEBUG(8, ("aclx_get failed with %s\n", strerror(errno))); + return NULL; + } + + return acl; +} + +static bool aixjfs2_get_nfs4_acl(TALLOC_CTX *mem_ctx, const char *name, + struct SMB4ACL_T **ppacl, bool *pretryPosix) +{ + int32_t i; + + AIXJFS2_ACL_T *pacl = NULL; + nfs4_acl_int_t *jfs2_acl = NULL; + nfs4_ace_int_t *jfs2_ace = NULL; + acl_type_t type; + + DEBUG(10,("jfs2 get_nt_acl invoked for %s\n", name)); + + memset(&type, 0, sizeof(acl_type_t)); + type.u64 = ACL_NFS4; + + pacl = aixjfs2_getacl_alloc(name, &type); + if (pacl == NULL) { + DEBUG(9, ("aixjfs2_getacl_alloc failed for %s with %s\n", + name, strerror(errno))); + if (errno==ENOSYS) + *pretryPosix = True; + return False; + } + + jfs2_acl = &pacl->jfs2_acl[0]; + DEBUG(10, ("len: %d, version: %d, nace: %d, type: 0x%x\n", + jfs2_acl->aclLength, jfs2_acl->aclVersion, jfs2_acl->aclEntryN, type.u64)); + + *ppacl = smb_create_smb4acl(mem_ctx); + if (*ppacl==NULL) + return False; + + jfs2_ace = &jfs2_acl->aclEntry[0]; + for (i=0; iaclEntryN; i++) { + SMB_ACE4PROP_T aceprop; + + DEBUG(10, ("type: %d, iflags: %x, flags: %x, mask: %x, " + "who: %d, aclLen: %d\n", jfs2_ace->aceType, jfs2_ace->flags, + jfs2_ace->aceFlags, jfs2_ace->aceMask, jfs2_ace->aceWho.id, jfs2_ace->entryLen)); + + aceprop.aceType = jfs2_ace->aceType; + aceprop.aceFlags = jfs2_ace->aceFlags; + aceprop.aceMask = jfs2_ace->aceMask; + aceprop.flags = (jfs2_ace->flags&ACE4_ID_SPECIAL) ? SMB_ACE4_ID_SPECIAL : 0; + + /* don't care it's real content is only 16 or 32 bit */ + aceprop.who.id = jfs2_ace->aceWho.id; + + if (smb_add_ace4(*ppacl, &aceprop)==NULL) + return False; + + /* iterate to the next jfs2 ace */ + jfs2_ace = (nfs4_ace_int_t *)(((char *)jfs2_ace) + jfs2_ace->entryLen); + } + + DEBUG(10,("jfs2 get_nt_acl finished successfully\n")); + + return True; +} + +static NTSTATUS aixjfs2_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + struct SMB4ACL_T *pacl = NULL; + bool result; + bool retryPosix = False; + TALLOC_CTX *frame = talloc_stackframe(); + + *ppdesc = NULL; + result = aixjfs2_get_nfs4_acl(frame, fsp->fsp_name->base_name, &pacl, + &retryPosix); + if (retryPosix) + { + TALLOC_FREE(frame); + DEBUG(10, ("retrying with posix acl...\n")); + return posix_fget_nt_acl(fsp, security_info, + mem_ctx, ppdesc); + } + if (result==False) { + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + + status = smb_fget_nt_acl_nfs4( + fsp, NULL, security_info, mem_ctx, ppdesc, pacl); + TALLOC_FREE(frame); + return status; +} + +static int aixjfs2_sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) +{ + struct SMB4ACL_T *pacl = NULL; + bool result; + bool retryPosix = False; + + result = aixjfs2_get_nfs4_acl(mem_ctx, fsp->fsp_name->base_name, &pacl, + &retryPosix); + if (retryPosix) + { + return posix_sys_acl_blob_get_fd(handle, fsp, mem_ctx, blob_description, blob); + } + + /* Now way to linarlise NFS4 ACLs at the moment, but the NT ACL is pretty close in this case */ + errno = ENOSYS; + return -1; +} + +static SMB_ACL_T aixjfs2_get_posix_acl(const char *path, acl_type_t type, TALLOC_CTX *mem_ctx) +{ + aixc_acl_t *pacl; + AIXJFS2_ACL_T *acl; + SMB_ACL_T result = NULL; + int ret; + + acl = aixjfs2_getacl_alloc(path, &type); + + if (acl == NULL) { + DEBUG(10, ("aixjfs2_getacl failed for %s with %s\n", + path, strerror(errno))); + if (errno == 0) { + errno = EINVAL; + } + goto done; + } + + pacl = &acl->aixc_acl[0]; + DEBUG(10, ("len: %d, mode: %d\n", + pacl->acl_len, pacl->acl_mode)); + + result = aixacl_to_smbacl(pacl, mem_ctx); + if (result == NULL) { + goto done; + } + + done: + if (errno != 0) { + TALLOC_FREE(result); + } + return result; +} + +SMB_ACL_T aixjfs2_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + acl_type_t aixjfs2_type; + + switch(type) { + case SMB_ACL_TYPE_ACCESS: + aixjfs2_type.u64 = ACL_AIXC; + break; + case SMB_ACL_TYPE_DEFAULT: + DEBUG(0, ("Got AIX JFS2 unsupported type: %d\n", type)); + return NULL; + default: + DEBUG(0, ("Got invalid type: %d\n", type)); + smb_panic("exiting"); + } + + return aixjfs2_get_posix_acl(fsp->fsp_name->base_name, + aixjfs2_type, mem_ctx); +} + +/* + * Test whether we have that aclType support on the given path + */ +static int aixjfs2_query_acl_support( + char *filepath, + uint64_t aclType, + acl_type_t *pacl_type_info +) +{ + acl_types_list_t acl_type_list; + size_t acl_type_list_len = sizeof(acl_types_list_t); + uint32_t i; + + memset(&acl_type_list, 0, sizeof(acl_type_list)); + + if (aclx_gettypes(filepath, &acl_type_list, &acl_type_list_len)) { + DEBUG(2, ("aclx_gettypes failed with error %s for %s\n", + strerror(errno), filepath)); + return -1; + } + + for(i=0; iacl_type, filepath)); + return 0; + } + } + + return 1; /* haven't found that ACL type. */ +} + +static bool aixjfs2_process_smbacl(vfs_handle_struct *handle, + files_struct *fsp, + struct SMB4ACL_T *smbacl) +{ + struct SMB4ACE_T *smbace; + TALLOC_CTX *mem_ctx; + nfs4_acl_int_t *jfs2acl; + int32_t entryLen; + uint32_t aclLen, naces; + int rc; + acl_type_t acltype; + + DEBUG(10, ("jfs2_process_smbacl invoked on %s\n", fsp_str_dbg(fsp))); + + /* no need to be freed which is alloced with mem_ctx */ + mem_ctx = talloc_tos(); + + entryLen = sizeof(nfs4_ace_int_t); + if (entryLen & 0x03) + entryLen = entryLen + 4 - (entryLen%4); + + naces = smb_get_naces(smbacl); + aclLen = ACL_V4_SIZ + naces * entryLen; + jfs2acl = (nfs4_acl_int_t *)TALLOC_SIZE(mem_ctx, aclLen); + if (jfs2acl==NULL) { + DEBUG(0, ("TALLOC_SIZE failed\n")); + errno = ENOMEM; + return False; + } + + jfs2acl->aclLength = ACL_V4_SIZ; + jfs2acl->aclVersion = NFS4_ACL_INT_STRUCT_VERSION; + jfs2acl->aclEntryN = 0; + + for(smbace = smb_first_ace4(smbacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) + { + SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace); + nfs4_ace_int_t *jfs2_ace = (nfs4_ace_int_t *)(((char *)jfs2acl) + jfs2acl->aclLength); + + memset(jfs2_ace, 0, entryLen); + jfs2_ace->entryLen = entryLen; /* won't store textual "who" */ + jfs2_ace->aceType = aceprop->aceType; /* only ACCESS|DENY supported by jfs2 */ + jfs2_ace->aceFlags = aceprop->aceFlags; + jfs2_ace->aceMask = aceprop->aceMask; + jfs2_ace->flags = (aceprop->flags&SMB_ACE4_ID_SPECIAL) ? ACE4_ID_SPECIAL : 0; + + /* don't care it's real content is only 16 or 32 bit */ + jfs2_ace->aceWho.id = aceprop->who.id; + + /* iterate to the next jfs2 ace */ + jfs2acl->aclLength += jfs2_ace->entryLen; + jfs2acl->aclEntryN++; + } + SMB_ASSERT(jfs2acl->aclEntryN==naces); + + /* Don't query it (again) */ + memset(&acltype, 0, sizeof(acl_type_t)); + acltype.u64 = ACL_NFS4; + + /* won't set S_ISUID - the only one JFS2/NFS4 accepts */ + rc = aclx_put( + fsp->fsp_name->base_name, + SET_ACL, /* set only the ACL, not mode bits */ + acltype, /* not a pointer !!! */ + jfs2acl, + jfs2acl->aclLength, + 0 /* don't set here mode bits */ + ); + if (rc) { + DEBUG(8, ("aclx_put failed with %s\n", strerror(errno))); + return False; + } + + DEBUG(10, ("jfs2_process_smbacl succeeded.\n")); + return True; +} + +static NTSTATUS aixjfs2_set_nt_acl_common(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) +{ + acl_type_t acl_type_info; + NTSTATUS result = NT_STATUS_ACCESS_DENIED; + int rc; + + rc = aixjfs2_query_acl_support( + fsp->fsp_name->base_name, + ACL_NFS4, + &acl_type_info); + + if (rc==0) + { + result = smb_set_nt_acl_nfs4(handle, + fsp, NULL, security_info_sent, psd, + aixjfs2_process_smbacl); + } else if (rc==1) { /* assume POSIX ACL - by default... */ + result = set_nt_acl(fsp, security_info_sent, psd); + } else + result = map_nt_error_from_unix(errno); /* query failed */ + + return result; +} + +NTSTATUS aixjfs2_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) +{ + return aixjfs2_set_nt_acl_common(handle, fsp, security_info_sent, psd); +} + +int aixjfs2_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct acl *acl_aixc; + acl_type_t acl_type_info; + int rc; + + DEBUG(10, ("aixjfs2_sys_acl_set_fd invoked for %s\n", fsp_str_dbg(fsp))); + + rc = aixjfs2_query_acl_support(fsp->fsp_name->base_name, ACL_AIXC, + &acl_type_info); + if (rc) { + DEBUG(8, ("jfs2_set_nt_acl: AIXC support not found\n")); + return -1; + } + + acl_aixc = aixacl_smb_to_aixacl(type, theacl); + if (!acl_aixc) + return -1; + + if (fsp->fsp_flags.is_pathref) { + /* + * This is no longer a handle based call. + */ + return aclx_put(fsp->fsp_name->base_name, + SET_ACL, + acl_type_info, + acl_aixc, + acl_aixc->acl_len, + 0); + } + + rc = aclx_fput( + fsp_get_io_fd(fsp), + SET_ACL, /* set only the ACL, not mode bits */ + acl_type_info, + acl_aixc, + acl_aixc->acl_len, + 0 + ); + if (rc) { + DEBUG(2, ("aclx_fput failed with %s for %s\n", + strerror(errno), fsp_str_dbg(fsp))); + return -1; + } + + return 0; +} + +int aixjfs2_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + /* Not available under AIXC ACL */ + /* Don't report here any error otherwise */ + /* upper layer will break the normal execution */ + return 0; +} + +static struct vfs_fn_pointers vfs_aixacl2_fns = { + .stat_fn = nfs4_acl_stat, + .fstat_fn = nfs4_acl_fstat, + .lstat_fn = nfs4_acl_lstat, + .fstatat_fn = nfs4_acl_fstatat, + .fget_nt_acl_fn = aixjfs2_fget_nt_acl, + .fset_nt_acl_fn = aixjfs2_fset_nt_acl, + .sys_acl_get_fd_fn = aixjfs2_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = aixjfs2_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = aixjfs2_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = aixjfs2_sys_acl_delete_def_fd +}; + +static_decl_vfs; +NTSTATUS vfs_aixacl2_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, AIXACL2_MODULE_NAME, + &vfs_aixacl2_fns); +} diff --git a/source3/modules/vfs_aixacl_util.c b/source3/modules/vfs_aixacl_util.c new file mode 100644 index 0000000..38b53eb --- /dev/null +++ b/source3/modules/vfs_aixacl_util.c @@ -0,0 +1,288 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls + Copyright (C) Jim McDonough 2006 + + 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/filesys.h" +#include "smbd/smbd.h" +#include "vfs_aixacl_util.h" + +SMB_ACL_T aixacl_to_smbacl(struct acl *file_acl, TALLOC_CTX *mem_ctx) +{ + struct acl_entry *acl_entry; + struct ace_id *idp; + + struct smb_acl_t *result = sys_acl_init(mem_ctx); + struct smb_acl_entry *ace; + int i; + + if (result == NULL) { + return NULL; + } + + /* Point to the first acl entry in the acl */ + acl_entry = file_acl->acl_ext; + + DEBUG(10,("acl_entry is %p\n",(void *)acl_entry)); + DEBUG(10,("acl_last(file_acl) id %p\n",(void *)acl_last(file_acl))); + + /* Check if the extended acl bit is on. * + * If it isn't, do not show the * + * contents of the acl since AIX intends * + * the extended info to remain unused */ + + if(file_acl->acl_mode & S_IXACL){ + /* while we are not pointing to the very end */ + while(acl_entry < acl_last(file_acl)) { + /* before we malloc anything, make sure this is */ + /* a valid acl entry and one that we want to map */ + idp = id_nxt(acl_entry->ace_id); + if((acl_entry->ace_type == ACC_SPECIFY || + (acl_entry->ace_type == ACC_PERMIT)) && (idp != id_last(acl_entry))) { + acl_entry = acl_nxt(acl_entry); + continue; + } + + idp = acl_entry->ace_id; + DEBUG(10,("idp->id_data is %d\n",idp->id_data[0])); + + result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count+1); + if (result == NULL) { + DEBUG(0, ("talloc_realloc failed\n")); + errno = ENOMEM; + return NULL; + } + + DEBUG(10,("idp->id_type is %d\n",idp->id_type)); + ace = &result->acl[result->count]; + + ace->a_type = idp->id_type; + + switch(ace->a_type) { + case ACEID_USER: { + ace->info.user.uid = idp->id_data[0]; + DEBUG(10,("case ACEID_USER ace->info.user.uid is %d\n",ace->info.user.uid)); + ace->a_type = SMB_ACL_USER; + break; + } + + case ACEID_GROUP: { + ace->info.group.gid = idp->id_data[0]; + DEBUG(10,("case ACEID_GROUP ace->info.group.gid is %d\n",ace->info.group.gid)); + ace->a_type = SMB_ACL_GROUP; + break; + } + default: + break; + } + /* The access in the acl entries must be left shifted by * + * three bites, because they will ultimately be compared * + * to S_IRUSR, S_IWUSR, and S_IXUSR. */ + + switch(acl_entry->ace_type){ + case ACC_PERMIT: + case ACC_SPECIFY: + ace->a_perm = acl_entry->ace_access; + ace->a_perm <<= 6; + DEBUG(10,("ace->a_perm is %d\n",ace->a_perm)); + break; + case ACC_DENY: + /* Since there is no way to return a DENY acl entry * + * change to PERMIT and then shift. */ + DEBUG(10,("acl_entry->ace_access is %d\n",acl_entry->ace_access)); + ace->a_perm = ~acl_entry->ace_access & 7; + DEBUG(10,("ace->a_perm is %d\n",ace->a_perm)); + ace->a_perm <<= 6; + break; + default: + DEBUG(0, ("unknown ace->type\n")); + TALLOC_FREE(result); + return(0); + } + + result->count++; + ace->a_perm |= (ace->a_perm & S_IRUSR) ? SMB_ACL_READ : 0; + ace->a_perm |= (ace->a_perm & S_IWUSR) ? SMB_ACL_WRITE : 0; + ace->a_perm |= (ace->a_perm & S_IXUSR) ? SMB_ACL_EXECUTE : 0; + DEBUG(10,("ace->a_perm is %d\n",ace->a_perm)); + + DEBUG(10,("acl_entry = %p\n",(void *)acl_entry)); + DEBUG(10,("The ace_type is %d\n",acl_entry->ace_type)); + + acl_entry = acl_nxt(acl_entry); + } + } /* end of if enabled */ + + /* Since owner, group, other acl entries are not * + * part of the acl entries in an acl, they must * + * be dummied up to become part of the list. */ + + for( i = 1; i < 4; i++) { + DEBUG(10,("i is %d\n",i)); + + result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count+1); + if (result->acl == NULL) { + TALLOC_FREE(result); + DEBUG(0, ("talloc_realloc failed\n")); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return NULL; + } + + ace = &result->acl[result->count]; + + ace->info.user.uid = 0; + ace->info.group.gid = 0; + DEBUG(10,("ace->info.user.uid = %d\n",ace->info.user.uid)); + + switch(i) { + case 2: + ace->a_perm = file_acl->g_access << 6; + ace->a_type = SMB_ACL_GROUP_OBJ; + break; + + case 3: + ace->a_perm = file_acl->o_access << 6; + ace->a_type = SMB_ACL_OTHER; + break; + + case 1: + ace->a_perm = file_acl->u_access << 6; + ace->a_type = SMB_ACL_USER_OBJ; + break; + + default: + return(NULL); + + } + ace->a_perm |= ((ace->a_perm & S_IRUSR) ? SMB_ACL_READ : 0); + ace->a_perm |= ((ace->a_perm & S_IWUSR) ? SMB_ACL_WRITE : 0); + ace->a_perm |= ((ace->a_perm & S_IXUSR) ? SMB_ACL_EXECUTE : 0); + + memcpy(&result->acl[result->count],ace,sizeof(struct smb_acl_entry)); + result->count++; + DEBUG(10,("ace->a_perm = %d\n",ace->a_perm)); + DEBUG(10,("ace->a_type = %d\n",ace->a_type)); + } + + return result; +} + +static ushort aixacl_smb_to_aixperm(SMB_ACL_PERM_T a_perm) +{ + ushort ret = (ushort)0; + if (a_perm & SMB_ACL_READ) + ret |= R_ACC; + if (a_perm & SMB_ACL_WRITE) + ret |= W_ACC; + if (a_perm & SMB_ACL_EXECUTE) + ret |= X_ACC; + return ret; +} + +struct acl *aixacl_smb_to_aixacl(SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) +{ + struct smb_acl_entry *smb_entry = NULL; + struct acl *file_acl = NULL; + struct acl *file_acl_temp = NULL; + struct acl_entry *acl_entry = NULL; + struct ace_id *ace_id = NULL; + unsigned int id_type; + unsigned int acl_length; + int i; + + DEBUG(10,("Entering aixacl_smb_to_aixacl\n")); + /* AIX has no default ACL */ + if(acltype == SMB_ACL_TYPE_DEFAULT) + return NULL; + + acl_length = BUFSIZ; + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + if(file_acl == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in aixacl_smb_to_aixacl is %d\n",errno)); + return NULL; + } + + memset(file_acl,0,BUFSIZ); + + file_acl->acl_len = ACL_SIZ; + file_acl->acl_mode = S_IXACL; + + for(i=0; icount; i++ ) { + smb_entry = &(theacl->acl[i]); + id_type = smb_entry->a_type; + DEBUG(10,("The id_type is %d\n",id_type)); + + switch(id_type) { + case SMB_ACL_USER_OBJ: + file_acl->u_access = aixacl_smb_to_aixperm(smb_entry->a_perm); + continue; + case SMB_ACL_GROUP_OBJ: + file_acl->g_access = aixacl_smb_to_aixperm(smb_entry->a_perm); + continue; + case SMB_ACL_OTHER: + file_acl->o_access = aixacl_smb_to_aixperm(smb_entry->a_perm); + continue; + case SMB_ACL_MASK: + continue; + case SMB_ACL_GROUP: + break; /* process this */ + case SMB_ACL_USER: + break; /* process this */ + default: /* abnormal case */ + DEBUG(10,("The id_type is unknown !\n")); + continue; + } + + if((file_acl->acl_len + sizeof(struct acl_entry)) > acl_length) { + acl_length += sizeof(struct acl_entry); + file_acl_temp = (struct acl *)SMB_MALLOC(acl_length); + if(file_acl_temp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in aixacl_smb_to_aixacl is %d\n",errno)); + return NULL; + } + + memcpy(file_acl_temp,file_acl,file_acl->acl_len); + SAFE_FREE(file_acl); + file_acl = file_acl_temp; + } + + acl_entry = (struct acl_entry *)((char *)file_acl + file_acl->acl_len); + file_acl->acl_len += sizeof(struct acl_entry); + acl_entry->ace_len = sizeof(struct acl_entry); /* contains 1 ace_id */ + acl_entry->ace_access = aixacl_smb_to_aixperm(smb_entry->a_perm); + + /* In order to use this, we'll need to wait until we can get denies */ + /* if(!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT) + acl_entry->ace_type = ACC_SPECIFY; */ + + acl_entry->ace_type = ACC_SPECIFY; + + ace_id = acl_entry->ace_id; + + ace_id->id_type = (smb_entry->a_type==SMB_ACL_GROUP) ? ACEID_GROUP : ACEID_USER; + DEBUG(10,("The id type is %d\n",ace_id->id_type)); + ace_id->id_len = sizeof(struct ace_id); /* contains 1 id_data */ + ace_id->id_data[0] = (smb_entry->a_type==SMB_ACL_GROUP) ? smb_entry->info.group.gid : smb_entry->info.user.uid; + } + + return file_acl; +} diff --git a/source3/modules/vfs_aixacl_util.h b/source3/modules/vfs_aixacl_util.h new file mode 100644 index 0000000..e283a3d --- /dev/null +++ b/source3/modules/vfs_aixacl_util.h @@ -0,0 +1,22 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls + Copyright (C) Jim McDonough 2006 + + 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 . +*/ + +SMB_ACL_T aixacl_to_smbacl( struct acl *file_acl, TALLOC_CTX *mem_ctx); +struct acl *aixacl_smb_to_aixacl(SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl); + diff --git a/source3/modules/vfs_audit.c b/source3/modules/vfs_audit.c new file mode 100644 index 0000000..2b01a6a --- /dev/null +++ b/source3/modules/vfs_audit.c @@ -0,0 +1,355 @@ +/* + * Auditing VFS module for samba. Log selected file operations to syslog + * facility. + * + * Copyright (C) Tim Potter 1999-2000 + * Copyright (C) Alexander Bokovoy 2002 + * Copyright (C) Stefan (metze) Metzmacher 2002 + * + * 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/filesys.h" +#include "system/syslog.h" +#include "smbd/smbd.h" +#include "lib/param/loadparm.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static int audit_syslog_facility(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_facilities[] = { +#ifdef LOG_AUTH + { LOG_AUTH, "AUTH" }, +#endif +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "AUTHPRIV" }, +#endif +#ifdef LOG_AUDIT + { LOG_AUDIT, "AUDIT" }, +#endif +#ifdef LOG_CONSOLE + { LOG_CONSOLE, "CONSOLE" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "CRON" }, +#endif +#ifdef LOG_DAEMON + { LOG_DAEMON, "DAEMON" }, +#endif +#ifdef LOG_FTP + { LOG_FTP, "FTP" }, +#endif +#ifdef LOG_INSTALL + { LOG_INSTALL, "INSTALL" }, +#endif +#ifdef LOG_KERN + { LOG_KERN, "KERN" }, +#endif +#ifdef LOG_LAUNCHD + { LOG_LAUNCHD, "LAUNCHD" }, +#endif +#ifdef LOG_LFMT + { LOG_LFMT, "LFMT" }, +#endif +#ifdef LOG_LPR + { LOG_LPR, "LPR" }, +#endif +#ifdef LOG_MAIL + { LOG_MAIL, "MAIL" }, +#endif +#ifdef LOG_MEGASAFE + { LOG_MEGASAFE, "MEGASAFE" }, +#endif +#ifdef LOG_NETINFO + { LOG_NETINFO, "NETINFO" }, +#endif +#ifdef LOG_NEWS + { LOG_NEWS, "NEWS" }, +#endif +#ifdef LOG_NFACILITIES + { LOG_NFACILITIES, "NFACILITIES" }, +#endif +#ifdef LOG_NTP + { LOG_NTP, "NTP" }, +#endif +#ifdef LOG_RAS + { LOG_RAS, "RAS" }, +#endif +#ifdef LOG_REMOTEAUTH + { LOG_REMOTEAUTH, "REMOTEAUTH" }, +#endif +#ifdef LOG_SECURITY + { LOG_SECURITY, "SECURITY" }, +#endif +#ifdef LOG_SYSLOG + { LOG_SYSLOG, "SYSLOG" }, +#endif +#ifdef LOG_USER + { LOG_USER, "USER" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "UUCP" }, +#endif + { LOG_LOCAL0, "LOCAL0" }, + { LOG_LOCAL1, "LOCAL1" }, + { LOG_LOCAL2, "LOCAL2" }, + { LOG_LOCAL3, "LOCAL3" }, + { LOG_LOCAL4, "LOCAL4" }, + { LOG_LOCAL5, "LOCAL5" }, + { LOG_LOCAL6, "LOCAL6" }, + { LOG_LOCAL7, "LOCAL7" }, + { -1, NULL } + }; + + int facility; + + facility = lp_parm_enum(SNUM(handle->conn), "audit", "facility", enum_log_facilities, LOG_USER); + + return facility; +} + + +static int audit_syslog_priority(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_priorities[] = { + { LOG_EMERG, "EMERG" }, + { LOG_ALERT, "ALERT" }, + { LOG_CRIT, "CRIT" }, + { LOG_ERR, "ERR" }, + { LOG_WARNING, "WARNING" }, + { LOG_NOTICE, "NOTICE" }, + { LOG_INFO, "INFO" }, + { LOG_DEBUG, "DEBUG" }, + { -1, NULL } + }; + + int priority; + + priority = lp_parm_enum(SNUM(handle->conn), "audit", "priority", + enum_log_priorities, LOG_NOTICE); + if (priority == -1) { + priority = LOG_WARNING; + } + + return priority; +} + +/* Implementation of vfs_ops. Pass everything on to the default + operation but log event first. */ + +static int audit_connect(vfs_handle_struct *handle, const char *svc, const char *user) +{ + int result; + + result = SMB_VFS_NEXT_CONNECT(handle, svc, user); + if (result < 0) { + return result; + } + + openlog("smbd_audit", LOG_PID, audit_syslog_facility(handle)); + + syslog(audit_syslog_priority(handle), "connect to service %s by user %s\n", + svc, user); + + return 0; +} + +static void audit_disconnect(vfs_handle_struct *handle) +{ + syslog(audit_syslog_priority(handle), "disconnected\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + + return; +} + +static int audit_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + + syslog(audit_syslog_priority(handle), "mkdirat %s %s%s\n", + full_fname->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + TALLOC_FREE(full_fname); + return result; +} + +static int audit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + int result; + + result = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + + syslog(audit_syslog_priority(handle), + "openat %s (fd %d) %s%s%s\n", + fsp_str_dbg(fsp), result, + ((how->flags & O_WRONLY) || (how->flags & O_RDWR)) ? + "for writing " : "", + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + return result; +} + +static int audit_close(vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + + result = SMB_VFS_NEXT_CLOSE(handle, fsp); + + syslog(audit_syslog_priority(handle), "close fd %d %s%s\n", + fsp_get_pathref_fd(fsp), + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + return result; +} + +static int audit_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + int result; + int saved_errno = 0; + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + return -1; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return -1; + } + result = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (result == -1) { + saved_errno = errno; + } + + syslog(audit_syslog_priority(handle), "renameat %s -> %s %s%s\n", + full_fname_src->base_name, + full_fname_dst->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + + if (saved_errno != 0) { + errno = saved_errno; + } + + return result; +} + +static int audit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + + syslog(audit_syslog_priority(handle), "unlinkat %s %s%s\n", + full_fname->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + TALLOC_FREE(full_fname); + return result; +} + +static int audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) +{ + int result; + + result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + + syslog(audit_syslog_priority(handle), "fchmod %s mode 0x%x %s%s\n", + fsp->fsp_name->base_name, mode, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + return result; +} + +static struct vfs_fn_pointers vfs_audit_fns = { + .connect_fn = audit_connect, + .disconnect_fn = audit_disconnect, + .mkdirat_fn = audit_mkdirat, + .openat_fn = audit_openat, + .close_fn = audit_close, + .renameat_fn = audit_renameat, + .unlinkat_fn = audit_unlinkat, + .fchmod_fn = audit_fchmod, +}; + +static_decl_vfs; +NTSTATUS vfs_audit_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "audit", + &vfs_audit_fns); +} diff --git a/source3/modules/vfs_btrfs.c b/source3/modules/vfs_btrfs.c new file mode 100644 index 0000000..9031252 --- /dev/null +++ b/source3/modules/vfs_btrfs.c @@ -0,0 +1,887 @@ +/* + * Module to make use of awesome Btrfs features + * + * Copyright (C) David Disseldorp 2011-2013 + * + * 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/filesys.h" +#include +#include +#include +#include +#include +#include +#include +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "librpc/gen_ndr/smbXsrv.h" +#include "librpc/gen_ndr/ioctl.h" +#include "lib/util/tevent_ntstatus.h" +#include "offload_token.h" + +static uint32_t btrfs_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *_ts_res) +{ + uint32_t fs_capabilities; + enum timestamp_set_resolution ts_res; + + /* inherit default capabilities, expose compression support */ + fs_capabilities = SMB_VFS_NEXT_FS_CAPABILITIES(handle, &ts_res); + fs_capabilities |= (FILE_FILE_COMPRESSION + | FILE_SUPPORTS_BLOCK_REFCOUNTING); + *_ts_res = ts_res; + + return fs_capabilities; +} + +#define SHADOW_COPY_PREFIX "@GMT-" /* vfs_shadow_copy format */ +#define SHADOW_COPY_PATH_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S" + +#define BTRFS_SUBVOL_RDONLY (1ULL << 1) +#define BTRFS_SUBVOL_NAME_MAX 4039 +#define BTRFS_PATH_NAME_MAX 4087 +struct btrfs_ioctl_vol_args_v2 { + int64_t fd; + uint64_t transid; + uint64_t flags; + uint64_t unused[4]; + char name[BTRFS_SUBVOL_NAME_MAX + 1]; +}; +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; + +struct btrfs_ioctl_clone_range_args { + int64_t src_fd; + uint64_t src_offset; + uint64_t src_length; + uint64_t dest_offset; +}; + +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ + struct btrfs_ioctl_clone_range_args) +#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \ + struct btrfs_ioctl_vol_args) +#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \ + struct btrfs_ioctl_vol_args_v2) + +static struct vfs_offload_ctx *btrfs_offload_ctx; + +struct btrfs_offload_read_state { + struct vfs_handle_struct *handle; + files_struct *fsp; + uint32_t flags; + uint64_t xferlen; + DATA_BLOB token; +}; + +static void btrfs_offload_read_done(struct tevent_req *subreq); + +static struct tevent_req *btrfs_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct btrfs_offload_read_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct btrfs_offload_read_state); + if (req == NULL) { + return NULL; + } + *state = (struct btrfs_offload_read_state) { + .handle = handle, + .fsp = fsp, + }; + + status = vfs_offload_token_ctx_init(fsp->conn->sconn->client, + &btrfs_offload_ctx); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) { + status = vfs_offload_token_create_blob(state, fsp, fsctl, + &state->token); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx, fsp, + &state->token); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp, + fsctl, ttl, offset, to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, btrfs_offload_read_done, req); + return req; +} + +static void btrfs_offload_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct btrfs_offload_read_state *state = tevent_req_data( + req, struct btrfs_offload_read_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq, + state->handle, + state, + &state->flags, + &state->xferlen, + &state->token); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx, + state->fsp, + &state->token); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; +} + +static NTSTATUS btrfs_offload_read_recv(struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *token) +{ + struct btrfs_offload_read_state *state = tevent_req_data( + req, struct btrfs_offload_read_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *flags = state->flags; + *xferlen = state->xferlen; + token->length = state->token.length; + token->data = talloc_move(mem_ctx, &state->token.data); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct btrfs_offload_write_state { + struct vfs_handle_struct *handle; + off_t copied; + bool need_unbecome_user; +}; + +static void btrfs_offload_write_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct btrfs_offload_write_state *state = + tevent_req_data(req, + struct btrfs_offload_write_state); + bool ok; + + if (!state->need_unbecome_user) { + return; + } + + ok = unbecome_user_without_service(); + SMB_ASSERT(ok); + state->need_unbecome_user = false; +} + +static void btrfs_offload_write_done(struct tevent_req *subreq); + +static struct tevent_req *btrfs_offload_write_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req = NULL; + struct btrfs_offload_write_state *state = NULL; + struct tevent_req *subreq = NULL; + struct btrfs_ioctl_clone_range_args cr_args; + struct lock_struct src_lck; + struct lock_struct dest_lck; + off_t src_off = transfer_offset; + files_struct *src_fsp = NULL; + int ret; + bool handle_offload_write = true; + bool do_locking = false; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct btrfs_offload_write_state); + if (req == NULL) { + return NULL; + } + + state->handle = handle; + + tevent_req_set_cleanup_fn(req, btrfs_offload_write_cleanup); + + status = vfs_offload_token_db_fetch_fsp(btrfs_offload_ctx, + token, &src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + switch (fsctl) { + case FSCTL_SRV_COPYCHUNK: + case FSCTL_SRV_COPYCHUNK_WRITE: + do_locking = true; + break; + + case FSCTL_DUP_EXTENTS_TO_FILE: + /* dup extents does not use locking */ + break; + + default: + handle_offload_write = false; + break; + } + + if (num == 0) { + /* + * With a @src_length of zero, BTRFS_IOC_CLONE_RANGE clones + * all data from @src_offset->EOF! This is certainly not what + * the caller expects, and not what vfs_default does. + */ + handle_offload_write = false; + } + + if (!handle_offload_write) { + subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, + state, + ev, + fsctl, + token, + transfer_offset, + dest_fsp, + dest_off, + num); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + btrfs_offload_write_done, + req); + return req; + } + + status = vfs_offload_token_check_handles( + fsctl, src_fsp, dest_fsp); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + ok = become_user_without_service_by_fsp(src_fsp); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return tevent_req_post(req, ev); + } + state->need_unbecome_user = true; + + status = vfs_stat_fsp(src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (src_fsp->fsp_name->st.st_ex_size < src_off + num) { + /* [MS-SMB2] Handling a Server-Side Data Copy Request */ + tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE); + return tevent_req_post(req, ev); + } + + if (do_locking) { + init_strict_lock_struct(src_fsp, + src_fsp->op->global->open_persistent_id, + src_off, + num, + READ_LOCK, + lp_posix_cifsu_locktype(src_fsp), + &src_lck); + if (!SMB_VFS_STRICT_LOCK_CHECK(src_fsp->conn, src_fsp, &src_lck)) { + tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT); + return tevent_req_post(req, ev); + } + } + + ok = unbecome_user_without_service(); + SMB_ASSERT(ok); + state->need_unbecome_user = false; + + if (do_locking) { + init_strict_lock_struct(dest_fsp, + dest_fsp->op->global->open_persistent_id, + dest_off, + num, + WRITE_LOCK, + lp_posix_cifsu_locktype(dest_fsp), + &dest_lck); + + if (!SMB_VFS_STRICT_LOCK_CHECK(dest_fsp->conn, dest_fsp, &dest_lck)) { + tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT); + return tevent_req_post(req, ev); + } + } + + ZERO_STRUCT(cr_args); + cr_args.src_fd = fsp_get_io_fd(src_fsp); + cr_args.src_offset = (uint64_t)src_off; + cr_args.dest_offset = (uint64_t)dest_off; + cr_args.src_length = (uint64_t)num; + + ret = ioctl(fsp_get_io_fd(dest_fsp), BTRFS_IOC_CLONE_RANGE, &cr_args); + if (ret < 0) { + /* + * BTRFS_IOC_CLONE_RANGE only supports 'sectorsize' aligned + * cloning. Which is 4096 by default, therefore fall back to + * manual read/write on failure. + */ + DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %llu, " + "src fd: %lld off: %llu, dest fd: %d off: %llu\n", + strerror(errno), + (unsigned long long)cr_args.src_length, + (long long)cr_args.src_fd, + (unsigned long long)cr_args.src_offset, + fsp_get_io_fd(dest_fsp), + (unsigned long long)cr_args.dest_offset)); + subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, + state, + ev, + fsctl, + token, + transfer_offset, + dest_fsp, + dest_off, + num); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + /* wait for subreq completion */ + tevent_req_set_callback(subreq, + btrfs_offload_write_done, + req); + return req; + } + + DEBUG(5, ("BTRFS_IOC_CLONE_RANGE returned %d\n", ret)); + /* BTRFS_IOC_CLONE_RANGE is all or nothing */ + state->copied = num; + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +/* only used if the request is passed through to next VFS module */ +static void btrfs_offload_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct btrfs_offload_write_state *state = + tevent_req_data(req, + struct btrfs_offload_write_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle, + subreq, + &state->copied); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static NTSTATUS btrfs_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + struct btrfs_offload_write_state *state = + tevent_req_data(req, + struct btrfs_offload_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(4, ("server side copy chunk failed: %s\n", + nt_errstr(status))); + tevent_req_received(req); + return status; + } + + DEBUG(10, ("server side copy chunk copied %llu\n", + (unsigned long long)state->copied)); + *copied = state->copied; + tevent_req_received(req); + return NT_STATUS_OK; +} + +static NTSTATUS btrfs_fget_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + struct sys_proc_fd_path_buf buf; + int ret; + long flags = 0; + int fsp_fd = fsp_get_pathref_fd(fsp); + int fd = -1; + NTSTATUS status; + + if (!fsp->fsp_flags.is_pathref) { + ret = ioctl(fsp_fd, FS_IOC_GETFLAGS, &flags); + if (ret < 0) { + DBG_WARNING("FS_IOC_GETFLAGS failed: %s, fd %lld\n", + strerror(errno), (long long)fd); + return map_nt_error_from_unix(errno); + } + if (flags & FS_COMPR_FL) { + *_compression_fmt = COMPRESSION_FORMAT_LZNT1; + } else { + *_compression_fmt = COMPRESSION_FORMAT_NONE; + } + return NT_STATUS_OK; + } + + if (!fsp->fsp_flags.have_proc_fds) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + fd = open(sys_proc_fd_path(fsp_fd, &buf), O_RDONLY); + if (fd == -1) { + DBG_DEBUG("/proc open of %s failed: %s\n", + buf.buf, + strerror(errno)); + return map_nt_error_from_unix(errno); + } + + ret = ioctl(fd, FS_IOC_GETFLAGS, &flags); + if (ret < 0) { + DEBUG(1, ("FS_IOC_GETFLAGS failed: %s, fd %lld\n", + strerror(errno), (long long)fd)); + status = map_nt_error_from_unix(errno); + goto err_close; + } + if (flags & FS_COMPR_FL) { + *_compression_fmt = COMPRESSION_FORMAT_LZNT1; + } else { + *_compression_fmt = COMPRESSION_FORMAT_NONE; + } + status = NT_STATUS_OK; + +err_close: + if (fd != -1) { + close(fd); + } + + return status; +} + +static NTSTATUS btrfs_set_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + int ret; + long flags = 0; + int fd; + NTSTATUS status; + + if ((fsp == NULL) || (fsp_get_io_fd(fsp) == -1)) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_out; + } + fd = fsp_get_io_fd(fsp); + + ret = ioctl(fd, FS_IOC_GETFLAGS, &flags); + if (ret < 0) { + DEBUG(1, ("FS_IOC_GETFLAGS failed: %s, fd %d\n", + strerror(errno), fd)); + status = map_nt_error_from_unix(errno); + goto err_out; + } + + if (compression_fmt == COMPRESSION_FORMAT_NONE) { + DEBUG(5, ("setting compression\n")); + flags &= (~FS_COMPR_FL); + } else if ((compression_fmt == COMPRESSION_FORMAT_DEFAULT) + || (compression_fmt == COMPRESSION_FORMAT_LZNT1)) { + DEBUG(5, ("clearing compression\n")); + flags |= FS_COMPR_FL; + } else { + DEBUG(1, ("invalid compression format 0x%x\n", + (int)compression_fmt)); + status = NT_STATUS_INVALID_PARAMETER; + goto err_out; + } + + ret = ioctl(fd, FS_IOC_SETFLAGS, &flags); + if (ret < 0) { + DEBUG(1, ("FS_IOC_SETFLAGS failed: %s, fd %d\n", + strerror(errno), fd)); + status = map_nt_error_from_unix(errno); + goto err_out; + } + status = NT_STATUS_OK; +err_out: + return status; +} + +/* + * Check whether a path can be shadow copied. Return the base volume, allowing + * the caller to determine if multiple paths lie on the same base volume. + */ +#define BTRFS_INODE_SUBVOL 256 +static NTSTATUS btrfs_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + struct stat st; + char *base; + + if (!lp_parm_bool(SNUM(handle->conn), + "btrfs", "manipulate snapshots", false)) { + DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n")); + return SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, + service_path, base_volume); + } + + /* btrfs userspace uses this logic to confirm subvolume */ + if (stat(service_path, &st) < 0) { + return NT_STATUS_NOT_SUPPORTED; + } + if ((st.st_ino != BTRFS_INODE_SUBVOL) || !S_ISDIR(st.st_mode)) { + DEBUG(0, ("%s not a btrfs subvolume, snapshots not available\n", + service_path)); + return NT_STATUS_NOT_SUPPORTED; + } + + /* we "snapshot" the service path itself */ + base = talloc_strdup(mem_ctx, service_path); + if (base == NULL) { + return NT_STATUS_NO_MEMORY; + } + *base_volume = base; + + return NT_STATUS_OK; +} + +static NTSTATUS btrfs_gen_snap_dest_path(TALLOC_CTX *mem_ctx, + const char *src_path, + time_t *tstamp, + char **dest_path, char **subvolume) +{ + struct tm t_gmt; + char time_str[50]; + size_t tlen; + + gmtime_r(tstamp, &t_gmt); + + tlen = strftime(time_str, ARRAY_SIZE(time_str), + SHADOW_COPY_PATH_FORMAT, &t_gmt); + if (tlen <= 0) { + return NT_STATUS_UNSUCCESSFUL; + } + + *dest_path = talloc_strdup(mem_ctx, src_path); + *subvolume = talloc_strdup(mem_ctx, time_str); + if ((*dest_path == NULL) || (*subvolume == NULL)) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS btrfs_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **_base_path, + char **_snap_path) +{ + struct btrfs_ioctl_vol_args_v2 ioctl_arg; + DIR *src_dir; + DIR *dest_dir; + int src_fd; + int dest_fd; + char *dest_path = NULL; + char *dest_subvolume = NULL; + int ret; + NTSTATUS status; + char *base_path; + char *snap_path; + TALLOC_CTX *tmp_ctx; + int saved_errno; + size_t len; + + if (!lp_parm_bool(SNUM(handle->conn), + "btrfs", "manipulate snapshots", false)) { + DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n")); + return SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, + tstamp, rw, _base_path, + _snap_path); + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + base_path = talloc_strdup(tmp_ctx, base_volume); + if (base_path == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = btrfs_gen_snap_dest_path(tmp_ctx, base_volume, tstamp, + &dest_path, &dest_subvolume); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + + snap_path = talloc_asprintf(tmp_ctx, "%s/%s", dest_path, + dest_subvolume); + if (snap_path == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + src_dir = opendir(base_volume); + if (src_dir == NULL) { + DEBUG(0, ("snap src %s open failed: %s\n", + base_volume, strerror(errno))); + status = map_nt_error_from_unix(errno); + talloc_free(tmp_ctx); + return status; + } + src_fd = dirfd(src_dir); + if (src_fd < 0) { + status = map_nt_error_from_unix(errno); + closedir(src_dir); + talloc_free(tmp_ctx); + return status; + } + + dest_dir = opendir(dest_path); + if (dest_dir == NULL) { + DEBUG(0, ("snap dest %s open failed: %s\n", + dest_path, strerror(errno))); + status = map_nt_error_from_unix(errno); + closedir(src_dir); + talloc_free(tmp_ctx); + return status; + } + dest_fd = dirfd(dest_dir); + if (dest_fd < 0) { + status = map_nt_error_from_unix(errno); + closedir(src_dir); + closedir(dest_dir); + talloc_free(tmp_ctx); + return status; + } + + /* avoid zeroing the entire struct here, name is 4k */ + ioctl_arg.fd = src_fd; + ioctl_arg.transid = 0; + ioctl_arg.flags = (rw == false) ? BTRFS_SUBVOL_RDONLY : 0; + memset(ioctl_arg.unused, 0, sizeof(ioctl_arg.unused)); + len = strlcpy(ioctl_arg.name, dest_subvolume, + ARRAY_SIZE(ioctl_arg.name)); + if (len >= ARRAY_SIZE(ioctl_arg.name)) { + DEBUG(1, ("subvolume name too long for SNAP_CREATE ioctl\n")); + closedir(src_dir); + closedir(dest_dir); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + become_root(); + ret = ioctl(dest_fd, BTRFS_IOC_SNAP_CREATE_V2, &ioctl_arg); + saved_errno = errno; + unbecome_root(); + if (ret < 0) { + DEBUG(0, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 failed: %s\n", + base_volume, dest_path, dest_subvolume, + strerror(saved_errno))); + status = map_nt_error_from_unix(saved_errno); + closedir(src_dir); + closedir(dest_dir); + talloc_free(tmp_ctx); + return status; + } + DEBUG(5, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 done\n", + base_volume, dest_path, dest_subvolume)); + + *_base_path = talloc_steal(mem_ctx, base_path); + *_snap_path = talloc_steal(mem_ctx, snap_path); + closedir(src_dir); + closedir(dest_dir); + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +static NTSTATUS btrfs_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + char *tstr; + struct tm t_gmt = {}; + DIR *dest_dir; + int dest_fd; + struct btrfs_ioctl_vol_args ioctl_arg; + int ret; + NTSTATUS status; + char *dest_path; + char *subvolume; + TALLOC_CTX *tmp_ctx; + int saved_errno; + size_t len; + + if (!lp_parm_bool(SNUM(handle->conn), + "btrfs", "manipulate snapshots", false)) { + DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n")); + return SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, + base_path, snap_path); + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dest_path = talloc_strdup(tmp_ctx, snap_path); + if (dest_path == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + subvolume = talloc_strdup(tmp_ctx, snap_path); + if (subvolume == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + dest_path = dirname(dest_path); + subvolume = basename(subvolume); + + /* confirm snap_path matches creation format */ + tstr = strptime(subvolume, SHADOW_COPY_PATH_FORMAT, &t_gmt); + if ((tstr == NULL) || (*tstr != '\0')) { + DEBUG(0, ("snapshot path %s does not match creation format\n", + snap_path)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + dest_dir = opendir(dest_path); + if (dest_dir == NULL) { + DEBUG(0, ("snap destroy dest %s open failed: %s\n", + dest_path, strerror(errno))); + status = map_nt_error_from_unix(errno); + talloc_free(tmp_ctx); + return status; + } + dest_fd = dirfd(dest_dir); + if (dest_fd < 0) { + status = map_nt_error_from_unix(errno); + closedir(dest_dir); + talloc_free(tmp_ctx); + return status; + } + + ioctl_arg.fd = -1; /* not needed */ + len = strlcpy(ioctl_arg.name, subvolume, ARRAY_SIZE(ioctl_arg.name)); + if (len >= ARRAY_SIZE(ioctl_arg.name)) { + DEBUG(1, ("subvolume name too long for SNAP_DESTROY ioctl\n")); + closedir(dest_dir); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + become_root(); + ret = ioctl(dest_fd, BTRFS_IOC_SNAP_DESTROY, &ioctl_arg); + saved_errno = errno; + unbecome_root(); + if (ret < 0) { + DEBUG(0, ("%s(%s) BTRFS_IOC_SNAP_DESTROY failed: %s\n", + dest_path, subvolume, strerror(saved_errno))); + status = map_nt_error_from_unix(saved_errno); + closedir(dest_dir); + talloc_free(tmp_ctx); + return status; + } + DEBUG(5, ("%s(%s) BTRFS_IOC_SNAP_DESTROY done\n", + dest_path, subvolume)); + + closedir(dest_dir); + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +static struct vfs_fn_pointers btrfs_fns = { + .fs_capabilities_fn = btrfs_fs_capabilities, + .offload_read_send_fn = btrfs_offload_read_send, + .offload_read_recv_fn = btrfs_offload_read_recv, + .offload_write_send_fn = btrfs_offload_write_send, + .offload_write_recv_fn = btrfs_offload_write_recv, + .fget_compression_fn = btrfs_fget_compression, + .set_compression_fn = btrfs_set_compression, + .snap_check_path_fn = btrfs_snap_check_path, + .snap_create_fn = btrfs_snap_create, + .snap_delete_fn = btrfs_snap_delete, +}; + +static_decl_vfs; +NTSTATUS vfs_btrfs_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "btrfs", &btrfs_fns); +} diff --git a/source3/modules/vfs_cacheprime.c b/source3/modules/vfs_cacheprime.c new file mode 100644 index 0000000..2718700 --- /dev/null +++ b/source3/modules/vfs_cacheprime.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) James Peach 2005-2006 + * + * 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 "smbd/smbd.h" +#include "lib/util/sys_rw.h" + +/* Cache priming module. + * + * The purpose of this module is to do RAID stripe width reads to prime the + * buffer cache to do zero-copy I/O for subsequent sendfile calls. The idea is + * to do a single large read at the start of the file to make sure that most or + * all of the file is pulled into the buffer cache. Subsequent I/Os have + * reduced latency. + * + * Tunables. + * + * cacheprime:rsize Amount of readahead in bytes. This should be a + * multiple of the RAID stripe width. + * cacheprime:debug Debug level at which to emit messages. + */ + +#define READAHEAD_MIN (128 * 1024) /* min is 128 KiB */ +#define READAHEAD_MAX (100 * 1024 * 1024) /* max is 100 MiB */ + +#define MODULE "cacheprime" + +static int module_debug; +static ssize_t g_readsz = 0; +static void * g_readbuf = NULL; + +/* Prime the kernel buffer cache with data from the specified file. We use + * per-fsp data to make sure we only ever do this once. If pread is being + * emulated by seek/read/seek, when this will suck quite a lot. + */ +static bool prime_cache( + struct vfs_handle_struct * handle, + files_struct * fsp, + off_t offset, + size_t count) +{ + off_t * last; + ssize_t nread; + + last = VFS_ADD_FSP_EXTENSION(handle, fsp, off_t, NULL); + if (!last) { + return False; + } + + if (*last == -1) { + /* Readahead disabled. */ + return False; + } + + if ((*last + g_readsz) > (offset + count)) { + /* Skip readahead ... we've already been here. */ + return False; + } + + DEBUG(module_debug, + ("%s: doing readahead of %lld bytes at %lld for %s\n", + MODULE, (long long)g_readsz, (long long)*last, + fsp_str_dbg(fsp))); + + nread = sys_pread(fsp_get_io_fd(fsp), g_readbuf, g_readsz, *last); + if (nread < 0) { + *last = -1; + return False; + } + + *last += nread; + return True; +} + +static int cprime_connect( + struct vfs_handle_struct * handle, + const char * service, + const char * user) +{ + int ret; + + module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100); + if (g_readbuf) { + /* Only allocate g_readbuf once. If the config changes and + * another client multiplexes onto this smbd, we don't want + * to risk memory corruption. + */ + return SMB_VFS_NEXT_CONNECT(handle, service, user); + } + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + g_readsz = conv_str_size(lp_parm_const_string(SNUM(handle->conn), + MODULE, "rsize", NULL)); + + if (g_readsz < READAHEAD_MIN) { + DEBUG(module_debug, ("%s: %ld bytes of readahead " + "requested, using minimum of %u\n", + MODULE, (long)g_readsz, READAHEAD_MIN)); + g_readsz = READAHEAD_MIN; + } else if (g_readsz > READAHEAD_MAX) { + DEBUG(module_debug, ("%s: %ld bytes of readahead " + "requested, using maximum of %u\n", + MODULE, (long)g_readsz, READAHEAD_MAX)); + g_readsz = READAHEAD_MAX; + } + + if ((g_readbuf = SMB_MALLOC(g_readsz)) == NULL) { + /* Turn off readahead if we can't get a buffer. */ + g_readsz = 0; + } + + return 0; +} + +static ssize_t cprime_sendfile( + struct vfs_handle_struct * handle, + int tofd, + files_struct * fromfsp, + const DATA_BLOB * header, + off_t offset, + size_t count) +{ + if (g_readbuf && offset == 0) { + prime_cache(handle, fromfsp, offset, count); + } + + return SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, + header, offset, count); +} + +static ssize_t cprime_pread( + vfs_handle_struct * handle, + files_struct * fsp, + void * data, + size_t count, + off_t offset) +{ + if (g_readbuf) { + prime_cache(handle, fsp, offset, count); + } + + return SMB_VFS_NEXT_PREAD(handle, fsp, data, count, offset); +} + +static struct vfs_fn_pointers vfs_cacheprime_fns = { + .sendfile_fn = cprime_sendfile, + .pread_fn = cprime_pread, + .connect_fn = cprime_connect, +}; + +/* ------------------------------------------------------------------------- + * Samba module initialisation entry point. + * ------------------------------------------------------------------------- + */ + +static_decl_vfs; +NTSTATUS vfs_cacheprime_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE, + &vfs_cacheprime_fns); +} + +/* vim: set sw=4 ts=4 tw=79 et: */ diff --git a/source3/modules/vfs_cap.c b/source3/modules/vfs_cap.c new file mode 100644 index 0000000..3553e11 --- /dev/null +++ b/source3/modules/vfs_cap.c @@ -0,0 +1,1004 @@ +/* + * CAP VFS module for Samba 3.x Version 0.3 + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002-2003 + * Copyright (C) Stefan (metze) Metzmacher, 2003 + * Copyright (C) TAKAHASHI Motonobu (monyo), 2003 + * Copyright (C) Jeremy Allison, 2007 + * + * 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 "smbd/smbd.h" + +/* cap functions */ +static char *capencode(TALLOC_CTX *ctx, const char *from); +static char *capdecode(TALLOC_CTX *ctx, const char *from); + +static uint64_t cap_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + char *capname = capencode(talloc_tos(), smb_fname->base_name); + struct smb_filename *cap_smb_fname = NULL; + + if (!capname) { + errno = ENOMEM; + return (uint64_t)-1; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + capname, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(capname); + errno = ENOMEM; + return (uint64_t)-1; + } + return SMB_VFS_NEXT_DISK_FREE(handle, cap_smb_fname, + bsize, dfree, dsize); +} + +static int cap_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + struct smb_filename *cap_smb_fname = NULL; + + if (!cappath) { + errno = ENOMEM; + return -1; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + return SMB_VFS_NEXT_GET_QUOTA(handle, cap_smb_fname, qtype, id, dq); +} + +static struct dirent * +cap_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp) +{ + struct dirent *result; + struct dirent *newdirent; + char *newname; + size_t newnamelen; + DEBUG(3,("cap: cap_readdir\n")); + + result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp); + if (!result) { + return NULL; + } + + newname = capdecode(talloc_tos(), result->d_name); + if (!newname) { + return NULL; + } + DEBUG(3,("cap: cap_readdir: %s\n", newname)); + newnamelen = strlen(newname)+1; + newdirent = talloc_size( + talloc_tos(), sizeof(struct dirent) + newnamelen); + if (!newdirent) { + return NULL; + } + talloc_set_name_const(newdirent, "struct dirent"); + memcpy(newdirent, result, sizeof(struct dirent)); + memcpy(&newdirent->d_name, newname, newnamelen); + return newdirent; +} + +static int cap_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + struct smb_filename *cap_smb_fname = NULL; + + if (!cappath) { + errno = ENOMEM; + return -1; + } + + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + cap_smb_fname, + mode); +} + +static int cap_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + files_struct *fsp, + const struct vfs_open_how *how) +{ + char *cappath = NULL; + struct smb_filename *smb_fname = NULL; + int ret; + int saved_errno = 0; + + cappath = capencode(talloc_tos(), smb_fname_in->base_name); + if (cappath == NULL) { + errno = ENOMEM; + return -1; + } + + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + smb_fname->base_name = cappath; + + DBG_DEBUG("cap_open for %s\n", smb_fname_str_dbg(smb_fname)); + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(cappath); + TALLOC_FREE(smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int cap_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + char *capold = NULL; + char *capnew = NULL; + struct smb_filename *smb_fname_src_tmp = NULL; + struct smb_filename *smb_fname_dst_tmp = NULL; + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + int ret = -1; + int saved_errno = 0; + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + goto out; + } + + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + errno = ENOMEM; + goto out; + } + + capold = capencode(talloc_tos(), full_fname_src->base_name); + capnew = capencode(talloc_tos(), full_fname_dst->base_name); + if (!capold || !capnew) { + errno = ENOMEM; + goto out; + } + + /* Setup temporary smb_filename structs. */ + smb_fname_src_tmp = cp_smb_filename(talloc_tos(), full_fname_src); + if (smb_fname_src_tmp == NULL) { + errno = ENOMEM; + goto out; + } + smb_fname_dst_tmp = cp_smb_filename(talloc_tos(), full_fname_dst); + if (smb_fname_dst_tmp == NULL) { + errno = ENOMEM; + goto out; + } + + smb_fname_src_tmp->base_name = capold; + smb_fname_dst_tmp->base_name = capnew; + + ret = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp->conn->cwd_fsp, + smb_fname_src_tmp, + dstfsp->conn->cwd_fsp, + smb_fname_dst_tmp); + + out: + + if (ret != 0) { + saved_errno = errno; + } + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + TALLOC_FREE(smb_fname_src_tmp); + TALLOC_FREE(smb_fname_dst_tmp); + + if (ret != 0) { + errno = saved_errno; + } + + return ret; +} + +static int cap_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname) +{ + char *cappath; + char *tmp_base_name = NULL; + int ret; + + cappath = capencode(talloc_tos(), smb_fname->base_name); + + if (!cappath) { + errno = ENOMEM; + return -1; + } + + tmp_base_name = smb_fname->base_name; + smb_fname->base_name = cappath; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + + smb_fname->base_name = tmp_base_name; + TALLOC_FREE(cappath); + + return ret; +} + +static int cap_lstat(vfs_handle_struct *handle, struct smb_filename *smb_fname) +{ + char *cappath; + char *tmp_base_name = NULL; + int ret; + + cappath = capencode(talloc_tos(), smb_fname->base_name); + + if (!cappath) { + errno = ENOMEM; + return -1; + } + + tmp_base_name = smb_fname->base_name; + smb_fname->base_name = cappath; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + + smb_fname->base_name = tmp_base_name; + TALLOC_FREE(cappath); + + return ret; +} + +static int cap_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *smb_fname_tmp = NULL; + char *cappath = NULL; + int ret; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + cappath = capencode(talloc_tos(), full_fname->base_name); + if (!cappath) { + TALLOC_FREE(full_fname); + errno = ENOMEM; + return -1; + } + + /* Setup temporary smb_filename structs. */ + smb_fname_tmp = cp_smb_filename(talloc_tos(), full_fname); + TALLOC_FREE(full_fname); + if (smb_fname_tmp == NULL) { + errno = ENOMEM; + return -1; + } + + smb_fname_tmp->base_name = cappath; + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + smb_fname_tmp, + flags); + + TALLOC_FREE(smb_fname_tmp); + return ret; +} + +static int cap_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + struct smb_filename *cap_smb_fname = NULL; + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + int ret; + int saved_errno; + + if (!cappath) { + errno = ENOMEM; + return -1; + } + + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_LCHOWN(handle, cap_smb_fname, uid, gid); + saved_errno = errno; + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + errno = saved_errno; + return ret; +} + +static int cap_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct smb_filename *cap_smb_fname = NULL; + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + int ret; + int saved_errno = 0; + + if (!cappath) { + errno = ENOMEM; + return -1; + } + DEBUG(3,("cap: cap_chdir for %s\n", smb_fname->base_name)); + + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, cap_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int cap_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + struct smb_filename *full_fname = NULL; + char *capold = capencode(talloc_tos(), link_contents->base_name); + char *capnew = NULL; + struct smb_filename *new_link_target = NULL; + struct smb_filename *new_cap_smb_fname = NULL; + int saved_errno = 0; + int ret; + + if (capold == NULL) { + errno = ENOMEM; + return -1; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + return -1; + } + + capnew = capencode(talloc_tos(), full_fname->base_name); + if (!capnew) { + TALLOC_FREE(full_fname); + errno = ENOMEM; + return -1; + } + + new_link_target = synthetic_smb_fname(talloc_tos(), + capold, + NULL, + NULL, + new_smb_fname->twrp, + new_smb_fname->flags); + if (new_link_target == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + errno = ENOMEM; + return -1; + } + + new_cap_smb_fname = synthetic_smb_fname(talloc_tos(), + capnew, + NULL, + NULL, + new_smb_fname->twrp, + new_smb_fname->flags); + if (new_cap_smb_fname == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + TALLOC_FREE(new_link_target); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_SYMLINKAT(handle, + new_link_target, + handle->conn->cwd_fsp, + new_cap_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(full_fname); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + TALLOC_FREE(new_link_target); + TALLOC_FREE(new_cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int cap_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *cap_smb_fname = NULL; + char *cappath = NULL; + int saved_errno = 0; + int ret; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + cappath = capencode(talloc_tos(), full_fname->base_name); + if (cappath == NULL) { + TALLOC_FREE(full_fname); + errno = ENOMEM; + return -1; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + cap_smb_fname, + buf, + bufsiz); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int cap_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + struct smb_filename *old_full_fname = NULL; + struct smb_filename *new_full_fname = NULL; + char *capold = NULL; + char *capnew = NULL; + struct smb_filename *old_cap_smb_fname = NULL; + struct smb_filename *new_cap_smb_fname = NULL; + int saved_errno = 0; + int ret; + + /* Process 'old' name. */ + old_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (old_full_fname == NULL) { + goto nomem_out; + } + capold = capencode(talloc_tos(), old_full_fname->base_name); + if (capold == NULL) { + goto nomem_out; + } + TALLOC_FREE(old_full_fname); + old_cap_smb_fname = synthetic_smb_fname(talloc_tos(), + capold, + NULL, + NULL, + old_smb_fname->twrp, + old_smb_fname->flags); + if (old_cap_smb_fname == NULL) { + goto nomem_out; + } + + /* Process 'new' name. */ + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + goto nomem_out; + } + capnew = capencode(talloc_tos(), new_full_fname->base_name); + if (capnew == NULL) { + goto nomem_out; + } + TALLOC_FREE(new_full_fname); + new_cap_smb_fname = synthetic_smb_fname(talloc_tos(), + capnew, + NULL, + NULL, + new_smb_fname->twrp, + new_smb_fname->flags); + if (new_cap_smb_fname == NULL) { + goto nomem_out; + } + + ret = SMB_VFS_NEXT_LINKAT(handle, + handle->conn->cwd_fsp, + old_cap_smb_fname, + handle->conn->cwd_fsp, + new_cap_smb_fname, + flags); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(old_full_fname); + TALLOC_FREE(old_full_fname); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + TALLOC_FREE(old_cap_smb_fname); + TALLOC_FREE(new_cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; + + nomem_out: + + TALLOC_FREE(old_full_fname); + TALLOC_FREE(old_full_fname); + TALLOC_FREE(capold); + TALLOC_FREE(capnew); + TALLOC_FREE(old_cap_smb_fname); + TALLOC_FREE(new_cap_smb_fname); + errno = ENOMEM; + return -1; +} + +static int cap_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *cap_smb_fname = NULL; + char *cappath = NULL; + int ret; + int saved_errno = 0; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + cappath = capencode(talloc_tos(), full_fname->base_name); + if (!cappath) { + TALLOC_FREE(full_fname); + errno = ENOMEM; + return -1; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_MKNODAT(handle, + handle->conn->cwd_fsp, + cap_smb_fname, + mode, + dev); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static struct smb_filename *cap_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + /* monyo need capencode'ed and capdecode'ed? */ + struct smb_filename *cap_smb_fname = NULL; + struct smb_filename *return_fname = NULL; + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + int saved_errno = 0; + + if (!cappath) { + errno = ENOMEM; + return NULL; + } + cap_smb_fname = synthetic_smb_fname(ctx, + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + errno = ENOMEM; + return NULL; + } + return_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, cap_smb_fname); + if (return_fname == NULL) { + saved_errno = errno; + } + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return return_fname; +} + +static ssize_t cap_fgetxattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path, void *value, size_t size) +{ + char *cappath = capencode(talloc_tos(), path); + + if (!cappath) { + errno = ENOMEM; + return -1; + } + return SMB_VFS_NEXT_FGETXATTR(handle, fsp, cappath, value, size); +} + +static int cap_fremovexattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path) +{ + char *cappath = capencode(talloc_tos(), path); + + if (!cappath) { + errno = ENOMEM; + return -1; + } + return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, cappath); +} + +static int cap_fsetxattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path, const void *value, size_t size, int flags) +{ + char *cappath = capencode(talloc_tos(), path); + + if (!cappath) { + errno = ENOMEM; + return -1; + } + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, cappath, value, size, flags); +} + +static NTSTATUS cap_create_dfs_pathat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + char *cappath = capencode(talloc_tos(), smb_fname->base_name); + struct smb_filename *cap_smb_fname = NULL; + NTSTATUS status; + + if (cappath == NULL) { + return NT_STATUS_NO_MEMORY; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(cappath); + return NT_STATUS_NO_MEMORY; + } + status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + cap_smb_fname, + reflist, + referral_count); + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + return status; +} + +static NTSTATUS cap_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *cap_smb_fname = NULL; + char *cappath = NULL; + NTSTATUS status; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + cappath = capencode(talloc_tos(), full_fname->base_name); + if (cappath == NULL) { + TALLOC_FREE(full_fname); + return NT_STATUS_NO_MEMORY; + } + cap_smb_fname = synthetic_smb_fname(talloc_tos(), + cappath, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (cap_smb_fname == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + handle->conn->cwd_fsp, + cap_smb_fname, + ppreflist, + preferral_count); + + if (NT_STATUS_IS_OK(status)) { + /* Return any stat(2) info. */ + smb_fname->st = cap_smb_fname->st; + } + + TALLOC_FREE(full_fname); + TALLOC_FREE(cappath); + TALLOC_FREE(cap_smb_fname); + return status; +} + +static struct vfs_fn_pointers vfs_cap_fns = { + .disk_free_fn = cap_disk_free, + .get_quota_fn = cap_get_quota, + .readdir_fn = cap_readdir, + .mkdirat_fn = cap_mkdirat, + .openat_fn = cap_openat, + .renameat_fn = cap_renameat, + .stat_fn = cap_stat, + .lstat_fn = cap_lstat, + .unlinkat_fn = cap_unlinkat, + .lchown_fn = cap_lchown, + .chdir_fn = cap_chdir, + .symlinkat_fn = cap_symlinkat, + .readlinkat_fn = cap_readlinkat, + .linkat_fn = cap_linkat, + .mknodat_fn = cap_mknodat, + .realpath_fn = cap_realpath, + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = cap_fgetxattr, + .fremovexattr_fn = cap_fremovexattr, + .fsetxattr_fn = cap_fsetxattr, + .create_dfs_pathat_fn = cap_create_dfs_pathat, + .read_dfs_pathat_fn = cap_read_dfs_pathat +}; + +static_decl_vfs; +NTSTATUS vfs_cap_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "cap", + &vfs_cap_fns); +} + +/* For CAP functions */ +#define hex_tag ':' +#define hex2bin(c) hex2bin_table[(unsigned char)(c)] +#define bin2hex(c) bin2hex_table[(unsigned char)(c)] +#define is_hex(s) ((s)[0] == hex_tag) + +static unsigned char hex2bin_table[256] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ +0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 */ +0000, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0000, /* 0x40 */ +0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ +0000, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0000, /* 0x60 */ +0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 */ +}; +static unsigned char bin2hex_table[256] = "0123456789abcdef"; + +/******************************************************************* + original code -> ":xx" - CAP format +********************************************************************/ + +static char *capencode(TALLOC_CTX *ctx, const char *from) +{ + char *out = NULL; + const char *p1; + char *to = NULL; + size_t len = 0; + + for (p1 = from; *p1; p1++) { + if ((unsigned char)*p1 >= 0x80) { + len += 3; + } else { + len++; + } + } + len++; + + to = talloc_array(ctx, char, len); + if (!to) { + return NULL; + } + + for (out = to; *from;) { + /* buffer husoku error */ + if ((unsigned char)*from >= 0x80) { + *out++ = hex_tag; + *out++ = bin2hex (((*from)>>4)&0x0f); + *out++ = bin2hex ((*from)&0x0f); + from++; + } else { + *out++ = *from++; + } + } + *out = '\0'; + return to; +} + +/******************************************************************* + CAP -> original code +********************************************************************/ +/* ":xx" -> a byte */ + +static char *capdecode(TALLOC_CTX *ctx, const char *from) +{ + const char *p1; + char *out = NULL; + char *to = NULL; + size_t len = 0; + + for (p1 = from; *p1; len++) { + if (is_hex(p1)) { + p1 += 3; + } else { + p1++; + } + } + len++; + + to = talloc_array(ctx, char, len); + if (!to) { + return NULL; + } + + for (out = to; *from;) { + if (is_hex(from)) { + *out++ = (hex2bin(from[1])<<4) | (hex2bin(from[2])); + from += 3; + } else { + *out++ = *from++; + } + } + *out = '\0'; + return to; +} diff --git a/source3/modules/vfs_catia.c b/source3/modules/vfs_catia.c new file mode 100644 index 0000000..36aa431 --- /dev/null +++ b/source3/modules/vfs_catia.c @@ -0,0 +1,1952 @@ +/* + * Catia VFS module + * + * Implement a fixed mapping of forbidden NT characters in filenames that are + * used a lot by the CAD package Catia. + * + * Catia V4 on AIX uses characters like "<*$ a *lot*, all forbidden under + * Windows... + * + * Copyright (C) Volker Lendecke, 2005 + * Copyright (C) Aravind Srinivasan, 2009 + * Copyright (C) Guenter Kukkukk, 2013 + * Copyright (C) Ralph Boehme, 2017 + * + * 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 "smbd/smbd.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/tevent_ntstatus.h" +#include "string_replace.h" + +static int vfs_catia_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_catia_debug_level + +struct share_mapping_entry { + int snum; + struct share_mapping_entry *next; + struct char_mappings **mappings; +}; + +struct catia_cache { + bool is_fsp_ext; + const struct catia_cache * const *busy; + char *orig_fname; + char *fname; + char *orig_base_fname; + char *base_fname; +}; + +static struct share_mapping_entry *srt_head = NULL; + +static struct share_mapping_entry *get_srt(connection_struct *conn, + struct share_mapping_entry **global) +{ + struct share_mapping_entry *share; + + for (share = srt_head; share != NULL; share = share->next) { + if (share->snum == GLOBAL_SECTION_SNUM) + (*global) = share; + + if (share->snum == SNUM(conn)) + return share; + } + + return share; +} + +static struct share_mapping_entry *add_srt(int snum, const char **mappings) +{ + struct share_mapping_entry *sme = NULL; + + sme = talloc_zero(NULL, struct share_mapping_entry); + if (sme == NULL) + return sme; + + sme->snum = snum; + sme->next = srt_head; + srt_head = sme; + + if (mappings == NULL) { + sme->mappings = NULL; + return sme; + } + + sme->mappings = string_replace_init_map(sme, mappings); + + return sme; +} + +static bool init_mappings(connection_struct *conn, + struct share_mapping_entry **selected_out) +{ + const char **mappings = NULL; + struct share_mapping_entry *share_level = NULL; + struct share_mapping_entry *global = NULL; + + /* check srt cache */ + share_level = get_srt(conn, &global); + if (share_level) { + *selected_out = share_level; + return (share_level->mappings != NULL); + } + + /* see if we have a global setting */ + if (!global) { + /* global setting */ + mappings = lp_parm_string_list(-1, "catia", "mappings", NULL); + global = add_srt(GLOBAL_SECTION_SNUM, mappings); + } + + /* no global setting - what about share level ? */ + mappings = lp_parm_string_list(SNUM(conn), "catia", "mappings", NULL); + share_level = add_srt(SNUM(conn), mappings); + + if (share_level->mappings) { + (*selected_out) = share_level; + return True; + } + if (global->mappings) { + share_level->mappings = global->mappings; + (*selected_out) = share_level; + return True; + } + + return False; +} + +static NTSTATUS catia_string_replace_allocate(connection_struct *conn, + const char *name_in, + char **mapped_name, + enum vfs_translate_direction direction) +{ + struct share_mapping_entry *selected; + NTSTATUS status; + + if (!init_mappings(conn, &selected)) { + /* No mappings found. Just use the old name */ + *mapped_name = talloc_strdup(talloc_tos(), name_in); + if (!*mapped_name) { + errno = ENOMEM; + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } + + status = string_replace_allocate(conn, + name_in, + selected->mappings, + talloc_tos(), + mapped_name, + direction); + return status; +} + +static int catia_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + /* + * Unless we have an async implementation of get_dos_attributes turn + * this off. + */ + lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false"); + + return SMB_VFS_NEXT_CONNECT(handle, service, user); +} + +/* + * TRANSLATE_NAME call which converts the given name to + * "WINDOWS displayable" name + */ +static NTSTATUS catia_translate_name(struct vfs_handle_struct *handle, + const char *orig_name, + enum vfs_translate_direction direction, + TALLOC_CTX *mem_ctx, + char **pmapped_name) +{ + char *name = NULL; + char *mapped_name; + NTSTATUS status, ret; + + /* + * Copy the supplied name and free the memory for mapped_name, + * already allocated by the caller. + * We will be allocating new memory for mapped_name in + * catia_string_replace_allocate + */ + name = talloc_strdup(talloc_tos(), orig_name); + if (!name) { + errno = ENOMEM; + return NT_STATUS_NO_MEMORY; + } + status = catia_string_replace_allocate(handle->conn, name, + &mapped_name, direction); + + TALLOC_FREE(name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ret = SMB_VFS_NEXT_TRANSLATE_NAME(handle, mapped_name, direction, + mem_ctx, pmapped_name); + + if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) { + *pmapped_name = talloc_move(mem_ctx, &mapped_name); + /* we need to return the former translation result here */ + ret = status; + } else { + TALLOC_FREE(mapped_name); + } + + return ret; +} + +#define CATIA_DEBUG_CC(lvl, cc, fsp) \ + catia_debug_cc((lvl), (cc), (fsp), __location__); + +static void catia_debug_cc(int lvl, + struct catia_cache *cc, + files_struct *fsp, + const char *location) +{ + DEBUG(lvl, ("%s: cc [%p] cc->busy [%p] " + "is_fsp_ext [%s] " + "fsp [%p] fsp name [%s] " + "orig_fname [%s] " + "fname [%s] " + "orig_base_fname [%s] " + "base_fname [%s]\n", + location, + cc, cc->busy, + cc->is_fsp_ext ? "yes" : "no", + fsp, fsp_str_dbg(fsp), + cc->orig_fname, cc->fname, + cc->orig_base_fname, cc->base_fname)); +} + +static void catia_free_cc(struct catia_cache **_cc, + vfs_handle_struct *handle, + files_struct *fsp) +{ + struct catia_cache *cc = *_cc; + + if (cc->is_fsp_ext) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + cc = NULL; + } else { + TALLOC_FREE(cc); + } + + *_cc = NULL; +} + +static struct catia_cache *catia_validate_and_apply_cc( + vfs_handle_struct *handle, + files_struct *fsp, + const struct catia_cache * const *busy, + bool *make_tmp_cache) +{ + struct catia_cache *cc = NULL; + + *make_tmp_cache = false; + + cc = (struct catia_cache *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + if (cc == NULL) { + return NULL; + } + + if (cc->busy != NULL) { + if (cc->busy == busy) { + /* This should never happen */ + CATIA_DEBUG_CC(0, cc, fsp); + smb_panic(__location__); + } + + /* + * Recursion. Validate names, the names in the fsp's should be + * the translated names we had set. + */ + + if ((cc->fname != fsp->fsp_name->base_name) + || + (fsp_is_alternate_stream(fsp) && + (cc->base_fname != fsp->base_fsp->fsp_name->base_name))) + { + CATIA_DEBUG_CC(10, cc, fsp); + + /* + * Names changed. Setting don't expose the cache on the + * fsp and ask the caller to create a temporary cache. + */ + *make_tmp_cache = true; + return NULL; + } + + /* + * Ok, a validated cache while in a recursion, just let the + * caller detect that cc->busy is != busy and there's + * nothing else to do. + */ + CATIA_DEBUG_CC(10, cc, fsp); + return cc; + } + + /* Not in a recursion */ + + if ((cc->orig_fname != fsp->fsp_name->base_name) + || + (fsp_is_alternate_stream(fsp) && + (cc->orig_base_fname != fsp->base_fsp->fsp_name->base_name))) + { + /* + * fsp names changed, this can happen in an rename op. + * Trigger recreation as a full fledged fsp extension. + */ + + CATIA_DEBUG_CC(10, cc, fsp); + catia_free_cc(&cc, handle, fsp); + return NULL; + } + + + /* + * Ok, we found a valid cache entry, no recursion. Just set translated + * names from the cache and mark the cc as busy. + */ + fsp->fsp_name->base_name = cc->fname; + if (fsp_is_alternate_stream(fsp)) { + fsp->base_fsp->fsp_name->base_name = cc->base_fname; + } + + cc->busy = busy; + CATIA_DEBUG_CC(10, cc, fsp); + return cc; +} + +#define CATIA_FETCH_FSP_PRE_NEXT(mem_ctx, handle, fsp, _cc) \ + catia_fetch_fsp_pre_next((mem_ctx), (handle), (fsp), (_cc), __func__); + +static int catia_fetch_fsp_pre_next(TALLOC_CTX *mem_ctx, + vfs_handle_struct *handle, + files_struct *fsp, + struct catia_cache **_cc, + const char *function) +{ + const struct catia_cache * const *busy = + (const struct catia_cache * const *)_cc; + struct catia_cache *cc = NULL; + NTSTATUS status; + bool make_tmp_cache = false; + + *_cc = NULL; + + DBG_DEBUG("Called from [%s]\n", function); + + cc = catia_validate_and_apply_cc(handle, + fsp, + busy, + &make_tmp_cache); + if (cc != NULL) { + if (cc->busy != busy) { + return 0; + } + *_cc = cc; + return 0; + } + + if (!make_tmp_cache) { + cc = VFS_ADD_FSP_EXTENSION( + handle, fsp, struct catia_cache, NULL); + if (cc == NULL) { + return -1; + } + *cc = (struct catia_cache) { + .is_fsp_ext = true, + }; + + mem_ctx = VFS_MEMCTX_FSP_EXTENSION(handle, fsp); + if (mem_ctx == NULL) { + DBG_ERR("VFS_MEMCTX_FSP_EXTENSION failed\n"); + catia_free_cc(&cc, handle, fsp); + return -1; + } + } else { + cc = talloc_zero(mem_ctx, struct catia_cache); + if (cc == NULL) { + return -1; + } + mem_ctx = cc; + } + + + status = catia_string_replace_allocate(handle->conn, + fsp->fsp_name->base_name, + &cc->fname, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + catia_free_cc(&cc, handle, fsp); + errno = map_errno_from_nt_status(status); + return -1; + } + talloc_steal(mem_ctx, cc->fname); + + if (fsp_is_alternate_stream(fsp)) { + status = catia_string_replace_allocate( + handle->conn, + fsp->base_fsp->fsp_name->base_name, + &cc->base_fname, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + catia_free_cc(&cc, handle, fsp); + errno = map_errno_from_nt_status(status); + return -1; + } + talloc_steal(mem_ctx, cc->base_fname); + } + + cc->orig_fname = fsp->fsp_name->base_name; + fsp->fsp_name->base_name = cc->fname; + + if (fsp_is_alternate_stream(fsp)) { + cc->orig_base_fname = fsp->base_fsp->fsp_name->base_name; + fsp->base_fsp->fsp_name->base_name = cc->base_fname; + } + + cc->busy = busy; + CATIA_DEBUG_CC(10, cc, fsp); + + *_cc = cc; + + return 0; +} + +#define CATIA_FETCH_FSP_POST_NEXT(_cc, fsp) do { \ + int catia_saved_errno = errno; \ + catia_fetch_fsp_post_next((_cc), (fsp), __func__); \ + errno = catia_saved_errno; \ +} while(0) + +static void catia_fetch_fsp_post_next(struct catia_cache **_cc, + files_struct *fsp, + const char *function) +{ + const struct catia_cache * const *busy = + (const struct catia_cache * const *)_cc; + struct catia_cache *cc = *_cc; + + DBG_DEBUG("Called from [%s]\n", function); + + if (cc == NULL) { + /* + * This can happen when recursing in the VFS on the fsp when the + * pre_next func noticed the recursion and set out cc pointer to + * NULL. + */ + return; + } + + if (cc->busy != busy) { + CATIA_DEBUG_CC(0, cc, fsp); + smb_panic(__location__); + return; + } + + cc->busy = NULL; + *_cc = NULL; + + fsp->fsp_name->base_name = cc->orig_fname; + if (fsp_is_alternate_stream(fsp)) { + fsp->base_fsp->fsp_name->base_name = cc->orig_base_fname; + } + + CATIA_DEBUG_CC(10, cc, fsp); + + if (!cc->is_fsp_ext) { + TALLOC_FREE(cc); + } + + return; +} + +static int catia_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + files_struct *fsp, + const struct vfs_open_how *how) +{ + struct smb_filename *smb_fname = NULL; + struct catia_cache *cc = NULL; + char *mapped_name = NULL; + NTSTATUS status; + int ret; + int saved_errno = 0; + + status = catia_string_replace_allocate(handle->conn, + smb_fname_in->base_name, + &mapped_name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + TALLOC_FREE(mapped_name); + return ret; + } + + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + TALLOC_FREE(mapped_name); + errno = ENOMEM; + return -1; + } + smb_fname->base_name = mapped_name; + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(smb_fname); + TALLOC_FREE(mapped_name); + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int catia_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + TALLOC_CTX *ctx = talloc_tos(); + struct smb_filename *smb_fname_src_tmp = NULL; + struct smb_filename *smb_fname_dst_tmp = NULL; + char *src_name_mapped = NULL; + char *dst_name_mapped = NULL; + NTSTATUS status; + int ret = -1; + + status = catia_string_replace_allocate(handle->conn, + smb_fname_src->base_name, + &src_name_mapped, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + status = catia_string_replace_allocate(handle->conn, + smb_fname_dst->base_name, + &dst_name_mapped, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + /* Setup temporary smb_filename structs. */ + smb_fname_src_tmp = cp_smb_filename(ctx, smb_fname_src); + if (smb_fname_src_tmp == NULL) { + errno = ENOMEM; + goto out; + } + + smb_fname_dst_tmp = cp_smb_filename(ctx, smb_fname_dst); + if (smb_fname_dst_tmp == NULL) { + errno = ENOMEM; + goto out; + } + + smb_fname_src_tmp->base_name = src_name_mapped; + smb_fname_dst_tmp->base_name = dst_name_mapped; + DEBUG(10, ("converted old name: %s\n", + smb_fname_str_dbg(smb_fname_src_tmp))); + DEBUG(10, ("converted new name: %s\n", + smb_fname_str_dbg(smb_fname_dst_tmp))); + + ret = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src_tmp, + dstfsp, + smb_fname_dst_tmp); + +out: + TALLOC_FREE(src_name_mapped); + TALLOC_FREE(dst_name_mapped); + TALLOC_FREE(smb_fname_src_tmp); + TALLOC_FREE(smb_fname_dst_tmp); + return ret; +} + + +static int catia_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + char *name = NULL; + char *tmp_base_name; + int ret; + NTSTATUS status; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + tmp_base_name = smb_fname->base_name; + smb_fname->base_name = name; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + smb_fname->base_name = tmp_base_name; + + TALLOC_FREE(name); + return ret; +} + +static int catia_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + char *name = NULL; + char *tmp_base_name; + int ret; + NTSTATUS status; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + tmp_base_name = smb_fname->base_name; + smb_fname->base_name = name; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + smb_fname->base_name = tmp_base_name; + TALLOC_FREE(name); + + return ret; +} + +static int catia_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct catia_cache *cc = NULL; + struct smb_filename *smb_fname_tmp = NULL; + char *name = NULL; + NTSTATUS status; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, dirfsp, &cc); + if (ret != 0) { + return ret; + } + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto out; + } + + /* Setup temporary smb_filename structs. */ + smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname); + if (smb_fname_tmp == NULL) { + errno = ENOMEM; + goto out; + } + + smb_fname_tmp->base_name = name; + smb_fname_tmp->fsp = smb_fname->fsp; + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname_tmp, + flags); + TALLOC_FREE(smb_fname_tmp); + TALLOC_FREE(name); + +out: + CATIA_FETCH_FSP_POST_NEXT(&cc, dirfsp); + return ret; +} + +static int catia_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + char *name = NULL; + NTSTATUS status; + int ret; + int saved_errno; + struct smb_filename *catia_smb_fname = NULL; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + catia_smb_fname = synthetic_smb_fname(talloc_tos(), + name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (catia_smb_fname == NULL) { + TALLOC_FREE(name); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_LCHOWN(handle, catia_smb_fname, uid, gid); + saved_errno = errno; + TALLOC_FREE(name); + TALLOC_FREE(catia_smb_fname); + errno = saved_errno; + return ret; +} + +static int catia_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + char *name = NULL; + NTSTATUS status; + int ret; + struct smb_filename *catia_smb_fname = NULL; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + catia_smb_fname = synthetic_smb_fname(talloc_tos(), + name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (catia_smb_fname == NULL) { + TALLOC_FREE(name); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + catia_smb_fname, + mode); + TALLOC_FREE(name); + TALLOC_FREE(catia_smb_fname); + + return ret; +} + +static int catia_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + char *name = NULL; + struct smb_filename *catia_smb_fname = NULL; + NTSTATUS status; + int ret; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + catia_smb_fname = synthetic_smb_fname(talloc_tos(), + name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (catia_smb_fname == NULL) { + TALLOC_FREE(name); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, catia_smb_fname); + TALLOC_FREE(name); + TALLOC_FREE(catia_smb_fname); + + return ret; +} + +static int catia_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static struct smb_filename * +catia_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + char *mapped_name = NULL; + struct smb_filename *catia_smb_fname = NULL; + struct smb_filename *return_fname = NULL; + NTSTATUS status; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &mapped_name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return NULL; + } + + catia_smb_fname = synthetic_smb_fname(talloc_tos(), + mapped_name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (catia_smb_fname == NULL) { + TALLOC_FREE(mapped_name); + errno = ENOMEM; + return NULL; + } + return_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, catia_smb_fname); + TALLOC_FREE(mapped_name); + TALLOC_FREE(catia_smb_fname); + return return_fname; +} + +static NTSTATUS +catia_fstreaminfo(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *_num_streams, + struct stream_struct **_streams) +{ + char *mapped_name = NULL; + NTSTATUS status; + unsigned int i; + struct smb_filename *catia_smb_fname = NULL; + struct smb_filename *smb_fname = NULL; + unsigned int num_streams = 0; + struct stream_struct *streams = NULL; + + smb_fname = fsp->fsp_name; + *_num_streams = 0; + *_streams = NULL; + + status = catia_string_replace_allocate(handle->conn, + smb_fname->base_name, + &mapped_name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + mapped_name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags, + &catia_smb_fname); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(mapped_name); + return status; + } + + status = SMB_VFS_NEXT_FSTREAMINFO(handle, + catia_smb_fname->fsp, + mem_ctx, + &num_streams, + &streams); + TALLOC_FREE(mapped_name); + TALLOC_FREE(catia_smb_fname); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Translate stream names just like the base names + */ + for (i = 0; i < num_streams; i++) { + /* + * Strip ":" prefix and ":$DATA" suffix to get a + * "pure" stream name and only translate that. + */ + void *old_ptr = streams[i].name; + char *stream_name = streams[i].name + 1; + char *stream_type = strrchr_m(stream_name, ':'); + + if (stream_type != NULL) { + *stream_type = '\0'; + stream_type += 1; + } + + status = catia_string_replace_allocate(handle->conn, + stream_name, + &mapped_name, + vfs_translate_to_windows); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(streams); + return status; + } + + if (stream_type != NULL) { + streams[i].name = talloc_asprintf(streams, + ":%s:%s", + mapped_name, + stream_type); + } else { + streams[i].name = talloc_asprintf(streams, + ":%s", + mapped_name); + } + TALLOC_FREE(mapped_name); + TALLOC_FREE(old_ptr); + if (streams[i].name == NULL) { + TALLOC_FREE(streams); + return NT_STATUS_NO_MEMORY; + } + } + + *_num_streams = num_streams; + *_streams = streams; + return NT_STATUS_OK; +} + +static int catia_fstat(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static ssize_t catia_pread(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct catia_cache *cc = NULL; + ssize_t result; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static ssize_t catia_pwrite(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct catia_cache *cc = NULL; + ssize_t result; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static int catia_ftruncate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static int catia_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static ssize_t catia_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + void *value, + size_t size) +{ + char *mapped_xattr_name = NULL; + NTSTATUS status; + ssize_t result; + + status = catia_string_replace_allocate(handle->conn, + name, &mapped_xattr_name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, mapped_xattr_name, + value, size); + + TALLOC_FREE(mapped_xattr_name); + + return result; +} + +static ssize_t catia_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + char *list, + size_t size) +{ + struct catia_cache *cc = NULL; + ssize_t result; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static int catia_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + char *mapped_name = NULL; + NTSTATUS status; + int ret; + + status = catia_string_replace_allocate(handle->conn, + name, &mapped_name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, mapped_name); + + TALLOC_FREE(mapped_name); + + return ret; +} + +static int catia_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + const void *value, + size_t size, + int flags) +{ + char *mapped_xattr_name = NULL; + NTSTATUS status; + int ret; + + status = catia_string_replace_allocate( + handle->conn, name, &mapped_xattr_name, vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, mapped_xattr_name, + value, size, flags); + + TALLOC_FREE(mapped_xattr_name); + + return ret; +} + +static SMB_ACL_T catia_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + struct catia_cache *cc = NULL; + struct smb_acl_t *result = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return NULL; + } + + result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static int catia_sys_acl_blob_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **blob_description, + DATA_BLOB *blob) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, + blob_description, blob); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static int catia_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static NTSTATUS catia_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + struct catia_cache *cc = NULL; + NTSTATUS status; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return status; +} + +static NTSTATUS catia_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + struct catia_cache *cc = NULL; + NTSTATUS status; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return status; +} + +static NTSTATUS catia_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + struct catia_cache *cc = NULL; + NTSTATUS status; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return status; +} + +static NTSTATUS catia_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + struct catia_cache *cc = NULL; + NTSTATUS status; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + status = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return status; +} + +static int catia_fchown(vfs_handle_struct *handle, + files_struct *fsp, + uid_t uid, + gid_t gid) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static int catia_fchmod(vfs_handle_struct *handle, + files_struct *fsp, + mode_t mode) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +struct catia_pread_state { + ssize_t ret; + struct vfs_aio_state vfs_aio_state; + struct files_struct *fsp; + struct catia_cache *cc; +}; + +static void catia_pread_done(struct tevent_req *subreq); + +static struct tevent_req *catia_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, + off_t offset) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct catia_pread_state *state = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct catia_pread_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, catia_pread_done, req); + + return req; +} + +static void catia_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct catia_pread_state *state = tevent_req_data( + req, struct catia_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp); + + tevent_req_done(req); +} + +static ssize_t catia_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct catia_pread_state *state = tevent_req_data( + req, struct catia_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct catia_pwrite_state { + ssize_t ret; + struct vfs_aio_state vfs_aio_state; + struct files_struct *fsp; + struct catia_cache *cc; +}; + +static void catia_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *catia_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, + off_t offset) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct catia_pwrite_state *state = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct catia_pwrite_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, catia_pwrite_done, req); + + return req; +} + +static void catia_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct catia_pwrite_state *state = tevent_req_data( + req, struct catia_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp); + + tevent_req_done(req); +} + +static ssize_t catia_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct catia_pwrite_state *state = tevent_req_data( + req, struct catia_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static off_t catia_lseek(vfs_handle_struct *handle, + files_struct *fsp, + off_t offset, + int whence) +{ + struct catia_cache *cc = NULL; + ssize_t result; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return -1; + } + + result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +struct catia_fsync_state { + int ret; + struct vfs_aio_state vfs_aio_state; + struct files_struct *fsp; + struct catia_cache *cc; +}; + +static void catia_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *catia_fsync_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct catia_fsync_state *state = NULL; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct catia_fsync_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, catia_fsync_done, req); + + return req; +} + +static void catia_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct catia_fsync_state *state = tevent_req_data( + req, struct catia_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp); + + tevent_req_done(req); +} + +static int catia_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct catia_fsync_state *state = tevent_req_data( + req, struct catia_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static bool catia_lock(vfs_handle_struct *handle, + files_struct *fsp, + int op, + off_t offset, + off_t count, + int type) +{ + struct catia_cache *cc = NULL; + bool ok; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return false; + } + + ok = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ok; +} + +static int catia_filesystem_sharemode(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return -1; + } + + ret = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle, + fsp, + share_access, + access_mask); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static int catia_linux_setlease(vfs_handle_struct *handle, + files_struct *fsp, + int leasetype) +{ + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return -1; + } + + ret = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ret; +} + +static bool catia_getlock(vfs_handle_struct *handle, + files_struct *fsp, + off_t *poffset, + off_t *pcount, + int *ptype, + pid_t *ppid) +{ + struct catia_cache *cc = NULL; + int ret; + bool ok; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return false; + } + + ok = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, ppid); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ok; +} + +static bool catia_strict_lock_check(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct lock_struct *plock) +{ + struct catia_cache *cc = NULL; + int ret; + bool ok; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return false; + } + + ok = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return ok; +} + +static NTSTATUS catia_fsctl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *ctx, + uint32_t function, + uint16_t req_flags, + const uint8_t *_in_data, + uint32_t in_len, + uint8_t **_out_data, + uint32_t max_out_len, + uint32_t *out_len) +{ + NTSTATUS result; + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + result = SMB_VFS_NEXT_FSCTL(handle, + fsp, + ctx, + function, + req_flags, + _in_data, + in_len, + _out_data, + max_out_len, + out_len); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static NTSTATUS catia_fget_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + NTSTATUS result; + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + result = SMB_VFS_NEXT_FGET_COMPRESSION(handle, + mem_ctx, + fsp, + _compression_fmt); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static NTSTATUS catia_set_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + NTSTATUS result; + struct catia_cache *cc = NULL; + int ret; + + ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + + result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp, + compression_fmt); + + CATIA_FETCH_FSP_POST_NEXT(&cc, fsp); + + return result; +} + +static NTSTATUS catia_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + char *mapped_name = NULL; + const char *path = smb_fname->base_name; + struct smb_filename *mapped_smb_fname = NULL; + NTSTATUS status; + + status = catia_string_replace_allocate(handle->conn, + path, + &mapped_name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return status; + } + mapped_smb_fname = synthetic_smb_fname(talloc_tos(), + mapped_name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (mapped_smb_fname == NULL) { + TALLOC_FREE(mapped_name); + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + mapped_smb_fname, + reflist, + referral_count); + TALLOC_FREE(mapped_name); + TALLOC_FREE(mapped_smb_fname); + return status; +} + +static NTSTATUS catia_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + char *mapped_name = NULL; + const char *path = smb_fname->base_name; + struct smb_filename *mapped_smb_fname = NULL; + NTSTATUS status; + + status = catia_string_replace_allocate(handle->conn, + path, + &mapped_name, + vfs_translate_to_unix); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return status; + } + mapped_smb_fname = synthetic_smb_fname(talloc_tos(), + mapped_name, + NULL, + &smb_fname->st, + smb_fname->twrp, + smb_fname->flags); + if (mapped_smb_fname == NULL) { + TALLOC_FREE(mapped_name); + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + dirfsp, + mapped_smb_fname, + ppreflist, + preferral_count); + if (NT_STATUS_IS_OK(status)) { + /* Return any stat(2) info. */ + smb_fname->st = mapped_smb_fname->st; + } + + TALLOC_FREE(mapped_name); + TALLOC_FREE(mapped_smb_fname); + return status; +} + +static struct vfs_fn_pointers vfs_catia_fns = { + .connect_fn = catia_connect, + + /* Directory operations */ + .mkdirat_fn = catia_mkdirat, + + /* File operations */ + .openat_fn = catia_openat, + .pread_fn = catia_pread, + .pread_send_fn = catia_pread_send, + .pread_recv_fn = catia_pread_recv, + .pwrite_fn = catia_pwrite, + .pwrite_send_fn = catia_pwrite_send, + .pwrite_recv_fn = catia_pwrite_recv, + .lseek_fn = catia_lseek, + .renameat_fn = catia_renameat, + .fsync_send_fn = catia_fsync_send, + .fsync_recv_fn = catia_fsync_recv, + .stat_fn = catia_stat, + .fstat_fn = catia_fstat, + .lstat_fn = catia_lstat, + .unlinkat_fn = catia_unlinkat, + .fchmod_fn = catia_fchmod, + .fchown_fn = catia_fchown, + .lchown_fn = catia_lchown, + .chdir_fn = catia_chdir, + .fntimes_fn = catia_fntimes, + .ftruncate_fn = catia_ftruncate, + .fallocate_fn = catia_fallocate, + .lock_fn = catia_lock, + .filesystem_sharemode_fn = catia_filesystem_sharemode, + .linux_setlease_fn = catia_linux_setlease, + .getlock_fn = catia_getlock, + .realpath_fn = catia_realpath, + .fstreaminfo_fn = catia_fstreaminfo, + .strict_lock_check_fn = catia_strict_lock_check, + .translate_name_fn = catia_translate_name, + .fsctl_fn = catia_fsctl, + .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv, + .fset_dos_attributes_fn = catia_fset_dos_attributes, + .fget_dos_attributes_fn = catia_fget_dos_attributes, + .fget_compression_fn = catia_fget_compression, + .set_compression_fn = catia_set_compression, + .create_dfs_pathat_fn = catia_create_dfs_pathat, + .read_dfs_pathat_fn = catia_read_dfs_pathat, + + /* NT ACL operations. */ + .fget_nt_acl_fn = catia_fget_nt_acl, + .fset_nt_acl_fn = catia_fset_nt_acl, + + /* POSIX ACL operations. */ + .sys_acl_get_fd_fn = catia_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = catia_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = catia_sys_acl_set_fd, + + /* EA operations. */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = catia_fgetxattr, + .flistxattr_fn = catia_flistxattr, + .fremovexattr_fn = catia_fremovexattr, + .fsetxattr_fn = catia_fsetxattr, +}; + +static_decl_vfs; +NTSTATUS vfs_catia_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "catia", + &vfs_catia_fns); + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_catia_debug_level = debug_add_class("catia"); + if (vfs_catia_debug_level == -1) { + vfs_catia_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_catia: Couldn't register custom debugging " + "class!\n")); + } else { + DEBUG(10, ("vfs_catia: Debug class number of " + "'catia': %d\n", vfs_catia_debug_level)); + } + + return ret; + +} diff --git a/source3/modules/vfs_ceph.c b/source3/modules/vfs_ceph.c new file mode 100644 index 0000000..c9ee541 --- /dev/null +++ b/source3/modules/vfs_ceph.c @@ -0,0 +1,1960 @@ +/* + Unix SMB/CIFS implementation. + Wrap disk only vfs functions to sidestep dodgy compilers. + Copyright (C) Tim Potter 1998 + Copyright (C) Jeremy Allison 2007 + Copyright (C) Brian Chrisman 2011 + Copyright (C) Richard Sharpe 2011 + + 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 . +*/ + +/* + * This VFS only works with the libcephfs.so user-space client. It is not needed + * if you are using the kernel client or the FUSE client. + * + * Add the following smb.conf parameter to each share that will be hosted on + * Ceph: + * + * vfs objects = [any others you need go here] ceph + */ + +#include "includes.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include +#include +#include "cephfs/libcephfs.h" +#include "smbprofile.h" +#include "modules/posixacl_xattr.h" +#include "lib/util/tevent_unix.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#ifndef LIBCEPHFS_VERSION +#define LIBCEPHFS_VERSION(maj, min, extra) ((maj << 16) + (min << 8) + extra) +#define LIBCEPHFS_VERSION_CODE LIBCEPHFS_VERSION(0, 0, 0) +#endif + +/* + * Use %llu whenever we have a 64bit unsigned int, and cast to (long long unsigned) + */ +#define llu(_var) ((long long unsigned)_var) + +/* + * Note, libcephfs's return code model is to return -errno! So we have to + * convert to what Samba expects, which is to set errno to -return and return -1 + */ +#define WRAP_RETURN(_res) \ + errno = 0; \ + if (_res < 0) { \ + errno = -_res; \ + return -1; \ + } \ + return _res \ + +/* + * Track unique connections, as virtual mounts, to cephfs file systems. + * Individual mounts will be set on the handle->data attribute, but + * the mounts themselves will be shared so as not to spawn extra mounts + * to the same cephfs. + * + * Individual mounts are IDed by a 'cookie' value that is a string built + * from identifying parameters found in smb.conf. + */ + +static struct cephmount_cached { + char *cookie; + uint32_t count; + struct ceph_mount_info *mount; + struct cephmount_cached *next, *prev; +} *cephmount_cached; + +static int cephmount_cache_add(const char *cookie, + struct ceph_mount_info *mount) +{ + struct cephmount_cached *entry = NULL; + + entry = talloc_zero(NULL, struct cephmount_cached); + if (entry == NULL) { + errno = ENOMEM; + return -1; + } + + entry->cookie = talloc_strdup(entry, cookie); + if (entry->cookie == NULL) { + talloc_free(entry); + errno = ENOMEM; + return -1; + } + + entry->mount = mount; + entry->count = 1; + + DBG_DEBUG("adding mount cache entry for %s\n", entry->cookie); + DLIST_ADD(cephmount_cached, entry); + return 0; +} + +static struct ceph_mount_info *cephmount_cache_update(const char *cookie) +{ + struct cephmount_cached *entry = NULL; + + for (entry = cephmount_cached; entry; entry = entry->next) { + if (strcmp(entry->cookie, cookie) == 0) { + entry->count++; + DBG_DEBUG("updated mount cache: count is [%" + PRIu32 "]\n", entry->count); + return entry->mount; + } + } + + errno = ENOENT; + return NULL; +} + +static int cephmount_cache_remove(struct ceph_mount_info *mount) +{ + struct cephmount_cached *entry = NULL; + + for (entry = cephmount_cached; entry; entry = entry->next) { + if (entry->mount == mount) { + if (--entry->count) { + DBG_DEBUG("updated mount cache: count is [%" + PRIu32 "]\n", entry->count); + return entry->count; + } + + DBG_DEBUG("removing mount cache entry for %s\n", + entry->cookie); + DLIST_REMOVE(cephmount_cached, entry); + talloc_free(entry); + return 0; + } + } + errno = ENOENT; + return -1; +} + +static char *cephmount_get_cookie(TALLOC_CTX * mem_ctx, const int snum) +{ + const char *conf_file = + lp_parm_const_string(snum, "ceph", "config_file", "."); + const char *user_id = lp_parm_const_string(snum, "ceph", "user_id", ""); + const char *fsname = + lp_parm_const_string(snum, "ceph", "filesystem", ""); + return talloc_asprintf(mem_ctx, "(%s/%s/%s)", conf_file, user_id, + fsname); +} + +static int cephmount_select_fs(struct ceph_mount_info *mnt, const char *fsname) +{ + /* + * ceph_select_filesystem was added in ceph 'nautilus' (v14). + * Earlier versions of libcephfs will lack that API function. + * At the time of this writing (Feb 2023) all versions of ceph + * supported by ceph upstream have this function. + */ +#if defined(HAVE_CEPH_SELECT_FILESYSTEM) + DBG_DEBUG("[CEPH] calling: ceph_select_filesystem with %s\n", fsname); + return ceph_select_filesystem(mnt, fsname); +#else + DBG_ERR("[CEPH] ceph_select_filesystem not available\n"); + return -ENOTSUP; +#endif +} + +static struct ceph_mount_info *cephmount_mount_fs(const int snum) +{ + int ret; + char buf[256]; + struct ceph_mount_info *mnt = NULL; + /* if config_file and/or user_id are NULL, ceph will use defaults */ + const char *conf_file = + lp_parm_const_string(snum, "ceph", "config_file", NULL); + const char *user_id = + lp_parm_const_string(snum, "ceph", "user_id", NULL); + const char *fsname = + lp_parm_const_string(snum, "ceph", "filesystem", NULL); + + DBG_DEBUG("[CEPH] calling: ceph_create\n"); + ret = ceph_create(&mnt, user_id); + if (ret) { + errno = -ret; + return NULL; + } + + DBG_DEBUG("[CEPH] calling: ceph_conf_read_file with %s\n", + (conf_file == NULL ? "default path" : conf_file)); + ret = ceph_conf_read_file(mnt, conf_file); + if (ret) { + goto err_cm_release; + } + + DBG_DEBUG("[CEPH] calling: ceph_conf_get\n"); + ret = ceph_conf_get(mnt, "log file", buf, sizeof(buf)); + if (ret < 0) { + goto err_cm_release; + } + + /* libcephfs disables POSIX ACL support by default, enable it... */ + ret = ceph_conf_set(mnt, "client_acl_type", "posix_acl"); + if (ret < 0) { + goto err_cm_release; + } + /* tell libcephfs to perform local permission checks */ + ret = ceph_conf_set(mnt, "fuse_default_permissions", "false"); + if (ret < 0) { + goto err_cm_release; + } + /* + * select a cephfs file system to use: + * In ceph, multiple file system support has been stable since 'pacific'. + * Permit different shares to access different file systems. + */ + if (fsname != NULL) { + ret = cephmount_select_fs(mnt, fsname); + if (ret < 0) { + goto err_cm_release; + } + } + + DBG_DEBUG("[CEPH] calling: ceph_mount\n"); + ret = ceph_mount(mnt, NULL); + if (ret >= 0) { + goto cm_done; + } + + err_cm_release: + ceph_release(mnt); + mnt = NULL; + DBG_DEBUG("[CEPH] Error mounting fs: %s\n", strerror(-ret)); + cm_done: + /* + * Handle the error correctly. Ceph returns -errno. + */ + if (ret) { + errno = -ret; + } + return mnt; +} + +/* Check for NULL pointer parameters in cephwrap_* functions */ + +/* We don't want to have NULL function pointers lying around. Someone + is sure to try and execute them. These stubs are used to prevent + this possibility. */ + +static int cephwrap_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + int ret = 0; + struct ceph_mount_info *cmount = NULL; + int snum = SNUM(handle->conn); + char *cookie = cephmount_get_cookie(handle, snum); + if (cookie == NULL) { + return -1; + } + + cmount = cephmount_cache_update(cookie); + if (cmount != NULL) { + goto connect_ok; + } + + cmount = cephmount_mount_fs(snum); + if (cmount == NULL) { + ret = -1; + goto connect_fail; + } + ret = cephmount_cache_add(cookie, cmount); + if (ret) { + goto connect_fail; + } + + connect_ok: + handle->data = cmount; + DBG_WARNING("Connection established with the server: %s\n", cookie); + /* + * Unless we have an async implementation of getxattrat turn this off. + */ + lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false"); + connect_fail: + talloc_free(cookie); + return ret; +} + +static void cephwrap_disconnect(struct vfs_handle_struct *handle) +{ + int ret = cephmount_cache_remove(handle->data); + if (ret < 0) { + DBG_ERR("failed to remove ceph mount from cache: %s\n", + strerror(errno)); + return; + } + if (ret > 0) { + DBG_DEBUG("mount cache entry still in use\n"); + return; + } + + ret = ceph_unmount(handle->data); + if (ret < 0) { + DBG_ERR("[CEPH] failed to unmount: %s\n", strerror(-ret)); + } + + ret = ceph_release(handle->data); + if (ret < 0) { + DBG_ERR("[CEPH] failed to release: %s\n", strerror(-ret)); + } + handle->data = NULL; +} + +/* Disk operations */ + +static uint64_t cephwrap_disk_free(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + struct statvfs statvfs_buf = { 0 }; + int ret; + + if (!(ret = ceph_statfs(handle->data, smb_fname->base_name, + &statvfs_buf))) { + /* + * Provide all the correct values. + */ + *bsize = statvfs_buf.f_bsize; + *dfree = statvfs_buf.f_bavail; + *dsize = statvfs_buf.f_blocks; + DBG_DEBUG("[CEPH] bsize: %llu, dfree: %llu, dsize: %llu\n", + llu(*bsize), llu(*dfree), llu(*dsize)); + return *dfree; + } else { + DBG_DEBUG("[CEPH] ceph_statfs returned %d\n", ret); + WRAP_RETURN(ret); + } +} + +static int cephwrap_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ + /* libcephfs: Ceph does not implement this */ +#if 0 +/* was ifdef HAVE_SYS_QUOTAS */ + int ret; + + ret = ceph_get_quota(handle->conn->connectpath, qtype, id, qt); + + if (ret) { + errno = -ret; + ret = -1; + } + + return ret; +#else + errno = ENOSYS; + return -1; +#endif +} + +static int cephwrap_set_quota(struct vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt) +{ + /* libcephfs: Ceph does not implement this */ +#if 0 +/* was ifdef HAVE_SYS_QUOTAS */ + int ret; + + ret = ceph_set_quota(handle->conn->connectpath, qtype, id, qt); + if (ret) { + errno = -ret; + ret = -1; + } + + return ret; +#else + WRAP_RETURN(-ENOSYS); +#endif +} + +static int cephwrap_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + struct statvfs statvfs_buf = { 0 }; + int ret; + + ret = ceph_statfs(handle->data, smb_fname->base_name, &statvfs_buf); + if (ret < 0) { + WRAP_RETURN(ret); + } + + statbuf->OptimalTransferSize = statvfs_buf.f_frsize; + statbuf->BlockSize = statvfs_buf.f_bsize; + statbuf->TotalBlocks = statvfs_buf.f_blocks; + statbuf->BlocksAvail = statvfs_buf.f_bfree; + statbuf->UserBlocksAvail = statvfs_buf.f_bavail; + statbuf->TotalFileNodes = statvfs_buf.f_files; + statbuf->FreeFileNodes = statvfs_buf.f_ffree; + statbuf->FsIdentifier = statvfs_buf.f_fsid; + DBG_DEBUG("[CEPH] f_bsize: %ld, f_blocks: %ld, f_bfree: %ld, f_bavail: %ld\n", + (long int)statvfs_buf.f_bsize, (long int)statvfs_buf.f_blocks, + (long int)statvfs_buf.f_bfree, (long int)statvfs_buf.f_bavail); + + return ret; +} + +static uint32_t cephwrap_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES; + + *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER; + + return caps; +} + +/* Directory operations */ + +static DIR *cephwrap_fdopendir(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *mask, + uint32_t attributes) +{ + int ret = 0; + struct ceph_dir_result *result = NULL; + +#ifdef HAVE_CEPH_FDOPENDIR + int dirfd = fsp_get_io_fd(fsp); + DBG_DEBUG("[CEPH] fdopendir(%p, %d)\n", handle, dirfd); + ret = ceph_fdopendir(handle->data, dirfd, &result); +#else + DBG_DEBUG("[CEPH] fdopendir(%p, %p)\n", handle, fsp); + ret = ceph_opendir(handle->data, fsp->fsp_name->base_name, &result); +#endif + if (ret < 0) { + result = NULL; + errno = -ret; /* We return result which is NULL in this case */ + } + + DBG_DEBUG("[CEPH] fdopendir(...) = %d\n", ret); + return (DIR *) result; +} + +static struct dirent *cephwrap_readdir(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + struct dirent *result = NULL; + + DBG_DEBUG("[CEPH] readdir(%p, %p)\n", handle, dirp); + result = ceph_readdir(handle->data, (struct ceph_dir_result *) dirp); + DBG_DEBUG("[CEPH] readdir(...) = %p\n", result); + + return result; +} + +static void cephwrap_rewinddir(struct vfs_handle_struct *handle, DIR *dirp) +{ + DBG_DEBUG("[CEPH] rewinddir(%p, %p)\n", handle, dirp); + ceph_rewinddir(handle->data, (struct ceph_dir_result *) dirp); +} + +static int cephwrap_mkdirat(struct vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + int result = -1; +#ifdef HAVE_CEPH_MKDIRAT + int dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] mkdirat(%p, %d, %s)\n", + handle, + dirfd, + smb_fname->base_name); + + result = ceph_mkdirat(handle->data, dirfd, smb_fname->base_name, mode); + + DBG_DEBUG("[CEPH] mkdirat(...) = %d\n", result); + + WRAP_RETURN(result); +#else + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DBG_DEBUG("[CEPH] mkdir(%p, %s)\n", + handle, smb_fname_str_dbg(full_fname)); + + result = ceph_mkdir(handle->data, full_fname->base_name, mode); + + TALLOC_FREE(full_fname); + + WRAP_RETURN(result); +#endif +} + +static int cephwrap_closedir(struct vfs_handle_struct *handle, DIR *dirp) +{ + int result; + + DBG_DEBUG("[CEPH] closedir(%p, %p)\n", handle, dirp); + result = ceph_closedir(handle->data, (struct ceph_dir_result *) dirp); + DBG_DEBUG("[CEPH] closedir(...) = %d\n", result); + WRAP_RETURN(result); +} + +/* File operations */ + +static int cephwrap_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int flags = how->flags; + mode_t mode = how->mode; + struct smb_filename *name = NULL; + bool have_opath = false; + bool became_root = false; + int result = -ENOENT; +#ifdef HAVE_CEPH_OPENAT + int dirfd = -1; +#endif + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + if (smb_fname->stream_name) { + goto out; + } + +#ifdef O_PATH + have_opath = true; + if (fsp->fsp_flags.is_pathref) { + flags |= O_PATH; + } +#endif + +#ifdef HAVE_CEPH_OPENAT + dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] openat(%p, %d, %p, %d, %d)\n", + handle, dirfd, fsp, flags, mode); + + if (fsp->fsp_flags.is_pathref && !have_opath) { + become_root(); + became_root = true; + } + + result = ceph_openat(handle->data, + dirfd, + smb_fname->base_name, + flags, + mode); + +#else + if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) { + name = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (name == NULL) { + return -1; + } + smb_fname = name; + } + + DBG_DEBUG("[CEPH] openat(%p, %s, %p, %d, %d)\n", handle, + smb_fname_str_dbg(smb_fname), fsp, flags, mode); + + if (fsp->fsp_flags.is_pathref && !have_opath) { + become_root(); + became_root = true; + } + + result = ceph_open(handle->data, smb_fname->base_name, flags, mode); +#endif + if (became_root) { + unbecome_root(); + } +out: + TALLOC_FREE(name); + fsp->fsp_flags.have_proc_fds = false; + DBG_DEBUG("[CEPH] open(...) = %d\n", result); + WRAP_RETURN(result); +} + +static int cephwrap_close(struct vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + + DBG_DEBUG("[CEPH] close(%p, %p)\n", handle, fsp); + result = ceph_close(handle->data, fsp_get_pathref_fd(fsp)); + DBG_DEBUG("[CEPH] close(...) = %d\n", result); + + WRAP_RETURN(result); +} + +static ssize_t cephwrap_pread(struct vfs_handle_struct *handle, files_struct *fsp, void *data, + size_t n, off_t offset) +{ + ssize_t result; + + DBG_DEBUG("[CEPH] pread(%p, %p, %p, %llu, %llu)\n", handle, fsp, data, llu(n), llu(offset)); + + result = ceph_read(handle->data, fsp_get_io_fd(fsp), data, n, offset); + DBG_DEBUG("[CEPH] pread(...) = %llu\n", llu(result)); + WRAP_RETURN(result); +} + +struct cephwrap_pread_state { + ssize_t bytes_read; + struct vfs_aio_state vfs_aio_state; +}; + +/* + * Fake up an async ceph read by calling the synchronous API. + */ +static struct tevent_req *cephwrap_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct cephwrap_pread_state *state = NULL; + int ret = -1; + + DBG_DEBUG("[CEPH] %s\n", __func__); + req = tevent_req_create(mem_ctx, &state, struct cephwrap_pread_state); + if (req == NULL) { + return NULL; + } + + ret = ceph_read(handle->data, fsp_get_io_fd(fsp), data, n, offset); + if (ret < 0) { + /* ceph returns -errno on error. */ + tevent_req_error(req, -ret); + return tevent_req_post(req, ev); + } + + state->bytes_read = ret; + tevent_req_done(req); + /* Return and schedule the completion of the call. */ + return tevent_req_post(req, ev); +} + +static ssize_t cephwrap_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct cephwrap_pread_state *state = + tevent_req_data(req, struct cephwrap_pread_state); + + DBG_DEBUG("[CEPH] %s\n", __func__); + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->bytes_read; +} + +static ssize_t cephwrap_pwrite(struct vfs_handle_struct *handle, files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + ssize_t result; + + DBG_DEBUG("[CEPH] pwrite(%p, %p, %p, %llu, %llu)\n", handle, fsp, data, llu(n), llu(offset)); + result = ceph_write(handle->data, fsp_get_io_fd(fsp), data, n, offset); + DBG_DEBUG("[CEPH] pwrite(...) = %llu\n", llu(result)); + WRAP_RETURN(result); +} + +struct cephwrap_pwrite_state { + ssize_t bytes_written; + struct vfs_aio_state vfs_aio_state; +}; + +/* + * Fake up an async ceph write by calling the synchronous API. + */ +static struct tevent_req *cephwrap_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct cephwrap_pwrite_state *state = NULL; + int ret = -1; + + DBG_DEBUG("[CEPH] %s\n", __func__); + req = tevent_req_create(mem_ctx, &state, struct cephwrap_pwrite_state); + if (req == NULL) { + return NULL; + } + + ret = ceph_write(handle->data, fsp_get_io_fd(fsp), data, n, offset); + if (ret < 0) { + /* ceph returns -errno on error. */ + tevent_req_error(req, -ret); + return tevent_req_post(req, ev); + } + + state->bytes_written = ret; + tevent_req_done(req); + /* Return and schedule the completion of the call. */ + return tevent_req_post(req, ev); +} + +static ssize_t cephwrap_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct cephwrap_pwrite_state *state = + tevent_req_data(req, struct cephwrap_pwrite_state); + + DBG_DEBUG("[CEPH] %s\n", __func__); + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->bytes_written; +} + +static off_t cephwrap_lseek(struct vfs_handle_struct *handle, files_struct *fsp, off_t offset, int whence) +{ + off_t result = 0; + + DBG_DEBUG("[CEPH] cephwrap_lseek\n"); + result = ceph_lseek(handle->data, fsp_get_io_fd(fsp), offset, whence); + WRAP_RETURN(result); +} + +static ssize_t cephwrap_sendfile(struct vfs_handle_struct *handle, int tofd, files_struct *fromfsp, const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + /* + * We cannot support sendfile because libcephfs is in user space. + */ + DBG_DEBUG("[CEPH] cephwrap_sendfile\n"); + errno = ENOTSUP; + return -1; +} + +static ssize_t cephwrap_recvfile(struct vfs_handle_struct *handle, + int fromfd, + files_struct *tofsp, + off_t offset, + size_t n) +{ + /* + * We cannot support recvfile because libcephfs is in user space. + */ + DBG_DEBUG("[CEPH] cephwrap_recvfile\n"); + errno=ENOTSUP; + return -1; +} + +static int cephwrap_renameat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + int result = -1; + + DBG_DEBUG("[CEPH] cephwrap_renameat\n"); + if (smb_fname_src->stream_name || smb_fname_dst->stream_name) { + errno = ENOENT; + return result; + } + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + return -1; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return -1; + } + + result = ceph_rename(handle->data, + full_fname_src->base_name, + full_fname_dst->base_name); + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + + WRAP_RETURN(result); +} + +/* + * Fake up an async ceph fsync by calling the synchronous API. + */ + +static struct tevent_req *cephwrap_fsync_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + files_struct *fsp) +{ + struct tevent_req *req = NULL; + struct vfs_aio_state *state = NULL; + int ret = -1; + + DBG_DEBUG("[CEPH] cephwrap_fsync_send\n"); + + req = tevent_req_create(mem_ctx, &state, struct vfs_aio_state); + if (req == NULL) { + return NULL; + } + + /* Make sync call. */ + ret = ceph_fsync(handle->data, fsp_get_io_fd(fsp), false); + + if (ret != 0) { + /* ceph_fsync returns -errno on error. */ + tevent_req_error(req, -ret); + return tevent_req_post(req, ev); + } + + /* Mark it as done. */ + tevent_req_done(req); + /* Return and schedule the completion of the call. */ + return tevent_req_post(req, ev); +} + +static int cephwrap_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_aio_state *state = + tevent_req_data(req, struct vfs_aio_state); + + DBG_DEBUG("[CEPH] cephwrap_fsync_recv\n"); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = *state; + return 0; +} + +#define SAMBA_STATX_ATTR_MASK (CEPH_STATX_BASIC_STATS|CEPH_STATX_BTIME) + +static void init_stat_ex_from_ceph_statx(struct stat_ex *dst, const struct ceph_statx *stx) +{ + DBG_DEBUG("[CEPH]\tstx = {dev = %llx, ino = %llu, mode = 0x%x, " + "nlink = %llu, uid = %d, gid = %d, rdev = %llx, size = %llu, " + "blksize = %llu, blocks = %llu, atime = %llu, mtime = %llu, " + "ctime = %llu, btime = %llu}\n", + llu(stx->stx_dev), llu(stx->stx_ino), stx->stx_mode, + llu(stx->stx_nlink), stx->stx_uid, stx->stx_gid, + llu(stx->stx_rdev), llu(stx->stx_size), llu(stx->stx_blksize), + llu(stx->stx_blocks), llu(stx->stx_atime.tv_sec), + llu(stx->stx_mtime.tv_sec), llu(stx->stx_ctime.tv_sec), + llu(stx->stx_btime.tv_sec)); + + if ((stx->stx_mask & SAMBA_STATX_ATTR_MASK) != SAMBA_STATX_ATTR_MASK) { + DBG_WARNING("%s: stx->stx_mask is incorrect (wanted %x, got %x)\n", + __func__, SAMBA_STATX_ATTR_MASK, stx->stx_mask); + } + + dst->st_ex_dev = stx->stx_dev; + dst->st_ex_rdev = stx->stx_rdev; + dst->st_ex_ino = stx->stx_ino; + dst->st_ex_mode = stx->stx_mode; + dst->st_ex_uid = stx->stx_uid; + dst->st_ex_gid = stx->stx_gid; + dst->st_ex_size = stx->stx_size; + dst->st_ex_nlink = stx->stx_nlink; + dst->st_ex_atime = stx->stx_atime; + dst->st_ex_btime = stx->stx_btime; + dst->st_ex_ctime = stx->stx_ctime; + dst->st_ex_mtime = stx->stx_mtime; + dst->st_ex_blksize = stx->stx_blksize; + dst->st_ex_blocks = stx->stx_blocks; +} + +static int cephwrap_stat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result = -1; + struct ceph_statx stx = { 0 }; + + DBG_DEBUG("[CEPH] stat(%p, %s)\n", handle, smb_fname_str_dbg(smb_fname)); + + if (smb_fname->stream_name) { + errno = ENOENT; + return result; + } + + result = ceph_statx(handle->data, smb_fname->base_name, &stx, + SAMBA_STATX_ATTR_MASK, 0); + DBG_DEBUG("[CEPH] statx(...) = %d\n", result); + if (result < 0) { + WRAP_RETURN(result); + } + + init_stat_ex_from_ceph_statx(&smb_fname->st, &stx); + DBG_DEBUG("[CEPH] mode = 0x%x\n", smb_fname->st.st_ex_mode); + return result; +} + +static int cephwrap_fstat(struct vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int result = -1; + struct ceph_statx stx = { 0 }; + int fd = fsp_get_pathref_fd(fsp); + + DBG_DEBUG("[CEPH] fstat(%p, %d)\n", handle, fd); + result = ceph_fstatx(handle->data, fd, &stx, + SAMBA_STATX_ATTR_MASK, 0); + DBG_DEBUG("[CEPH] fstat(...) = %d\n", result); + if (result < 0) { + WRAP_RETURN(result); + } + + init_stat_ex_from_ceph_statx(sbuf, &stx); + DBG_DEBUG("[CEPH] mode = 0x%x\n", sbuf->st_ex_mode); + return result; +} + +static int cephwrap_fstatat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + int result = -1; + struct ceph_statx stx = { 0 }; +#ifdef HAVE_CEPH_STATXAT + int dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] fstatat(%p, %d, %s)\n", + handle, dirfd, smb_fname->base_name); + result = ceph_statxat(handle->data, dirfd, smb_fname->base_name, + &stx, SAMBA_STATX_ATTR_MASK, 0); +#else + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + DBG_DEBUG("[CEPH] fstatat(%p, %s)\n", + handle, smb_fname_str_dbg(full_fname)); + result = ceph_statx(handle->data, full_fname->base_name, + &stx, SAMBA_STATX_ATTR_MASK, 0); + + TALLOC_FREE(full_fname); +#endif + + DBG_DEBUG("[CEPH] fstatat(...) = %d\n", result); + if (result < 0) { + WRAP_RETURN(result); + } + + init_stat_ex_from_ceph_statx(sbuf, &stx); + DBG_DEBUG("[CEPH] mode = 0x%x\n", sbuf->st_ex_mode); + + return 0; +} + +static int cephwrap_lstat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result = -1; + struct ceph_statx stx = { 0 }; + + DBG_DEBUG("[CEPH] lstat(%p, %s)\n", handle, smb_fname_str_dbg(smb_fname)); + + if (smb_fname->stream_name) { + errno = ENOENT; + return result; + } + + result = ceph_statx(handle->data, smb_fname->base_name, &stx, + SAMBA_STATX_ATTR_MASK, AT_SYMLINK_NOFOLLOW); + DBG_DEBUG("[CEPH] lstat(...) = %d\n", result); + if (result < 0) { + WRAP_RETURN(result); + } + + init_stat_ex_from_ceph_statx(&smb_fname->st, &stx); + return result; +} + +static int cephwrap_fntimes(struct vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + struct ceph_statx stx = { 0 }; + int result; + int mask = 0; + + if (!is_omit_timespec(&ft->atime)) { + stx.stx_atime = ft->atime; + mask |= CEPH_SETATTR_ATIME; + } + if (!is_omit_timespec(&ft->mtime)) { + stx.stx_mtime = ft->mtime; + mask |= CEPH_SETATTR_MTIME; + } + if (!is_omit_timespec(&ft->create_time)) { + stx.stx_btime = ft->create_time; + mask |= CEPH_SETATTR_BTIME; + } + + if (!mask) { + return 0; + } + + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to set xattrs. + */ + result = ceph_fsetattrx(handle->data, + fsp_get_io_fd(fsp), + &stx, + mask); + } else { + /* + * This is no longer a handle based call. + */ + result = ceph_setattrx(handle->data, + fsp->fsp_name->base_name, + &stx, + mask, + 0); + } + + DBG_DEBUG("[CEPH] ntimes(%p, %s, {%ld, %ld, %ld, %ld}) = %d\n", + handle, fsp_str_dbg(fsp), ft->mtime.tv_sec, ft->atime.tv_sec, + ft->ctime.tv_sec, ft->create_time.tv_sec, result); + + return result; +} + +static int cephwrap_unlinkat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int result = -1; +#ifdef HAVE_CEPH_UNLINKAT + int dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] unlinkat(%p, %d, %s)\n", + handle, + dirfd, + smb_fname_str_dbg(smb_fname)); + + if (smb_fname->stream_name) { + errno = ENOENT; + return result; + } + + result = ceph_unlinkat(handle->data, + dirfd, + smb_fname->base_name, + flags); + DBG_DEBUG("[CEPH] unlinkat(...) = %d\n", result); + WRAP_RETURN(result); +#else + struct smb_filename *full_fname = NULL; + + DBG_DEBUG("[CEPH] unlink(%p, %s)\n", + handle, + smb_fname_str_dbg(smb_fname)); + + if (smb_fname->stream_name) { + errno = ENOENT; + return result; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + if (flags & AT_REMOVEDIR) { + result = ceph_rmdir(handle->data, full_fname->base_name); + } else { + result = ceph_unlink(handle->data, full_fname->base_name); + } + TALLOC_FREE(full_fname); + DBG_DEBUG("[CEPH] unlink(...) = %d\n", result); + WRAP_RETURN(result); +#endif +} + +static int cephwrap_fchmod(struct vfs_handle_struct *handle, files_struct *fsp, mode_t mode) +{ + int result; + + DBG_DEBUG("[CEPH] fchmod(%p, %p, %d)\n", handle, fsp, mode); + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to change permissions. + */ + result = ceph_fchmod(handle->data, fsp_get_io_fd(fsp), mode); + } else { + /* + * This is no longer a handle based call. + */ + result = ceph_chmod(handle->data, + fsp->fsp_name->base_name, + mode); + } + DBG_DEBUG("[CEPH] fchmod(...) = %d\n", result); + WRAP_RETURN(result); +} + +static int cephwrap_fchown(struct vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid) +{ + int result; + + DBG_DEBUG("[CEPH] fchown(%p, %p, %d, %d)\n", handle, fsp, uid, gid); + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to change ownership. + */ + result = ceph_fchown(handle->data, + fsp_get_io_fd(fsp), + uid, + gid); + } else { + /* + * This is no longer a handle based call. + */ + result = ceph_chown(handle->data, + fsp->fsp_name->base_name, + uid, + gid); + } + + DBG_DEBUG("[CEPH] fchown(...) = %d\n", result); + WRAP_RETURN(result); +} + +static int cephwrap_lchown(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int result; + DBG_DEBUG("[CEPH] lchown(%p, %s, %d, %d)\n", handle, smb_fname->base_name, uid, gid); + result = ceph_lchown(handle->data, smb_fname->base_name, uid, gid); + DBG_DEBUG("[CEPH] lchown(...) = %d\n", result); + WRAP_RETURN(result); +} + +static int cephwrap_chdir(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int result = -1; + DBG_DEBUG("[CEPH] chdir(%p, %s)\n", handle, smb_fname->base_name); + result = ceph_chdir(handle->data, smb_fname->base_name); + DBG_DEBUG("[CEPH] chdir(...) = %d\n", result); + WRAP_RETURN(result); +} + +static struct smb_filename *cephwrap_getwd(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + const char *cwd = ceph_getcwd(handle->data); + DBG_DEBUG("[CEPH] getwd(%p) = %s\n", handle, cwd); + return synthetic_smb_fname(ctx, + cwd, + NULL, + NULL, + 0, + 0); +} + +static int strict_allocate_ftruncate(struct vfs_handle_struct *handle, files_struct *fsp, off_t len) +{ + off_t space_to_write; + int result; + NTSTATUS status; + SMB_STRUCT_STAT *pst; + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + pst = &fsp->fsp_name->st; + +#ifdef S_ISFIFO + if (S_ISFIFO(pst->st_ex_mode)) + return 0; +#endif + + if (pst->st_ex_size == len) + return 0; + + /* Shrink - just ftruncate. */ + if (pst->st_ex_size > len) { + result = ceph_ftruncate(handle->data, fsp_get_io_fd(fsp), len); + WRAP_RETURN(result); + } + + space_to_write = len - pst->st_ex_size; + result = ceph_fallocate(handle->data, fsp_get_io_fd(fsp), 0, pst->st_ex_size, + space_to_write); + WRAP_RETURN(result); +} + +static int cephwrap_ftruncate(struct vfs_handle_struct *handle, files_struct *fsp, off_t len) +{ + int result = -1; + + DBG_DEBUG("[CEPH] ftruncate(%p, %p, %llu\n", handle, fsp, llu(len)); + + if (lp_strict_allocate(SNUM(fsp->conn))) { + return strict_allocate_ftruncate(handle, fsp, len); + } + + result = ceph_ftruncate(handle->data, fsp_get_io_fd(fsp), len); + WRAP_RETURN(result); +} + +static int cephwrap_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + int result; + + DBG_DEBUG("[CEPH] fallocate(%p, %p, %u, %llu, %llu\n", + handle, fsp, mode, llu(offset), llu(len)); + /* unsupported mode flags are rejected by libcephfs */ + result = ceph_fallocate(handle->data, fsp_get_io_fd(fsp), mode, offset, len); + DBG_DEBUG("[CEPH] fallocate(...) = %d\n", result); + WRAP_RETURN(result); +} + +static bool cephwrap_lock(struct vfs_handle_struct *handle, files_struct *fsp, int op, off_t offset, off_t count, int type) +{ + DBG_DEBUG("[CEPH] lock\n"); + return true; +} + +static int cephwrap_filesystem_sharemode(struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + DBG_ERR("[CEPH] filesystem sharemodes unsupported! Consider setting " + "\"kernel share modes = no\"\n"); + + errno = ENOSYS; + return -1; +} + +static int cephwrap_fcntl(vfs_handle_struct *handle, + files_struct *fsp, int cmd, va_list cmd_arg) +{ + /* + * SMB_VFS_FCNTL() is currently only called by vfs_set_blocking() to + * clear O_NONBLOCK, etc for LOCK_MAND and FIFOs. Ignore it. + */ + if (cmd == F_GETFL) { + return 0; + } else if (cmd == F_SETFL) { + va_list dup_cmd_arg; + int opt; + + va_copy(dup_cmd_arg, cmd_arg); + opt = va_arg(dup_cmd_arg, int); + va_end(dup_cmd_arg); + if (opt == 0) { + return 0; + } + DBG_ERR("unexpected fcntl SETFL(%d)\n", opt); + goto err_out; + } + DBG_ERR("unexpected fcntl: %d\n", cmd); +err_out: + errno = EINVAL; + return -1; +} + +static bool cephwrap_getlock(struct vfs_handle_struct *handle, files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid) +{ + DBG_DEBUG("[CEPH] getlock returning false and errno=0\n"); + + errno = 0; + return false; +} + +/* + * We cannot let this fall through to the default, because the file might only + * be accessible from libcephfs (which is a user-space client) but the fd might + * be for some file the kernel knows about. + */ +static int cephwrap_linux_setlease(struct vfs_handle_struct *handle, files_struct *fsp, + int leasetype) +{ + int result = -1; + + DBG_DEBUG("[CEPH] linux_setlease\n"); + errno = ENOSYS; + return result; +} + +static int cephwrap_symlinkat(struct vfs_handle_struct *handle, + const struct smb_filename *link_target, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int result = -1; +#ifdef HAVE_CEPH_SYMLINKAT + int dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] symlinkat(%p, %s, %d, %s)\n", + handle, + link_target->base_name, + dirfd, + new_smb_fname->base_name); + + result = ceph_symlinkat(handle->data, + link_target->base_name, + dirfd, + new_smb_fname->base_name); + DBG_DEBUG("[CEPH] symlinkat(...) = %d\n", result); + WRAP_RETURN(result); +#else + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + return -1; + } + + DBG_DEBUG("[CEPH] symlink(%p, %s, %s)\n", handle, + link_target->base_name, + full_fname->base_name); + + result = ceph_symlink(handle->data, + link_target->base_name, + full_fname->base_name); + TALLOC_FREE(full_fname); + DBG_DEBUG("[CEPH] symlink(...) = %d\n", result); + WRAP_RETURN(result); +#endif +} + +static int cephwrap_readlinkat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + int result = -1; +#ifdef HAVE_CEPH_READLINKAT + int dirfd = fsp_get_pathref_fd(dirfsp); + + DBG_DEBUG("[CEPH] readlinkat(%p, %d, %s, %p, %llu)\n", + handle, + dirfd, + smb_fname->base_name, + buf, + llu(bufsiz)); + + result = ceph_readlinkat(handle->data, + dirfd, + smb_fname->base_name, + buf, + bufsiz); + + DBG_DEBUG("[CEPH] readlinkat(...) = %d\n", result); + WRAP_RETURN(result); +#else + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DBG_DEBUG("[CEPH] readlink(%p, %s, %p, %llu)\n", handle, + full_fname->base_name, buf, llu(bufsiz)); + + result = ceph_readlink(handle->data, full_fname->base_name, buf, bufsiz); + TALLOC_FREE(full_fname); + DBG_DEBUG("[CEPH] readlink(...) = %d\n", result); + WRAP_RETURN(result); +#endif +} + +static int cephwrap_linkat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + struct smb_filename *full_fname_old = NULL; + struct smb_filename *full_fname_new = NULL; + int result = -1; + + full_fname_old = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (full_fname_old == NULL) { + return -1; + } + full_fname_new = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (full_fname_new == NULL) { + TALLOC_FREE(full_fname_old); + return -1; + } + + DBG_DEBUG("[CEPH] link(%p, %s, %s)\n", handle, + full_fname_old->base_name, + full_fname_new->base_name); + + result = ceph_link(handle->data, + full_fname_old->base_name, + full_fname_new->base_name); + DBG_DEBUG("[CEPH] link(...) = %d\n", result); + TALLOC_FREE(full_fname_old); + TALLOC_FREE(full_fname_new); + WRAP_RETURN(result); +} + +static int cephwrap_mknodat(struct vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + struct smb_filename *full_fname = NULL; + int result = -1; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DBG_DEBUG("[CEPH] mknodat(%p, %s)\n", handle, full_fname->base_name); + result = ceph_mknod(handle->data, full_fname->base_name, mode, dev); + DBG_DEBUG("[CEPH] mknodat(...) = %d\n", result); + + TALLOC_FREE(full_fname); + + WRAP_RETURN(result); +} + +/* + * This is a simple version of real-path ... a better version is needed to + * ask libcephfs about symbolic links. + */ +static struct smb_filename *cephwrap_realpath(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + char *result = NULL; + const char *path = smb_fname->base_name; + size_t len = strlen(path); + struct smb_filename *result_fname = NULL; + int r = -1; + + if (len && (path[0] == '/')) { + r = asprintf(&result, "%s", path); + } else if ((len >= 2) && (path[0] == '.') && (path[1] == '/')) { + if (len == 2) { + r = asprintf(&result, "%s", + handle->conn->cwd_fsp->fsp_name->base_name); + } else { + r = asprintf(&result, "%s/%s", + handle->conn->cwd_fsp->fsp_name->base_name, &path[2]); + } + } else { + r = asprintf(&result, "%s/%s", + handle->conn->cwd_fsp->fsp_name->base_name, path); + } + + if (r < 0) { + return NULL; + } + + DBG_DEBUG("[CEPH] realpath(%p, %s) = %s\n", handle, path, result); + result_fname = synthetic_smb_fname(ctx, + result, + NULL, + NULL, + 0, + 0); + SAFE_FREE(result); + return result_fname; +} + + +static int cephwrap_fchflags(struct vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + errno = ENOSYS; + return -1; +} + +static NTSTATUS cephwrap_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + /* + * Don't fall back to get_real_filename so callers can differentiate + * between a full directory scan and an actual case-insensitive stat. + */ + return NT_STATUS_NOT_SUPPORTED; +} + +static const char *cephwrap_connectpath( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + return handle->conn->connectpath; +} + +/**************************************************************** + Extended attribute operations. +*****************************************************************/ + +static ssize_t cephwrap_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + void *value, + size_t size) +{ + int ret; + DBG_DEBUG("[CEPH] fgetxattr(%p, %p, %s, %p, %llu)\n", + handle, + fsp, + name, + value, + llu(size)); + if (!fsp->fsp_flags.is_pathref) { + ret = ceph_fgetxattr(handle->data, + fsp_get_io_fd(fsp), + name, + value, + size); + } else { + ret = ceph_getxattr(handle->data, + fsp->fsp_name->base_name, + name, + value, + size); + } + DBG_DEBUG("[CEPH] fgetxattr(...) = %d\n", ret); + if (ret < 0) { + WRAP_RETURN(ret); + } + return (ssize_t)ret; +} + +static ssize_t cephwrap_flistxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size) +{ + int ret; + DBG_DEBUG("[CEPH] flistxattr(%p, %p, %p, %llu)\n", + handle, fsp, list, llu(size)); + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to list xattrs. + */ + ret = ceph_flistxattr(handle->data, + fsp_get_io_fd(fsp), + list, + size); + } else { + /* + * This is no longer a handle based call. + */ + ret = ceph_listxattr(handle->data, + fsp->fsp_name->base_name, + list, + size); + } + DBG_DEBUG("[CEPH] flistxattr(...) = %d\n", ret); + if (ret < 0) { + WRAP_RETURN(ret); + } + return (ssize_t)ret; +} + +static int cephwrap_fremovexattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name) +{ + int ret; + DBG_DEBUG("[CEPH] fremovexattr(%p, %p, %s)\n", handle, fsp, name); + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to remove xattrs. + */ + ret = ceph_fremovexattr(handle->data, fsp_get_io_fd(fsp), name); + } else { + /* + * This is no longer a handle based call. + */ + ret = ceph_removexattr(handle->data, + fsp->fsp_name->base_name, + name); + } + DBG_DEBUG("[CEPH] fremovexattr(...) = %d\n", ret); + WRAP_RETURN(ret); +} + +static int cephwrap_fsetxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags) +{ + int ret; + DBG_DEBUG("[CEPH] fsetxattr(%p, %p, %s, %p, %llu, %d)\n", handle, fsp, name, value, llu(size), flags); + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to set xattrs. + */ + ret = ceph_fsetxattr(handle->data, + fsp_get_io_fd(fsp), + name, + value, + size, + flags); + } else { + /* + * This is no longer a handle based call. + */ + ret = ceph_setxattr(handle->data, + fsp->fsp_name->base_name, + name, + value, + size, + flags); + } + DBG_DEBUG("[CEPH] fsetxattr(...) = %d\n", ret); + WRAP_RETURN(ret); +} + +static bool cephwrap_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp) +{ + + /* + * We do not support AIO yet. + */ + + DBG_DEBUG("[CEPH] cephwrap_aio_force(%p, %p) = false (errno = ENOTSUP)\n", handle, fsp); + errno = ENOTSUP; + return false; +} + +static NTSTATUS cephwrap_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NO_MEMORY; + int ret; + char *msdfs_link = NULL; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + goto out; + } + + /* Form the msdfs_link contents */ + msdfs_link = msdfs_link_string(frame, + reflist, + referral_count); + if (msdfs_link == NULL) { + goto out; + } + + ret = ceph_symlink(handle->data, + msdfs_link, + full_fname->base_name); + if (ret == 0) { + status = NT_STATUS_OK; + } else { + status = map_nt_error_from_unix(-ret); + } + + out: + + DBG_DEBUG("[CEPH] create_dfs_pathat(%s) = %s\n", + full_fname != NULL ? full_fname->base_name : "", + nt_errstr(status)); + + TALLOC_FREE(frame); + return status; +} + +/* + * Read and return the contents of a DFS redirect given a + * pathname. A caller can pass in NULL for ppreflist and + * preferral_count but still determine if this was a + * DFS redirect point by getting NT_STATUS_OK back + * without incurring the overhead of reading and parsing + * the referral contents. + */ + +static NTSTATUS cephwrap_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + NTSTATUS status = NT_STATUS_NO_MEMORY; + size_t bufsize; + char *link_target = NULL; + int referral_len; + bool ok; +#if defined(HAVE_BROKEN_READLINK) + char link_target_buf[PATH_MAX]; +#else + char link_target_buf[7]; +#endif + struct ceph_statx stx = { 0 }; + struct smb_filename *full_fname = NULL; + int ret; + + if (is_named_stream(smb_fname)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* + * We're only checking if this is a DFS + * redirect. We don't need to return data. + */ + bufsize = sizeof(link_target_buf); + link_target = link_target_buf; + } else { + bufsize = PATH_MAX; + link_target = talloc_array(mem_ctx, char, bufsize); + if (!link_target) { + goto err; + } + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err; + } + + ret = ceph_statx(handle->data, + full_fname->base_name, + &stx, + SAMBA_STATX_ATTR_MASK, + AT_SYMLINK_NOFOLLOW); + if (ret < 0) { + status = map_nt_error_from_unix(-ret); + goto err; + } + + referral_len = ceph_readlink(handle->data, + full_fname->base_name, + link_target, + bufsize - 1); + if (referral_len < 0) { + /* ceph errors are -errno. */ + if (-referral_len == EINVAL) { + DBG_INFO("%s is not a link.\n", + full_fname->base_name); + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + } else { + status = map_nt_error_from_unix(-referral_len); + DBG_ERR("Error reading " + "msdfs link %s: %s\n", + full_fname->base_name, + strerror(errno)); + } + goto err; + } + link_target[referral_len] = '\0'; + + DBG_INFO("%s -> %s\n", + full_fname->base_name, + link_target); + + if (!strnequal(link_target, "msdfs:", 6)) { + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* Early return for checking if this is a DFS link. */ + TALLOC_FREE(full_fname); + init_stat_ex_from_ceph_statx(&smb_fname->st, &stx); + return NT_STATUS_OK; + } + + ok = parse_msdfs_symlink(mem_ctx, + lp_msdfs_shuffle_referrals(SNUM(handle->conn)), + link_target, + ppreflist, + preferral_count); + + if (ok) { + init_stat_ex_from_ceph_statx(&smb_fname->st, &stx); + status = NT_STATUS_OK; + } else { + status = NT_STATUS_NO_MEMORY; + } + + err: + + if (link_target != link_target_buf) { + TALLOC_FREE(link_target); + } + TALLOC_FREE(full_fname); + return status; +} + +static struct vfs_fn_pointers ceph_fns = { + /* Disk operations */ + + .connect_fn = cephwrap_connect, + .disconnect_fn = cephwrap_disconnect, + .disk_free_fn = cephwrap_disk_free, + .get_quota_fn = cephwrap_get_quota, + .set_quota_fn = cephwrap_set_quota, + .statvfs_fn = cephwrap_statvfs, + .fs_capabilities_fn = cephwrap_fs_capabilities, + + /* Directory operations */ + + .fdopendir_fn = cephwrap_fdopendir, + .readdir_fn = cephwrap_readdir, + .rewind_dir_fn = cephwrap_rewinddir, + .mkdirat_fn = cephwrap_mkdirat, + .closedir_fn = cephwrap_closedir, + + /* File operations */ + + .create_dfs_pathat_fn = cephwrap_create_dfs_pathat, + .read_dfs_pathat_fn = cephwrap_read_dfs_pathat, + .openat_fn = cephwrap_openat, + .close_fn = cephwrap_close, + .pread_fn = cephwrap_pread, + .pread_send_fn = cephwrap_pread_send, + .pread_recv_fn = cephwrap_pread_recv, + .pwrite_fn = cephwrap_pwrite, + .pwrite_send_fn = cephwrap_pwrite_send, + .pwrite_recv_fn = cephwrap_pwrite_recv, + .lseek_fn = cephwrap_lseek, + .sendfile_fn = cephwrap_sendfile, + .recvfile_fn = cephwrap_recvfile, + .renameat_fn = cephwrap_renameat, + .fsync_send_fn = cephwrap_fsync_send, + .fsync_recv_fn = cephwrap_fsync_recv, + .stat_fn = cephwrap_stat, + .fstat_fn = cephwrap_fstat, + .lstat_fn = cephwrap_lstat, + .fstatat_fn = cephwrap_fstatat, + .unlinkat_fn = cephwrap_unlinkat, + .fchmod_fn = cephwrap_fchmod, + .fchown_fn = cephwrap_fchown, + .lchown_fn = cephwrap_lchown, + .chdir_fn = cephwrap_chdir, + .getwd_fn = cephwrap_getwd, + .fntimes_fn = cephwrap_fntimes, + .ftruncate_fn = cephwrap_ftruncate, + .fallocate_fn = cephwrap_fallocate, + .lock_fn = cephwrap_lock, + .filesystem_sharemode_fn = cephwrap_filesystem_sharemode, + .fcntl_fn = cephwrap_fcntl, + .linux_setlease_fn = cephwrap_linux_setlease, + .getlock_fn = cephwrap_getlock, + .symlinkat_fn = cephwrap_symlinkat, + .readlinkat_fn = cephwrap_readlinkat, + .linkat_fn = cephwrap_linkat, + .mknodat_fn = cephwrap_mknodat, + .realpath_fn = cephwrap_realpath, + .fchflags_fn = cephwrap_fchflags, + .get_real_filename_at_fn = cephwrap_get_real_filename_at, + .connectpath_fn = cephwrap_connectpath, + + /* EA operations. */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = cephwrap_fgetxattr, + .flistxattr_fn = cephwrap_flistxattr, + .fremovexattr_fn = cephwrap_fremovexattr, + .fsetxattr_fn = cephwrap_fsetxattr, + + /* Posix ACL Operations */ + .sys_acl_get_fd_fn = posixacl_xattr_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = posixacl_xattr_acl_set_fd, + .sys_acl_delete_def_fd_fn = posixacl_xattr_acl_delete_def_fd, + + /* aio operations */ + .aio_force_fn = cephwrap_aio_force, +}; + +static_decl_vfs; +NTSTATUS vfs_ceph_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "ceph", &ceph_fns); +} diff --git a/source3/modules/vfs_ceph_snapshots.c b/source3/modules/vfs_ceph_snapshots.c new file mode 100644 index 0000000..98b8f5f --- /dev/null +++ b/source3/modules/vfs_ceph_snapshots.c @@ -0,0 +1,1478 @@ +/* + * Module for accessing CephFS snapshots as Previous Versions. This module is + * separate to vfs_ceph, so that it can also be used atop a CephFS kernel backed + * share with vfs_default. + * + * Copyright (C) David Disseldorp 2019 + * + * 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 +#include +#include "includes.h" +#include "include/ntioctl.h" +#include "include/smb.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/smb_strtox.h" +#include "source3/smbd/dir.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* + * CephFS has a magic snapshots subdirectory in all parts of the directory tree. + * This module automatically makes all snapshots in this subdir visible to SMB + * clients (if permitted by corresponding access control). + */ +#define CEPH_SNAP_SUBDIR_DEFAULT ".snap" +/* + * The ceph.snap.btime (virtual) extended attribute carries the snapshot + * creation time in $secs.$nsecs format. It was added as part of + * https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions + * which don't provide this xattr will not be able to enumerate or access + * snapshots using this module. As an alternative, vfs_shadow_copy2 could be + * used instead, alongside special shadow:format snapshot directory names. + */ +#define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime" + +static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle, + struct files_struct *fsp, + time_t *_snap_secs) +{ + int ret; + char snap_btime[33]; + char *s = NULL; + char *endptr = NULL; + struct timespec snap_timespec; + int err; + + ret = SMB_VFS_NEXT_FGETXATTR(handle, + fsp, + CEPH_SNAP_BTIME_XATTR, + snap_btime, + sizeof(snap_btime)); + if (ret < 0) { + DBG_ERR("failed to get %s xattr: %s\n", + CEPH_SNAP_BTIME_XATTR, strerror(errno)); + return -errno; + } + + if (ret == 0 || ret >= sizeof(snap_btime) - 1) { + return -EINVAL; + } + + /* ensure zero termination */ + snap_btime[ret] = '\0'; + + /* format is sec.nsec */ + s = strchr(snap_btime, '.'); + if (s == NULL) { + DBG_ERR("invalid %s xattr value: %s\n", + CEPH_SNAP_BTIME_XATTR, snap_btime); + return -EINVAL; + } + + /* First component is seconds, extract it */ + *s = '\0'; + snap_timespec.tv_sec = smb_strtoull(snap_btime, + &endptr, + 10, + &err, + SMB_STR_FULL_STR_CONV); + if (err != 0) { + return -err; + } + + /* second component is nsecs */ + s++; + snap_timespec.tv_nsec = smb_strtoul(s, + &endptr, + 10, + &err, + SMB_STR_FULL_STR_CONV); + if (err != 0) { + return -err; + } + + /* + * >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT + * tokens only offer 1-second resolution (while twrp is nsec). + */ + *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30); + + return 0; +} + +/* + * XXX Ceph snapshots can be created with sub-second granularity, which means + * that multiple snapshots may be mapped to the same @GMT- label. + * + * @this_label is a pre-zeroed buffer to be filled with a @GMT label + * @return 0 if label successfully filled or -errno on error. + */ +static int ceph_snap_fill_label(struct vfs_handle_struct *handle, + TALLOC_CTX *tmp_ctx, + struct files_struct *dirfsp, + const char *subdir, + SHADOW_COPY_LABEL this_label) +{ + const char *parent_snapsdir = dirfsp->fsp_name->base_name; + struct smb_filename *smb_fname; + struct smb_filename *atname = NULL; + time_t snap_secs; + struct tm gmt_snap_time; + struct tm *tm_ret; + size_t str_sz; + char snap_path[PATH_MAX + 1]; + int ret; + NTSTATUS status; + + /* + * CephFS snapshot creation times are available via a special + * xattr - snapshot b/m/ctimes all match the snap source. + */ + ret = snprintf(snap_path, sizeof(snap_path), "%s/%s", + parent_snapsdir, subdir); + if (ret >= sizeof(snap_path)) { + return -EINVAL; + } + + smb_fname = synthetic_smb_fname(tmp_ctx, + snap_path, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + return -ENOMEM; + } + + ret = vfs_stat(handle->conn, smb_fname); + if (ret < 0) { + ret = -errno; + TALLOC_FREE(smb_fname); + return ret; + } + + atname = synthetic_smb_fname(tmp_ctx, + subdir, + NULL, + &smb_fname->st, + 0, + 0); + if (atname == NULL) { + TALLOC_FREE(smb_fname); + return -ENOMEM; + } + + status = openat_pathref_fsp(dirfsp, atname); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + return -map_errno_from_nt_status(status); + } + + ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs); + if (ret < 0) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + return ret; + } + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + + tm_ret = gmtime_r(&snap_secs, &gmt_snap_time); + if (tm_ret == NULL) { + return -EINVAL; + } + str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL), + "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time); + if (str_sz == 0) { + DBG_ERR("failed to convert tm to @GMT token\n"); + return -EINVAL; + } + + DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n", + snap_path, this_label); + + return 0; +} + +static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle, + struct smb_filename *snaps_dname, + bool labels, + struct shadow_copy_data *sc_data) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct smb_Dir *dir_hnd = NULL; + struct files_struct *dirfsp = NULL; + const char *dname = NULL; + char *talloced = NULL; + NTSTATUS status; + int ret; + uint32_t slots; + + DBG_DEBUG("enumerating shadow copy dir at %s\n", + snaps_dname->base_name); + + /* + * CephFS stat(dir).size *normally* returns the number of child entries + * for a given dir, but it unfortunately that's not the case for the one + * place we need it (dir=.snap), so we need to dynamically determine it + * via readdir. + */ + + status = OpenDir(frame, + handle->conn, + snaps_dname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + /* Check we have SEC_DIR_LIST access on this fsp. */ + dirfsp = dir_hnd_fetch_fsp(dir_hnd); + status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp, + dirfsp, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("user does not have list permission " + "on snapdir %s\n", + fsp_str_dbg(dirfsp)); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + slots = 0; + sc_data->num_volumes = 0; + sc_data->labels = NULL; + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + sc_data->num_volumes++; + if (!labels) { + TALLOC_FREE(talloced); + continue; + } + if (sc_data->num_volumes > slots) { + uint32_t new_slot_count = slots + 10; + SMB_ASSERT(new_slot_count > slots); + sc_data->labels = talloc_realloc(sc_data, + sc_data->labels, + SHADOW_COPY_LABEL, + new_slot_count); + if (sc_data->labels == NULL) { + TALLOC_FREE(talloced); + ret = -ENOMEM; + goto err_closedir; + } + memset(sc_data->labels[slots], 0, + sizeof(SHADOW_COPY_LABEL) * 10); + + DBG_DEBUG("%d->%d slots for enum_snaps response\n", + slots, new_slot_count); + slots = new_slot_count; + } + DBG_DEBUG("filling shadow copy label for %s/%s\n", + snaps_dname->base_name, dname); + ret = ceph_snap_fill_label(handle, + snaps_dname, + dirfsp, + dname, + sc_data->labels[sc_data->num_volumes - 1]); + if (ret < 0) { + TALLOC_FREE(talloced); + goto err_closedir; + } + TALLOC_FREE(talloced); + } + + DBG_DEBUG("%s shadow copy enumeration found %d labels \n", + snaps_dname->base_name, sc_data->num_volumes); + + TALLOC_FREE(frame); + return 0; + +err_closedir: + TALLOC_FREE(frame); +err_out: + TALLOC_FREE(sc_data->labels); + return ret; +} + +/* + * Prior reading: The Meaning of Path Names + * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module + * + * translate paths so that we can use the parent dir for .snap access: + * myfile -> parent= trimmed=myfile + * /a -> parent=/ trimmed=a + * dir/sub/file -> parent=dir/sub trimmed=file + * /dir/sub -> parent=/dir/ trimmed=sub + */ +static int ceph_snap_get_parent_path(const char *connectpath, + const char *path, + char *_parent_buf, + size_t buflen, + const char **_trimmed) +{ + const char *p; + size_t len; + int ret; + + if (!strcmp(path, "/")) { + DBG_ERR("can't go past root for %s .snap dir\n", path); + return -EINVAL; + } + + p = strrchr_m(path, '/'); /* Find final '/', if any */ + if (p == NULL) { + DBG_DEBUG("parent .snap dir for %s is cwd\n", path); + ret = strlcpy(_parent_buf, "", buflen); + if (ret >= buflen) { + return -EINVAL; + } + if (_trimmed != NULL) { + *_trimmed = path; + } + return 0; + } + + SMB_ASSERT(p >= path); + len = p - path; + + ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path); + if (ret >= buflen) { + return -EINVAL; + } + + /* for absolute paths, check that we're not going outside the share */ + if ((len > 0) && (_parent_buf[0] == '/')) { + bool connectpath_match = false; + size_t clen = strlen(connectpath); + DBG_DEBUG("checking absolute path %s lies within share at %s\n", + _parent_buf, connectpath); + /* need to check for separator, to avoid /x/abcd vs /x/ab */ + connectpath_match = (strncmp(connectpath, + _parent_buf, + clen) == 0); + if (!connectpath_match + || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) { + DBG_ERR("%s parent path is outside of share at %s\n", + _parent_buf, connectpath); + return -EINVAL; + } + } + + if (_trimmed != NULL) { + /* + * point to path component which was trimmed from _parent_buf + * excluding path separator. + */ + *_trimmed = p + 1; + } + + DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n", + path, _parent_buf, p + 1); + + return 0; +} + +static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *sc_data, + bool labels) +{ + int ret; + TALLOC_CTX *tmp_ctx; + const char *parent_dir = NULL; + char tmp[PATH_MAX + 1]; + char snaps_path[PATH_MAX + 1]; + struct smb_filename *snaps_dname = NULL; + const char *snapdir = lp_parm_const_string(SNUM(handle->conn), + "ceph", "snapdir", + CEPH_SNAP_SUBDIR_DEFAULT); + + DBG_DEBUG("getting shadow copy data for %s\n", + fsp->fsp_name->base_name); + + tmp_ctx = talloc_new(fsp); + if (tmp_ctx == NULL) { + ret = -ENOMEM; + goto err_out; + } + + if (sc_data == NULL) { + ret = -EINVAL; + goto err_out; + } + + if (fsp->fsp_flags.is_directory) { + parent_dir = fsp->fsp_name->base_name; + } else { + ret = ceph_snap_get_parent_path(handle->conn->connectpath, + fsp->fsp_name->base_name, + tmp, + sizeof(tmp), + NULL); /* trimmed */ + if (ret < 0) { + goto err_out; + } + parent_dir = tmp; + } + + if (strlen(parent_dir) == 0) { + ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path)); + } else { + ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s", + parent_dir, snapdir); + } + if (ret >= sizeof(snaps_path)) { + ret = -EINVAL; + goto err_out; + } + + snaps_dname = synthetic_smb_fname(tmp_ctx, + snaps_path, + NULL, + NULL, + 0, + fsp->fsp_name->flags); + if (snaps_dname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data); + if (ret < 0) { + goto err_out; + } + + talloc_free(tmp_ctx); + return 0; + +err_out: + talloc_free(tmp_ctx); + errno = -ret; + return -1; +} + +static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + time_t *_timestamp, + char *_stripped_buf, + size_t buflen) +{ + size_t len; + + if (smb_fname->twrp == 0) { + goto no_snapshot; + } + + if (_stripped_buf != NULL) { + len = strlcpy(_stripped_buf, smb_fname->base_name, buflen); + if (len >= buflen) { + return -ENAMETOOLONG; + } + } + + *_timestamp = nt_time_to_unix(smb_fname->twrp); + return 0; +no_snapshot: + *_timestamp = 0; + return 0; +} + +static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle, + const char *name, + time_t timestamp, + char *_converted_buf, + size_t buflen) +{ + int ret; + NTSTATUS status; + struct smb_Dir *dir_hnd = NULL; + struct files_struct *dirfsp = NULL; + const char *dname = NULL; + char *talloced = NULL; + struct smb_filename *snaps_dname = NULL; + const char *snapdir = lp_parm_const_string(SNUM(handle->conn), + "ceph", "snapdir", + CEPH_SNAP_SUBDIR_DEFAULT); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + ret = -ENOMEM; + goto err_out; + } + + /* + * Temporally use the caller's return buffer for this. + */ + if (strlen(name) == 0) { + ret = strlcpy(_converted_buf, snapdir, buflen); + } else { + ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir); + } + if (ret >= buflen) { + ret = -EINVAL; + goto err_out; + } + + snaps_dname = synthetic_smb_fname(tmp_ctx, + _converted_buf, + NULL, + NULL, + 0, + 0); /* XXX check? */ + if (snaps_dname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + /* stat first to trigger error fallback in ceph_snap_gmt_convert() */ + ret = SMB_VFS_NEXT_STAT(handle, snaps_dname); + if (ret < 0) { + ret = -errno; + goto err_out; + } + + DBG_DEBUG("enumerating shadow copy dir at %s\n", + snaps_dname->base_name); + + status = OpenDir(tmp_ctx, + handle->conn, + snaps_dname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + /* Check we have SEC_DIR_LIST access on this fsp. */ + dirfsp = dir_hnd_fetch_fsp(dir_hnd); + status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp, + dirfsp, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("user does not have list permission " + "on snapdir %s\n", + fsp_str_dbg(dirfsp)); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + struct smb_filename *smb_fname = NULL; + struct smb_filename *atname = NULL; + time_t snap_secs = 0; + + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + + ret = snprintf(_converted_buf, buflen, "%s/%s", + snaps_dname->base_name, dname); + if (ret >= buflen) { + ret = -EINVAL; + goto err_out; + } + + smb_fname = synthetic_smb_fname(tmp_ctx, + _converted_buf, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + ret = vfs_stat(handle->conn, smb_fname); + if (ret < 0) { + ret = -errno; + TALLOC_FREE(smb_fname); + goto err_out; + } + + atname = synthetic_smb_fname(tmp_ctx, + dname, + NULL, + &smb_fname->st, + 0, + 0); + if (atname == NULL) { + TALLOC_FREE(smb_fname); + ret = -ENOMEM; + goto err_out; + } + + status = openat_pathref_fsp(dirfsp, atname); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs); + if (ret < 0) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + goto err_out; + } + + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + + /* + * check gmt_snap_time matches @timestamp + */ + if (timestamp == snap_secs) { + break; + } + DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n", + handle->conn->connectpath, name, (long long)timestamp, + dname, (long long)snap_secs); + TALLOC_FREE(talloced); + } + + if (dname == NULL) { + DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n", + handle->conn->connectpath, name, (long long)timestamp); + ret = -ENOENT; + goto err_out; + } + + /* found, _converted_buf already contains path of interest */ + DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n", + handle->conn->connectpath, name, (long long)timestamp, + _converted_buf); + + TALLOC_FREE(talloced); + talloc_free(tmp_ctx); + return 0; + +err_out: + TALLOC_FREE(talloced); + talloc_free(tmp_ctx); + return ret; +} + +static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle, + const char *name, + time_t timestamp, + char *_converted_buf, + size_t buflen) +{ + int ret; + char parent[PATH_MAX + 1]; + const char *trimmed = NULL; + /* + * CephFS Snapshots for a given dir are nested under the ./.snap subdir + * *or* under ../.snap/dir (and subsequent parent dirs). + * Child dirs inherit snapshots created in parent dirs if the child + * exists at the time of snapshot creation. + * + * At this point we don't know whether @name refers to a file or dir, so + * first assume it's a dir (with a corresponding .snaps subdir) + */ + ret = ceph_snap_gmt_convert_dir(handle, + name, + timestamp, + _converted_buf, + buflen); + if (ret >= 0) { + /* all done: .snap subdir exists - @name is a dir */ + DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name); + return ret; + } + + /* @name/.snap access failed, attempt snapshot access via parent */ + DBG_DEBUG("%s/.snap access failed, attempting parent access\n", + name); + + ret = ceph_snap_get_parent_path(handle->conn->connectpath, + name, + parent, + sizeof(parent), + &trimmed); + if (ret < 0) { + return ret; + } + + ret = ceph_snap_gmt_convert_dir(handle, + parent, + timestamp, + _converted_buf, + buflen); + if (ret < 0) { + return ret; + } + + /* + * found snapshot via parent. Append the child path component + * that was trimmed... +1 for path separator + 1 for null termination. + */ + if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) { + return -EINVAL; + } + strlcat(_converted_buf, "/", buflen); + strlcat(_converted_buf, trimmed, buflen); + + return 0; +} + +static int ceph_snap_gmt_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int ret; + time_t timestamp_src, timestamp_dst; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_src, + ×tamp_src, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_dst, + ×tamp_dst, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp_src != 0) { + errno = EXDEV; + return -1; + } + if (timestamp_dst != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); +} + +/* block links from writeable shares to snapshots for now, like other modules */ +static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int ret; + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + link_contents, + ×tamp_old, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + new_smb_fname, + ×tamp_new, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); +} + +static int ceph_snap_gmt_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int ret; + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + old_smb_fname, + ×tamp_old, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + new_smb_fname, + ×tamp_new, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); +} + +static int ceph_snap_gmt_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + char *tmp; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + tmp = smb_fname->base_name; + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + smb_fname->base_name = tmp; + return ret; +} + +static int ceph_snap_gmt_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + char *tmp; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + tmp = smb_fname->base_name; + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + smb_fname->base_name = tmp; + return ret; +} + +static int ceph_snap_gmt_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + files_struct *fsp, + const struct vfs_open_how *how) +{ + time_t timestamp = 0; + struct smb_filename *smb_fname = NULL; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + int saved_errno = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_in, + ×tamp, + stripped, + sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname_in, + fsp, + how); + } + + ret = ceph_snap_gmt_convert(handle, + stripped, + timestamp, + conv, + sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + return -1; + } + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + int flags) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + csmb_fname, + flags); +} + +static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + const struct smb_filename *csmb_fname = NULL; + time_t timestamp = 0; + int ret; + + csmb_fname = fsp->fsp_name; + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); +} + +static int ceph_snap_gmt_chdir(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHDIR(handle, csmb_fname); + } + + ret = ceph_snap_gmt_convert_dir(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_CHDIR(handle, new_fname); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + fsp->fsp_name, + ×tamp, + NULL, + 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + char *buf, + size_t bufsiz) +{ + time_t timestamp = 0; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *full_fname = NULL; + int saved_errno; + + /* + * Now this function only looks at csmb_fname->twrp + * we don't need to copy out the path. Just use + * csmb_fname->base_name directly. + */ + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + csmb_fname, + buf, + bufsiz); + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + csmb_fname); + if (full_fname == NULL) { + return -1; + } + + /* Find the snapshot path from the full pathname. */ + ret = ceph_snap_gmt_convert(handle, + full_fname->base_name, + timestamp, + conv, + sizeof(conv)); + if (ret < 0) { + TALLOC_FREE(full_fname); + errno = -ret; + return -1; + } + full_fname->base_name = conv; + + ret = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + full_fname, + buf, + bufsiz); + saved_errno = errno; + TALLOC_FREE(full_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *csmb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + csmb_fname, + mode, + dev); +} + +static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *csmb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + struct smb_filename *result_fname; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return NULL; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return NULL; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return NULL; + } + new_fname->base_name = conv; + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return result_fname; +} + +static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + mode_t mode) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + csmb_fname, + mode); +} + +static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + fsp->fsp_name, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); +} + +static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *aname, const void *value, + size_t size, int flags) +{ + const struct smb_filename *csmb_fname = NULL; + time_t timestamp = 0; + int ret; + + csmb_fname = fsp->fsp_name; + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, + aname, value, size, flags); +} + +static NTSTATUS ceph_snap_gmt_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + struct smb_filename *conv_fname = NULL; + int ret; + NTSTATUS status; + + ret = ceph_snap_gmt_strip_snapshot( + handle, + dirfsp->fsp_name, + ×tamp, + stripped, + sizeof(stripped)); + if (ret < 0) { + return map_nt_error_from_unix(-ret); + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + ret = ceph_snap_gmt_convert_dir(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + return map_nt_error_from_unix(-ret); + } + + status = synthetic_pathref( + talloc_tos(), + dirfsp->conn->cwd_fsp, + conv, + NULL, + NULL, + 0, + 0, + &conv_fname); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, conv_fname->fsp, name, mem_ctx, found_name); + TALLOC_FREE(conv_fname); + return status; +} + +static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname, + bsize, dfree, dsize); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname, + bsize, dfree, dsize); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static struct vfs_fn_pointers ceph_snap_fns = { + .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data, + .disk_free_fn = ceph_snap_gmt_disk_free, + .get_quota_fn = ceph_snap_gmt_get_quota, + .renameat_fn = ceph_snap_gmt_renameat, + .linkat_fn = ceph_snap_gmt_linkat, + .symlinkat_fn = ceph_snap_gmt_symlinkat, + .stat_fn = ceph_snap_gmt_stat, + .lstat_fn = ceph_snap_gmt_lstat, + .openat_fn = ceph_snap_gmt_openat, + .unlinkat_fn = ceph_snap_gmt_unlinkat, + .fchmod_fn = ceph_snap_gmt_fchmod, + .chdir_fn = ceph_snap_gmt_chdir, + .fntimes_fn = ceph_snap_gmt_fntimes, + .readlinkat_fn = ceph_snap_gmt_readlinkat, + .mknodat_fn = ceph_snap_gmt_mknodat, + .realpath_fn = ceph_snap_gmt_realpath, + .mkdirat_fn = ceph_snap_gmt_mkdirat, + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fsetxattr_fn = ceph_snap_gmt_fsetxattr, + .fchflags_fn = ceph_snap_gmt_fchflags, + .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at, +}; + +static_decl_vfs; +NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "ceph_snapshots", &ceph_snap_fns); +} diff --git a/source3/modules/vfs_commit.c b/source3/modules/vfs_commit.c new file mode 100644 index 0000000..355edea --- /dev/null +++ b/source3/modules/vfs_commit.c @@ -0,0 +1,400 @@ +/* + * Copyright (c) James Peach 2006, 2007 + * Copyright (c) David Losada Carballo 2007 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_unix.h" + +/* Commit data module. + * + * The purpose of this module is to flush data to disk at regular intervals, + * just like the NFS commit operation. There's two rationales for this. First, + * it minimises the data loss in case of a power outage without incurring + * the poor performance of synchronous I/O. Second, a steady flush rate + * can produce better throughput than suddenly dumping massive amounts of + * writes onto a disk. + * + * Tunables: + * + * commit: dthresh Amount of dirty data that can accumulate + * before we commit (sync) it. + * + * commit: debug Debug level at which to emit messages. + * + * commit: eof mode String. Tunes how the module tries to guess when + * the client has written the last bytes of the file. + * Possible values (default = hinted): + * + * (*) = hinted Some clients (i.e. Windows Explorer) declare the + * size of the file before transferring it. With this + * option, we remember that hint, and commit after + * writing in that file position. If the client + * doesn't declare the size of file, committing on EOF + * is not triggered. + * + * = growth Commits after a write operation has made the file + * size grow. If the client declares a file size, it + * refrains to commit until the file has reached it. + * Useful for defeating writeback on NFS shares. + * + */ + +#define MODULE "commit" + +static int module_debug; + +enum eof_mode +{ + EOF_NONE = 0x0000, + EOF_HINTED = 0x0001, + EOF_GROWTH = 0x0002 +}; + +struct commit_info +{ + /* For chunk-based commits */ + off_t dbytes; /* Dirty (uncommitted) bytes */ + off_t dthresh; /* Dirty data threshold */ + /* For commits on EOF */ + enum eof_mode on_eof; + off_t eof; /* Expected file size */ +}; + +static int commit_do( + struct commit_info * c, + int fd) +{ + int result; + + DEBUG(module_debug, + ("%s: flushing %lu dirty bytes\n", + MODULE, (unsigned long)c->dbytes)); + +#if defined(HAVE_FDATASYNC) + result = fdatasync(fd); +#elif defined(HAVE_FSYNC) + result = fsync(fd); +#else + DEBUG(0, ("%s: WARNING: no commit support on this platform\n", + MODULE)); + result = 0 +#endif + if (result == 0) { + c->dbytes = 0; /* on success, no dirty bytes */ + } + return result; +} + +static int commit_all( + struct vfs_handle_struct * handle, + files_struct * fsp) +{ + struct commit_info *c; + + if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))) { + if (c->dbytes) { + DEBUG(module_debug, + ("%s: flushing %lu dirty bytes\n", + MODULE, (unsigned long)c->dbytes)); + + return commit_do(c, fsp_get_io_fd(fsp)); + } + } + return 0; +} + +static int commit( + struct vfs_handle_struct * handle, + files_struct * fsp, + off_t offset, + ssize_t last_write) +{ + struct commit_info *c; + + if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp)) + == NULL) { + return 0; + } + + c->dbytes += last_write; /* dirty bytes always counted */ + + if (c->dthresh && (c->dbytes > c->dthresh)) { + return commit_do(c, fsp_get_io_fd(fsp)); + } + + /* Return if we are not in EOF mode or if we have temporarily opted + * out of it. + */ + if (c->on_eof == EOF_NONE || c->eof < 0) { + return 0; + } + + /* This write hit or went past our cache the file size. */ + if ((offset + last_write) >= c->eof) { + if (commit_do(c, fsp_get_io_fd(fsp)) == -1) { + return -1; + } + + /* Hinted mode only commits the first time we hit EOF. */ + if (c->on_eof == EOF_HINTED) { + c->eof = -1; + } else if (c->on_eof == EOF_GROWTH) { + c->eof = offset + last_write; + } + } + + return 0; +} + +static int commit_connect( + struct vfs_handle_struct * handle, + const char * service, + const char * user) +{ + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100); + return 0; +} + +static int commit_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + off_t dthresh; + const char *eof_mode; + struct commit_info *c = NULL; + int fd; + + /* Don't bother with read-only files. */ + if ((how->flags & O_ACCMODE) == O_RDONLY) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + /* Read and check module configuration */ + dthresh = conv_str_size(lp_parm_const_string(SNUM(handle->conn), + MODULE, "dthresh", NULL)); + + eof_mode = lp_parm_const_string(SNUM(handle->conn), + MODULE, "eof mode", "none"); + + if (dthresh > 0 || !strequal(eof_mode, "none")) { + c = VFS_ADD_FSP_EXTENSION( + handle, fsp, struct commit_info, NULL); + /* Process main tunables */ + if (c) { + c->dthresh = dthresh; + c->dbytes = 0; + c->on_eof = EOF_NONE; + c->eof = 0; + } + } + /* Process eof_mode tunable */ + if (c) { + if (strequal(eof_mode, "hinted")) { + c->on_eof = EOF_HINTED; + } else if (strequal(eof_mode, "growth")) { + c->on_eof = EOF_GROWTH; + } + } + + fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + if (fd == -1) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return fd; + } + + /* EOF commit modes require us to know the initial file size. */ + if (c && (c->on_eof != EOF_NONE)) { + SMB_STRUCT_STAT st; + /* + * Setting the fd of the FSP is a hack + * but also practiced elsewhere - + * needed for calling the VFS. + */ + fsp_set_fd(fsp, fd); + if (SMB_VFS_FSTAT(fsp, &st) == -1) { + int saved_errno = errno; + SMB_VFS_CLOSE(fsp); + fsp_set_fd(fsp, -1); + errno = saved_errno; + return -1; + } + c->eof = st.st_ex_size; + } + + return fd; +} + +static ssize_t commit_pwrite( + vfs_handle_struct * handle, + files_struct * fsp, + const void * data, + size_t count, + off_t offset) +{ + ssize_t ret; + + ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, count, offset); + if (ret > 0) { + if (commit(handle, fsp, offset, ret) == -1) { + return -1; + } + } + + return ret; +} + +struct commit_pwrite_state { + struct vfs_handle_struct *handle; + struct files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void commit_pwrite_written(struct tevent_req *subreq); + +static struct tevent_req *commit_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct commit_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, struct commit_pwrite_state); + if (req == NULL) { + return NULL; + } + state->handle = handle; + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, commit_pwrite_written, req); + return req; +} + +static void commit_pwrite_written(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct commit_pwrite_state *state = tevent_req_data( + req, struct commit_pwrite_state); + int commit_ret; + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (state->ret <= 0) { + tevent_req_done(req); + return; + } + + /* + * Ok, this is a sync fake. We should make the sync async as well, but + * I'm too lazy for that right now -- vl + */ + commit_ret = commit(state->handle, + state->fsp, + fh_get_pos(state->fsp->fh), + state->ret); + + if (commit_ret == -1) { + state->ret = -1; + } + + tevent_req_done(req); +} + +static ssize_t commit_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct commit_pwrite_state *state = + tevent_req_data(req, struct commit_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int commit_close( + vfs_handle_struct * handle, + files_struct * fsp) +{ + /* Commit errors not checked, close() will find them again */ + commit_all(handle, fsp); + return SMB_VFS_NEXT_CLOSE(handle, fsp); +} + +static int commit_ftruncate( + vfs_handle_struct * handle, + files_struct * fsp, + off_t len) +{ + int result; + + result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len); + if (result == 0) { + struct commit_info *c; + if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION( + handle, fsp))) { + commit(handle, fsp, len, 0); + c->eof = len; + } + } + + return result; +} + +static struct vfs_fn_pointers vfs_commit_fns = { + .openat_fn = commit_openat, + .close_fn = commit_close, + .pwrite_fn = commit_pwrite, + .pwrite_send_fn = commit_pwrite_send, + .pwrite_recv_fn = commit_pwrite_recv, + .connect_fn = commit_connect, + .ftruncate_fn = commit_ftruncate +}; + +static_decl_vfs; +NTSTATUS vfs_commit_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE, + &vfs_commit_fns); +} + + diff --git a/source3/modules/vfs_crossrename.c b/source3/modules/vfs_crossrename.c new file mode 100644 index 0000000..042144b --- /dev/null +++ b/source3/modules/vfs_crossrename.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) Björn Jacke 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "transfer_file.h" +#include "smbprofile.h" + +#define MODULE "crossrename" +static off_t module_sizelimit; + +static int crossrename_connect( + struct vfs_handle_struct * handle, + const char * service, + const char * user) +{ + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + module_sizelimit = (off_t) lp_parm_int(SNUM(handle->conn), + MODULE, "sizelimit", 20); + /* convert from MiB to byte: */ + module_sizelimit *= 1048576; + + return 0; +} + +/********************************************************* + For rename across filesystems initial Patch from Warren Birnbaum + +**********************************************************/ + +static NTSTATUS copy_reg(vfs_handle_struct *handle, + struct files_struct *srcfsp, + const struct smb_filename *source, + struct files_struct *dstfsp, + const struct smb_filename *dest) +{ + NTSTATUS status; + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + int ret; + + if (!VALID_STAT(source->st)) { + status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto out; + } + if (!S_ISREG(source->st.st_ex_mode)) { + status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto out; + } + + if (source->st.st_ex_size > module_sizelimit) { + DBG_INFO("%s: size of %s larger than sizelimit (%lld > %lld), " + "rename prohibited\n", + MODULE, + source->base_name, + (long long)source->st.st_ex_size, + (long long)module_sizelimit); + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + source); + if (full_fname_src == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + dest); + if (full_fname_dst == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dstfsp, + dest, + 0); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + goto out; + } + + /* + * copy_internals() takes attribute values from the NTrename call. + * + * From MS-CIFS: + * + * "If the attribute is 0x0000, then only normal files are renamed. + * If the system file or hidden attributes are specified, then the + * rename is inclusive of both special types." + */ + status = copy_internals(talloc_tos(), + handle->conn, + NULL, + srcfsp, /* src_dirfsp */ + full_fname_src, + dstfsp, /* dst_dirfsp */ + full_fname_dst, + FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + srcfsp, + source, + 0); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + goto out; + } + + out: + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + return status; +} + +static int crossrename_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int result = -1; + + START_PROFILE(syscall_renameat); + + if (smb_fname_src->stream_name || smb_fname_dst->stream_name) { + errno = ENOENT; + goto out; + } + + result = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + + if ((result == -1) && (errno == EXDEV)) { + /* Rename across filesystems needed. */ + NTSTATUS status = copy_reg(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + result = -1; + } + } + + out: + END_PROFILE(syscall_renameat); + return result; +} + + +static struct vfs_fn_pointers vfs_crossrename_fns = { + .connect_fn = crossrename_connect, + .renameat_fn = crossrename_renameat +}; + +static_decl_vfs; +NTSTATUS vfs_crossrename_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE, + &vfs_crossrename_fns); +} + diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c new file mode 100644 index 0000000..62ad506 --- /dev/null +++ b/source3/modules/vfs_default.c @@ -0,0 +1,4098 @@ +/* + Unix SMB/CIFS implementation. + Wrap disk only vfs functions to sidestep dodgy compilers. + Copyright (C) Tim Potter 1998 + Copyright (C) Jeremy Allison 2007 + + 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/time.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "ntioctl.h" +#include "smbprofile.h" +#include "../libcli/security/security.h" +#include "passdb/lookup_sid.h" +#include "source3/include/msdfs.h" +#include "librpc/gen_ndr/ndr_dfsblobs.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/sys_rw.h" +#include "lib/pthreadpool/pthreadpool_tevent.h" +#include "librpc/gen_ndr/ndr_ioctl.h" +#include "offload_token.h" +#include "util_reparse.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* Check for NULL pointer parameters in vfswrap_* functions */ + +/* We don't want to have NULL function pointers lying around. Someone + is sure to try and execute them. These stubs are used to prevent + this possibility. */ + +static int vfswrap_connect(vfs_handle_struct *handle, const char *service, const char *user) +{ + bool bval; + + handle->conn->have_proc_fds = sys_have_proc_fds(); +#ifdef DISABLE_PROC_FDS + handle->conn->have_proc_fds = false; +#endif + + /* + * assume the kernel will support openat2(), + * it will be reset on the first ENOSYS. + * + * Note that libreplace will always provide openat2(), + * but return -1/errno = ENOSYS... + * + * The option is only there to test the fallback code. + */ + bval = lp_parm_bool(SNUM(handle->conn), + "vfs_default", + "VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS", + true); + if (bval) { + handle->conn->open_how_resolve |= + VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS; + } +#ifdef DISABLE_VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS + handle->conn->open_how_resolve &= ~VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS; +#endif + + return 0; /* Return >= 0 for success */ +} + +static void vfswrap_disconnect(vfs_handle_struct *handle) +{ +} + +/* Disk operations */ + +static uint64_t vfswrap_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + if (sys_fsusage(smb_fname->base_name, dfree, dsize) != 0) { + return (uint64_t)-1; + } + + *bsize = 512; + return *dfree / 2; +} + +static int vfswrap_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ +#ifdef HAVE_SYS_QUOTAS + int result; + + START_PROFILE(syscall_get_quota); + result = sys_get_quota(smb_fname->base_name, qtype, id, qt); + END_PROFILE(syscall_get_quota); + return result; +#else + errno = ENOSYS; + return -1; +#endif +} + +static int vfswrap_set_quota(struct vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt) +{ +#ifdef HAVE_SYS_QUOTAS + int result; + + START_PROFILE(syscall_set_quota); + result = sys_set_quota(handle->conn->connectpath, qtype, id, qt); + END_PROFILE(syscall_set_quota); + return result; +#else + errno = ENOSYS; + return -1; +#endif +} + +static int vfswrap_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *shadow_copy_data, + bool labels) +{ + errno = ENOSYS; + return -1; /* Not implemented. */ +} + +static int vfswrap_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + return sys_statvfs(smb_fname->base_name, statbuf); +} + +static uint32_t vfswrap_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + connection_struct *conn = handle->conn; + uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES; + struct smb_filename *smb_fname_cpath = NULL; + struct vfs_statvfs_struct statbuf; + int ret; + + smb_fname_cpath = synthetic_smb_fname(talloc_tos(), + conn->connectpath, + NULL, + NULL, + 0, + 0); + if (smb_fname_cpath == NULL) { + return caps; + } + + ZERO_STRUCT(statbuf); + ret = SMB_VFS_STATVFS(conn, smb_fname_cpath, &statbuf); + if (ret == 0) { + caps = statbuf.FsCapabilities; + } + + *p_ts_res = TIMESTAMP_SET_SECONDS; + + /* Work out what timestamp resolution we can + * use when setting a timestamp. */ + + ret = SMB_VFS_STAT(conn, smb_fname_cpath); + if (ret == -1) { + TALLOC_FREE(smb_fname_cpath); + return caps; + } + + if (smb_fname_cpath->st.st_ex_mtime.tv_nsec || + smb_fname_cpath->st.st_ex_atime.tv_nsec || + smb_fname_cpath->st.st_ex_ctime.tv_nsec) { + /* If any of the normal UNIX directory timestamps + * have a non-zero tv_nsec component assume + * we might be able to set sub-second timestamps. + * See what filetime set primitives we have. + */ +#if defined(HAVE_UTIMENSAT) + *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER; +#elif defined(HAVE_UTIMES) + /* utimes allows msec timestamps to be set. */ + *p_ts_res = TIMESTAMP_SET_MSEC; +#elif defined(HAVE_UTIME) + /* utime only allows sec timestamps to be set. */ + *p_ts_res = TIMESTAMP_SET_SECONDS; +#endif + + DBG_DEBUG("vfswrap_fs_capabilities: timestamp " + "resolution of %s " + "available on share %s, directory %s\n", + *p_ts_res == TIMESTAMP_SET_MSEC ? "msec" : "sec", + lp_servicename(talloc_tos(), lp_sub, conn->params->service), + conn->connectpath ); + } + TALLOC_FREE(smb_fname_cpath); + return caps; +} + +static NTSTATUS vfswrap_get_dfs_referrals(struct vfs_handle_struct *handle, + struct dfs_GetDFSReferral *r) +{ + struct junction_map *junction = NULL; + size_t consumedcnt = 0; + bool self_referral = false; + char *pathnamep = NULL; + char *local_dfs_path = NULL; + NTSTATUS status; + size_t i; + uint16_t max_referral_level = r->in.req.max_referral_level; + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_IN_DEBUG(dfs_GetDFSReferral, r); + } + + /* get the junction entry */ + if (r->in.req.servername == NULL) { + return NT_STATUS_NOT_FOUND; + } + + /* + * Trim pathname sent by client so it begins with only one backslash. + * Two backslashes confuse some dfs clients + */ + + local_dfs_path = talloc_strdup(r, r->in.req.servername); + if (local_dfs_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + pathnamep = local_dfs_path; + while (IS_DIRECTORY_SEP(pathnamep[0]) && + IS_DIRECTORY_SEP(pathnamep[1])) { + pathnamep++; + } + + junction = talloc_zero(r, struct junction_map); + if (junction == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* The following call can change cwd. */ + status = get_referred_path(r, + handle->conn->session_info, + pathnamep, + handle->conn->sconn->remote_address, + handle->conn->sconn->local_address, + junction, &consumedcnt, &self_referral); + if (!NT_STATUS_IS_OK(status)) { + struct smb_filename connectpath_fname = { + .base_name = handle->conn->connectpath + }; + vfs_ChDir(handle->conn, &connectpath_fname); + return status; + } + { + struct smb_filename connectpath_fname = { + .base_name = handle->conn->connectpath + }; + vfs_ChDir(handle->conn, &connectpath_fname); + } + + if (!self_referral) { + pathnamep[consumedcnt] = '\0'; + + if (DEBUGLVL(DBGLVL_INFO)) { + dbgtext("Path %s to alternate path(s):", + pathnamep); + for (i=0; i < junction->referral_count; i++) { + dbgtext(" %s", + junction->referral_list[i].alternate_path); + } + dbgtext(".\n"); + } + } + + if (r->in.req.max_referral_level <= 2) { + max_referral_level = 2; + } + if (r->in.req.max_referral_level >= 3) { + max_referral_level = 3; + } + + r->out.resp = talloc_zero(r, struct dfs_referral_resp); + if (r->out.resp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + r->out.resp->path_consumed = strlen_m(pathnamep) * 2; + r->out.resp->nb_referrals = junction->referral_count; + + r->out.resp->header_flags = DFS_HEADER_FLAG_STORAGE_SVR; + if (self_referral) { + r->out.resp->header_flags |= DFS_HEADER_FLAG_REFERAL_SVR; + } + + r->out.resp->referral_entries = talloc_zero_array(r, + struct dfs_referral_type, + r->out.resp->nb_referrals); + if (r->out.resp->referral_entries == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch (max_referral_level) { + case 2: + for(i=0; i < junction->referral_count; i++) { + struct referral *ref = &junction->referral_list[i]; + TALLOC_CTX *mem_ctx = r->out.resp->referral_entries; + struct dfs_referral_type *t = + &r->out.resp->referral_entries[i]; + struct dfs_referral_v2 *v2 = &t->referral.v2; + + t->version = 2; + v2->size = VERSION2_REFERRAL_SIZE; + if (self_referral) { + v2->server_type = DFS_SERVER_ROOT; + } else { + v2->server_type = DFS_SERVER_NON_ROOT; + } + v2->entry_flags = 0; + v2->proximity = ref->proximity; + v2->ttl = ref->ttl; + v2->DFS_path = talloc_strdup(mem_ctx, pathnamep); + if (v2->DFS_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + v2->DFS_alt_path = talloc_strdup(mem_ctx, pathnamep); + if (v2->DFS_alt_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + v2->netw_address = talloc_strdup(mem_ctx, + ref->alternate_path); + if (v2->netw_address == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + break; + case 3: + for(i=0; i < junction->referral_count; i++) { + struct referral *ref = &junction->referral_list[i]; + TALLOC_CTX *mem_ctx = r->out.resp->referral_entries; + struct dfs_referral_type *t = + &r->out.resp->referral_entries[i]; + struct dfs_referral_v3 *v3 = &t->referral.v3; + struct dfs_normal_referral *r1 = &v3->referrals.r1; + + t->version = 3; + v3->size = VERSION3_REFERRAL_SIZE; + if (self_referral) { + v3->server_type = DFS_SERVER_ROOT; + } else { + v3->server_type = DFS_SERVER_NON_ROOT; + } + v3->entry_flags = 0; + v3->ttl = ref->ttl; + r1->DFS_path = talloc_strdup(mem_ctx, pathnamep); + if (r1->DFS_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + r1->DFS_alt_path = talloc_strdup(mem_ctx, pathnamep); + if (r1->DFS_alt_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + r1->netw_address = talloc_strdup(mem_ctx, + ref->alternate_path); + if (r1->netw_address == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + break; + default: + DBG_ERR("Invalid dfs referral version: %d\n", + max_referral_level); + return NT_STATUS_INVALID_LEVEL; + } + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_OUT_DEBUG(dfs_GetDFSReferral, r); + } + + return NT_STATUS_OK; +} + +static NTSTATUS vfswrap_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NO_MEMORY; + int ret; + char *msdfs_link = NULL; + + /* Form the msdfs_link contents */ + msdfs_link = msdfs_link_string(frame, + reflist, + referral_count); + if (msdfs_link == NULL) { + goto out; + } + + ret = symlinkat(msdfs_link, + fsp_get_pathref_fd(dirfsp), + smb_fname->base_name); + if (ret == 0) { + status = NT_STATUS_OK; + } else { + status = map_nt_error_from_unix(errno); + } + + out: + + TALLOC_FREE(frame); + return status; +} + +/* + * Read and return the contents of a DFS redirect given a + * pathname. A caller can pass in NULL for ppreflist and + * preferral_count but still determine if this was a + * DFS redirect point by getting NT_STATUS_OK back + * without incurring the overhead of reading and parsing + * the referral contents. + */ + +static NTSTATUS vfswrap_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + NTSTATUS status = NT_STATUS_NO_MEMORY; + size_t bufsize; + char *link_target = NULL; + int referral_len; + bool ok; +#if defined(HAVE_BROKEN_READLINK) + char link_target_buf[PATH_MAX]; +#else + char link_target_buf[7]; +#endif + int ret; + + if (is_named_stream(smb_fname)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* + * We're only checking if this is a DFS + * redirect. We don't need to return data. + */ + bufsize = sizeof(link_target_buf); + link_target = link_target_buf; + } else { + bufsize = PATH_MAX; + link_target = talloc_array(mem_ctx, char, bufsize); + if (!link_target) { + goto err; + } + } + + referral_len = readlinkat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + link_target, + bufsize - 1); + if (referral_len == -1) { + if (errno == EINVAL) { + /* + * If the path isn't a link, readlinkat + * returns EINVAL. Allow the caller to + * detect this. + */ + DBG_INFO("%s is not a link.\n", smb_fname->base_name); + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + } else { + status = map_nt_error_from_unix(errno); + if (errno == ENOENT) { + DBG_NOTICE("Error reading " + "msdfs link %s: %s\n", + smb_fname->base_name, + strerror(errno)); + } else { + DBG_ERR("Error reading " + "msdfs link %s: %s\n", + smb_fname->base_name, + strerror(errno)); + } + } + goto err; + } + link_target[referral_len] = '\0'; + + DBG_INFO("%s -> %s\n", + smb_fname->base_name, + link_target); + + if (!strnequal(link_target, "msdfs:", 6)) { + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + goto err; + } + + ret = sys_fstatat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + &smb_fname->st, + AT_SYMLINK_NOFOLLOW, + lp_fake_directory_create_times(SNUM(handle->conn))); + if (ret < 0) { + status = map_nt_error_from_unix(errno); + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* Early return for checking if this is a DFS link. */ + return NT_STATUS_OK; + } + + ok = parse_msdfs_symlink(mem_ctx, + lp_msdfs_shuffle_referrals(SNUM(handle->conn)), + link_target, + ppreflist, + preferral_count); + + if (ok) { + status = NT_STATUS_OK; + } else { + status = NT_STATUS_NO_MEMORY; + } + + err: + + if (link_target != link_target_buf) { + TALLOC_FREE(link_target); + } + return status; +} + +static NTSTATUS vfswrap_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +static NTSTATUS vfswrap_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **base_path, + char **snap_path) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +static NTSTATUS vfswrap_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* Directory operations */ + +static DIR *vfswrap_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, + const char *mask, + uint32_t attr) +{ + DIR *result; + + START_PROFILE(syscall_fdopendir); + result = sys_fdopendir(fsp_get_io_fd(fsp)); + END_PROFILE(syscall_fdopendir); + return result; +} + +static struct dirent *vfswrap_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + struct dirent *result; + + START_PROFILE(syscall_readdir); + + result = readdir(dirp); + END_PROFILE(syscall_readdir); + + return result; +} + +static NTSTATUS vfswrap_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **attr_data) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +static void vfswrap_rewinddir(vfs_handle_struct *handle, DIR *dirp) +{ + START_PROFILE(syscall_rewinddir); + rewinddir(dirp); + END_PROFILE(syscall_rewinddir); +} + +static int vfswrap_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + int result; + + START_PROFILE(syscall_mkdirat); + + result = mkdirat(fsp_get_pathref_fd(dirfsp), smb_fname->base_name, mode); + + END_PROFILE(syscall_mkdirat); + return result; +} + +static int vfswrap_closedir(vfs_handle_struct *handle, DIR *dirp) +{ + int result; + + START_PROFILE(syscall_closedir); + result = closedir(dirp); + END_PROFILE(syscall_closedir); + return result; +} + +/* File operations */ + +static int vfswrap_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int flags = how->flags; + mode_t mode = how->mode; + bool have_opath = false; + bool became_root = false; + int result; + + START_PROFILE(syscall_openat); + + if (how->resolve & ~(VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS | + VFS_OPEN_HOW_WITH_BACKUP_INTENT)) { + errno = ENOSYS; + result = -1; + goto out; + } + + SMB_ASSERT(!is_named_stream(smb_fname)); + +#ifdef O_PATH + have_opath = true; + if (fsp->fsp_flags.is_pathref) { + flags |= O_PATH; + } + if (flags & O_PATH) { + /* + * From "man 2 openat": + * + * When O_PATH is specified in flags, flag bits other than + * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored. + * + * From "man 2 openat2": + * + * Whereas openat(2) ignores unknown bits in its flags + * argument, openat2() returns an error if unknown or + * conflicting flags are specified in how.flags. + * + * So we better clear ignored/invalid flags + * and only keep the expected ones. + */ + flags &= (O_PATH|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + } +#endif + + if (how->resolve & VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS) { + struct open_how linux_how = { + .flags = flags, + .mode = mode, + .resolve = RESOLVE_NO_SYMLINKS, + }; + + result = openat2(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + &linux_how, + sizeof(linux_how)); + if (result == -1) { + if (errno == ENOSYS) { + /* + * The kernel doesn't support + * openat2(), so indicate to + * the callers that + * VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS + * would just be a waste of time. + */ + fsp->conn->open_how_resolve &= + ~VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS; + } + goto out; + } + + goto done; + } + + if (fsp->fsp_flags.is_pathref && !have_opath) { + become_root(); + became_root = true; + } + + result = openat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + flags, + mode); + + if (became_root) { + int err = errno; + unbecome_root(); + errno = err; + } + +done: + if (result >= 0) { + fsp->fsp_flags.have_proc_fds = fsp->conn->have_proc_fds; + } else { + /* + * "/proc/self/fd/-1" never exists. Indicate to upper + * layers that for this fsp a possible name-based + * fallback is the only way to go. + */ + fsp->fsp_flags.have_proc_fds = false; + } + +out: + END_PROFILE(syscall_openat); + return result; +} +static NTSTATUS vfswrap_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + return create_file_default(handle->conn, req, dirfsp, smb_fname, + access_mask, share_access, + create_disposition, create_options, + file_attributes, oplock_request, lease, + allocation_size, private_flags, + sd, ea_list, result, + pinfo, in_context_blobs, out_context_blobs); +} + +static int vfswrap_close(vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + + START_PROFILE(syscall_close); + result = fd_close_posix(fsp); + END_PROFILE(syscall_close); + return result; +} + +static ssize_t vfswrap_pread(vfs_handle_struct *handle, files_struct *fsp, void *data, + size_t n, off_t offset) +{ + ssize_t result; + +#if defined(HAVE_PREAD) || defined(HAVE_PREAD64) + START_PROFILE_BYTES(syscall_pread, n); + result = sys_pread_full(fsp_get_io_fd(fsp), data, n, offset); + END_PROFILE_BYTES(syscall_pread); + + if (result == -1 && errno == ESPIPE) { + /* Maintain the fiction that pipes can be seeked (sought?) on. */ + result = sys_read(fsp_get_io_fd(fsp), data, n); + fh_set_pos(fsp->fh, 0); + } + +#else /* HAVE_PREAD */ + errno = ENOSYS; + result = -1; +#endif /* HAVE_PREAD */ + + return result; +} + +static ssize_t vfswrap_pwrite(vfs_handle_struct *handle, files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + ssize_t result; + +#if defined(HAVE_PWRITE) || defined(HAVE_PRWITE64) + START_PROFILE_BYTES(syscall_pwrite, n); + result = sys_pwrite_full(fsp_get_io_fd(fsp), data, n, offset); + END_PROFILE_BYTES(syscall_pwrite); + + if (result == -1 && errno == ESPIPE) { + /* Maintain the fiction that pipes can be sought on. */ + result = sys_write(fsp_get_io_fd(fsp), data, n); + } + +#else /* HAVE_PWRITE */ + errno = ENOSYS; + result = -1; +#endif /* HAVE_PWRITE */ + + return result; +} + +struct vfswrap_pread_state { + ssize_t ret; + int fd; + void *buf; + size_t count; + off_t offset; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_pread_do(void *private_data); +static void vfs_pread_done(struct tevent_req *subreq); +static int vfs_pread_state_destructor(struct vfswrap_pread_state *state); + +static struct tevent_req *vfswrap_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct vfswrap_pread_state *state; + + req = tevent_req_create(mem_ctx, &state, struct vfswrap_pread_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = fsp_get_io_fd(fsp); + state->buf = data; + state->count = n; + state->offset = offset; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p, + state->profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, + vfs_pread_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_pread_done, req); + + talloc_set_destructor(state, vfs_pread_state_destructor); + + return req; +} + +static void vfs_pread_do(void *private_data) +{ + struct vfswrap_pread_state *state = talloc_get_type_abort( + private_data, struct vfswrap_pread_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + state->ret = sys_pread_full(state->fd, + state->buf, + state->count, + state->offset); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_pread_state_destructor(struct vfswrap_pread_state *state) +{ + return -1; +} + +static void vfs_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_pread_state *state = tevent_req_data( + req, struct vfswrap_pread_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_pread_do(state); + } + + tevent_req_done(req); +} + +static ssize_t vfswrap_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfswrap_pread_state *state = tevent_req_data( + req, struct vfswrap_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct vfswrap_pwrite_state { + ssize_t ret; + int fd; + const void *buf; + size_t count; + off_t offset; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_pwrite_do(void *private_data); +static void vfs_pwrite_done(struct tevent_req *subreq); +static int vfs_pwrite_state_destructor(struct vfswrap_pwrite_state *state); + +static struct tevent_req *vfswrap_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct vfswrap_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, struct vfswrap_pwrite_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = fsp_get_io_fd(fsp); + state->buf = data; + state->count = n; + state->offset = offset; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p, + state->profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, + vfs_pwrite_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_pwrite_done, req); + + talloc_set_destructor(state, vfs_pwrite_state_destructor); + + return req; +} + +static void vfs_pwrite_do(void *private_data) +{ + struct vfswrap_pwrite_state *state = talloc_get_type_abort( + private_data, struct vfswrap_pwrite_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + state->ret = sys_pwrite_full(state->fd, + state->buf, + state->count, + state->offset); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_pwrite_state_destructor(struct vfswrap_pwrite_state *state) +{ + return -1; +} + +static void vfs_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_pwrite_state *state = tevent_req_data( + req, struct vfswrap_pwrite_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_pwrite_do(state); + } + + tevent_req_done(req); +} + +static ssize_t vfswrap_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfswrap_pwrite_state *state = tevent_req_data( + req, struct vfswrap_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct vfswrap_fsync_state { + ssize_t ret; + int fd; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_fsync_do(void *private_data); +static void vfs_fsync_done(struct tevent_req *subreq); +static int vfs_fsync_state_destructor(struct vfswrap_fsync_state *state); + +static struct tevent_req *vfswrap_fsync_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req, *subreq; + struct vfswrap_fsync_state *state; + + req = tevent_req_create(mem_ctx, &state, struct vfswrap_fsync_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = fsp_get_io_fd(fsp); + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p, + state->profile_bytes, 0); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, vfs_fsync_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_fsync_done, req); + + talloc_set_destructor(state, vfs_fsync_state_destructor); + + return req; +} + +static void vfs_fsync_do(void *private_data) +{ + struct vfswrap_fsync_state *state = talloc_get_type_abort( + private_data, struct vfswrap_fsync_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + do { + state->ret = fsync(state->fd); + } while ((state->ret == -1) && (errno == EINTR)); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_fsync_state_destructor(struct vfswrap_fsync_state *state) +{ + return -1; +} + +static void vfs_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_fsync_state *state = tevent_req_data( + req, struct vfswrap_fsync_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_fsync_do(state); + } + + tevent_req_done(req); +} + +static int vfswrap_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfswrap_fsync_state *state = tevent_req_data( + req, struct vfswrap_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static off_t vfswrap_lseek(vfs_handle_struct *handle, files_struct *fsp, off_t offset, int whence) +{ + off_t result = 0; + + START_PROFILE(syscall_lseek); + + result = lseek(fsp_get_io_fd(fsp), offset, whence); + /* + * We want to maintain the fiction that we can seek + * on a fifo for file system purposes. This allows + * people to set up UNIX fifo's that feed data to Windows + * applications. JRA. + */ + + if((result == -1) && (errno == ESPIPE)) { + result = 0; + errno = 0; + } + + END_PROFILE(syscall_lseek); + return result; +} + +static ssize_t vfswrap_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fromfsp, const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + ssize_t result; + + START_PROFILE_BYTES(syscall_sendfile, n); + result = sys_sendfile(tofd, fsp_get_io_fd(fromfsp), hdr, offset, n); + END_PROFILE_BYTES(syscall_sendfile); + return result; +} + +static ssize_t vfswrap_recvfile(vfs_handle_struct *handle, + int fromfd, + files_struct *tofsp, + off_t offset, + size_t n) +{ + ssize_t result; + + START_PROFILE_BYTES(syscall_recvfile, n); + result = sys_recvfile(fromfd, fsp_get_io_fd(tofsp), offset, n); + END_PROFILE_BYTES(syscall_recvfile); + return result; +} + +static int vfswrap_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int result = -1; + + START_PROFILE(syscall_renameat); + + SMB_ASSERT(!is_named_stream(smb_fname_src)); + SMB_ASSERT(!is_named_stream(smb_fname_dst)); + + result = renameat(fsp_get_pathref_fd(srcfsp), + smb_fname_src->base_name, + fsp_get_pathref_fd(dstfsp), + smb_fname_dst->base_name); + + END_PROFILE(syscall_renameat); + return result; +} + +static int vfswrap_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result = -1; + + START_PROFILE(syscall_stat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = sys_stat(smb_fname->base_name, &smb_fname->st, + lp_fake_directory_create_times(SNUM(handle->conn))); + + END_PROFILE(syscall_stat); + return result; +} + +static int vfswrap_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int result; + + START_PROFILE(syscall_fstat); + result = sys_fstat(fsp_get_pathref_fd(fsp), + sbuf, lp_fake_directory_create_times(SNUM(handle->conn))); + END_PROFILE(syscall_fstat); + return result; +} + +static int vfswrap_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result = -1; + + START_PROFILE(syscall_lstat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = sys_lstat(smb_fname->base_name, &smb_fname->st, + lp_fake_directory_create_times(SNUM(handle->conn))); + + END_PROFILE(syscall_lstat); + return result; +} + +static int vfswrap_fstatat( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + int result = -1; + + START_PROFILE(syscall_fstatat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = sys_fstatat( + fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + sbuf, + flags, + lp_fake_directory_create_times(SNUM(handle->conn))); + + END_PROFILE(syscall_fstatat); + return result; +} + +static NTSTATUS vfswrap_translate_name(struct vfs_handle_struct *handle, + const char *name, + enum vfs_translate_direction direction, + TALLOC_CTX *mem_ctx, + char **mapped_name) +{ + return NT_STATUS_NONE_MAPPED; +} + +/** + * Return allocated parent directory and basename of path + * + * Note: if requesting atname, it is returned as talloc child of the + * parent. Freeing the parent is thus sufficient to free both. + */ +static NTSTATUS vfswrap_parent_pathname(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const struct smb_filename *smb_fname_in, + struct smb_filename **parent_dir_out, + struct smb_filename **atname_out) +{ + struct smb_filename *parent = NULL; + struct smb_filename *name = NULL; + char *p = NULL; + + parent = cp_smb_filename_nostream(mem_ctx, smb_fname_in); + if (parent == NULL) { + return NT_STATUS_NO_MEMORY; + } + SET_STAT_INVALID(parent->st); + + p = strrchr_m(parent->base_name, '/'); /* Find final '/', if any */ + if (p == NULL) { + TALLOC_FREE(parent->base_name); + parent->base_name = talloc_strdup(parent, "."); + if (parent->base_name == NULL) { + TALLOC_FREE(parent); + return NT_STATUS_NO_MEMORY; + } + p = smb_fname_in->base_name; + } else { + *p = '\0'; + p++; + } + + if (atname_out == NULL) { + *parent_dir_out = parent; + return NT_STATUS_OK; + } + + name = synthetic_smb_fname( + parent, + p, + smb_fname_in->stream_name, + &smb_fname_in->st, + smb_fname_in->twrp, + smb_fname_in->flags); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *parent_dir_out = parent; + *atname_out = name; + return NT_STATUS_OK; +} + +/* + * Implement the default fsctl operation. + */ +static bool vfswrap_logged_ioctl_message = false; + +static NTSTATUS vfswrap_fsctl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *ctx, + uint32_t function, + uint16_t req_flags, /* Needed for UNICODE ... */ + const uint8_t *_in_data, + uint32_t in_len, + uint8_t **_out_data, + uint32_t max_out_len, + uint32_t *out_len) +{ + const char *in_data = (const char *)_in_data; + char **out_data = (char **)_out_data; + NTSTATUS status; + + /* + * Currently all fsctls operate on the base + * file if given an alternate data stream. + * Revisit this if we implement fsctls later + * that need access to the ADS handle. + */ + fsp = metadata_fsp(fsp); + + switch (function) { + case FSCTL_SET_SPARSE: + { + bool set_sparse = true; + + if (in_len >= 1 && in_data[0] == 0) { + set_sparse = false; + } + + status = file_set_sparse(handle->conn, fsp, set_sparse); + + DEBUG(NT_STATUS_IS_OK(status) ? 10 : 9, + ("FSCTL_SET_SPARSE: fname[%s] set[%u] - %s\n", + smb_fname_str_dbg(fsp->fsp_name), set_sparse, + nt_errstr(status))); + + return status; + } + + case FSCTL_CREATE_OR_GET_OBJECT_ID: + { + unsigned char objid[16]; + char *return_data = NULL; + + /* This should return the object-id on this file. + * I think I'll make this be the inode+dev. JRA. + */ + + DBG_DEBUG("FSCTL_CREATE_OR_GET_OBJECT_ID: called on %s\n", + fsp_fnum_dbg(fsp)); + + *out_len = MIN(max_out_len, 64); + + /* Hmmm, will this cause problems if less data asked for? */ + return_data = talloc_array(ctx, char, 64); + if (return_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* For backwards compatibility only store the dev/inode. */ + push_file_id_16(return_data, &fsp->file_id); + memcpy(return_data+16,create_volume_objectid(fsp->conn,objid),16); + push_file_id_16(return_data+32, &fsp->file_id); + memset(return_data+48, 0, 16); + *out_data = return_data; + return NT_STATUS_OK; + } + + case FSCTL_GET_REPARSE_POINT: + { + status = fsctl_get_reparse_point( + fsp, ctx, out_data, max_out_len, out_len); + return status; + } + + case FSCTL_SET_REPARSE_POINT: + { + status = fsctl_set_reparse_point(fsp, ctx, _in_data, in_len); + return status; + } + + case FSCTL_DELETE_REPARSE_POINT: + { + status = fsctl_del_reparse_point(fsp, ctx, _in_data, in_len); + return status; + } + + case FSCTL_GET_SHADOW_COPY_DATA: + { + /* + * This is called to retrieve the number of Shadow Copies (a.k.a. snapshots) + * and return their volume names. If max_data_count is 16, then it is just + * asking for the number of volumes and length of the combined names. + * + * pdata is the data allocated by our caller, but that uses + * total_data_count (which is 0 in our case) rather than max_data_count. + * Allocate the correct amount and return the pointer to let + * it be deallocated when we return. + */ + struct shadow_copy_data *shadow_data = NULL; + bool labels = False; + uint32_t labels_data_count = 0; + uint32_t i; + char *cur_pdata = NULL; + + if (max_out_len < 16) { + DBG_ERR("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) < 16 is invalid!\n", + max_out_len); + return NT_STATUS_INVALID_PARAMETER; + } + + if (max_out_len > 16) { + labels = True; + } + + shadow_data = talloc_zero(ctx, struct shadow_copy_data); + if (shadow_data == NULL) { + DBG_ERR("TALLOC_ZERO() failed!\n"); + return NT_STATUS_NO_MEMORY; + } + + /* + * Call the VFS routine to actually do the work. + */ + if (SMB_VFS_GET_SHADOW_COPY_DATA(fsp, shadow_data, labels)!=0) { + int log_lev = DBGLVL_ERR; + if (errno == 0) { + /* broken module didn't set errno on error */ + status = NT_STATUS_UNSUCCESSFUL; + } else { + status = map_nt_error_from_unix(errno); + if (NT_STATUS_EQUAL(status, + NT_STATUS_NOT_SUPPORTED)) { + log_lev = DBGLVL_INFO; + } + } + DEBUG(log_lev, ("FSCTL_GET_SHADOW_COPY_DATA: " + "connectpath %s, failed - %s.\n", + fsp->conn->connectpath, + nt_errstr(status))); + TALLOC_FREE(shadow_data); + return status; + } + + labels_data_count = (shadow_data->num_volumes * 2 * + sizeof(SHADOW_COPY_LABEL)) + 2; + + if (!labels) { + *out_len = 16; + } else { + *out_len = 12 + labels_data_count; + } + + if (max_out_len < *out_len) { + DBG_ERR("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) too small (%u) bytes needed!\n", + max_out_len, *out_len); + TALLOC_FREE(shadow_data); + return NT_STATUS_BUFFER_TOO_SMALL; + } + + cur_pdata = talloc_zero_array(ctx, char, *out_len); + if (cur_pdata == NULL) { + TALLOC_FREE(shadow_data); + return NT_STATUS_NO_MEMORY; + } + + *out_data = cur_pdata; + + /* num_volumes 4 bytes */ + SIVAL(cur_pdata, 0, shadow_data->num_volumes); + + if (labels) { + /* num_labels 4 bytes */ + SIVAL(cur_pdata, 4, shadow_data->num_volumes); + } + + /* needed_data_count 4 bytes */ + SIVAL(cur_pdata, 8, labels_data_count); + + cur_pdata += 12; + + DBG_DEBUG("FSCTL_GET_SHADOW_COPY_DATA: %u volumes for path[%s].\n", + shadow_data->num_volumes, fsp_str_dbg(fsp)); + if (labels && shadow_data->labels) { + for (i=0; inum_volumes; i++) { + size_t len = 0; + status = srvstr_push(cur_pdata, req_flags, + cur_pdata, shadow_data->labels[i], + 2 * sizeof(SHADOW_COPY_LABEL), + STR_UNICODE|STR_TERMINATE, &len); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(*out_data); + TALLOC_FREE(shadow_data); + return status; + } + cur_pdata += 2 * sizeof(SHADOW_COPY_LABEL); + DEBUGADD(DBGLVL_DEBUG,("Label[%u]: '%s'\n",i,shadow_data->labels[i])); + } + } + + TALLOC_FREE(shadow_data); + + return NT_STATUS_OK; + } + + case FSCTL_FIND_FILES_BY_SID: + { + /* pretend this succeeded - + * + * we have to send back a list with all files owned by this SID + * + * but I have to check that --metze + */ + ssize_t ret; + struct dom_sid sid; + struct dom_sid_buf buf; + uid_t uid; + size_t sid_len; + + DBG_DEBUG("FSCTL_FIND_FILES_BY_SID: called on %s\n", + fsp_fnum_dbg(fsp)); + + if (in_len < 8) { + /* NT_STATUS_BUFFER_TOO_SMALL maybe? */ + return NT_STATUS_INVALID_PARAMETER; + } + + sid_len = MIN(in_len - 4,SID_MAX_SIZE); + + /* unknown 4 bytes: this is not the length of the sid :-( */ + /*unknown = IVAL(pdata,0);*/ + + ret = sid_parse(_in_data + 4, sid_len, &sid); + if (ret == -1) { + return NT_STATUS_INVALID_PARAMETER; + } + DEBUGADD(DBGLVL_DEBUG, ("for SID: %s\n", + dom_sid_str_buf(&sid, &buf))); + + if (!sid_to_uid(&sid, &uid)) { + DBG_ERR("sid_to_uid: failed, sid[%s] sid_len[%lu]\n", + dom_sid_str_buf(&sid, &buf), + (unsigned long)sid_len); + uid = (-1); + } + + /* we can take a look at the find source :-) + * + * find ./ -uid $uid -name '*' is what we need here + * + * + * and send 4bytes len and then NULL terminated unicode strings + * for each file + * + * but I don't know how to deal with the paged results + * (maybe we can hang the result anywhere in the fsp struct) + * + * but I don't know how to deal with the paged results + * (maybe we can hang the result anywhere in the fsp struct) + * + * we don't send all files at once + * and at the next we should *not* start from the beginning, + * so we have to cache the result + * + * --metze + */ + + /* this works for now... */ + return NT_STATUS_OK; + } + + case FSCTL_QUERY_ALLOCATED_RANGES: + { + /* FIXME: This is just a dummy reply, telling that all of the + * file is allocated. MKS cp needs that. + * Adding the real allocated ranges via FIEMAP on Linux + * and SEEK_DATA/SEEK_HOLE on Solaris is needed to make + * this FSCTL correct for sparse files. + */ + uint64_t offset, length; + char *out_data_tmp = NULL; + + if (in_len != 16) { + DBG_ERR("FSCTL_QUERY_ALLOCATED_RANGES: data_count(%u) != 16 is invalid!\n", + in_len); + return NT_STATUS_INVALID_PARAMETER; + } + + if (max_out_len < 16) { + DBG_ERR("FSCTL_QUERY_ALLOCATED_RANGES: max_out_len (%u) < 16 is invalid!\n", + max_out_len); + return NT_STATUS_INVALID_PARAMETER; + } + + offset = BVAL(in_data,0); + length = BVAL(in_data,8); + + if (offset + length < offset) { + /* No 64-bit integer wrap. */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* Shouldn't this be SMB_VFS_STAT ... ? */ + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *out_len = 16; + out_data_tmp = talloc_array(ctx, char, *out_len); + if (out_data_tmp == NULL) { + DBG_DEBUG("unable to allocate memory for response\n"); + return NT_STATUS_NO_MEMORY; + } + + if (offset > fsp->fsp_name->st.st_ex_size || + fsp->fsp_name->st.st_ex_size == 0 || + length == 0) { + memset(out_data_tmp, 0, *out_len); + } else { + uint64_t end = offset + length; + end = MIN(end, fsp->fsp_name->st.st_ex_size); + SBVAL(out_data_tmp, 0, 0); + SBVAL(out_data_tmp, 8, end); + } + + *out_data = out_data_tmp; + + return NT_STATUS_OK; + } + + case FSCTL_IS_VOLUME_DIRTY: + { + DBG_DEBUG("FSCTL_IS_VOLUME_DIRTY: called on %s " + "(but remotely not supported)\n", fsp_fnum_dbg(fsp)); + /* + * http://msdn.microsoft.com/en-us/library/cc232128%28PROT.10%29.aspx + * says we have to respond with NT_STATUS_INVALID_PARAMETER + */ + return NT_STATUS_INVALID_PARAMETER; + } + + default: + /* + * Only print once ... unfortunately there could be lots of + * different FSCTLs that are called. + */ + if (!vfswrap_logged_ioctl_message) { + vfswrap_logged_ioctl_message = true; + DBG_NOTICE("%s (0x%x): Currently not implemented.\n", + __func__, function); + } + } + + return NT_STATUS_NOT_SUPPORTED; +} + +static bool vfswrap_is_offline(struct connection_struct *conn, + const struct smb_filename *fname); + +struct vfswrap_get_dos_attributes_state { + struct vfs_aio_state aio_state; + connection_struct *conn; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + files_struct *dir_fsp; + struct smb_filename *smb_fname; + uint32_t dosmode; + bool as_root; +}; + +static void vfswrap_get_dos_attributes_getxattr_done(struct tevent_req *subreq); + +static struct tevent_req *vfswrap_get_dos_attributes_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + struct smb_filename *smb_fname) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct vfswrap_get_dos_attributes_state *state = NULL; + + SMB_ASSERT(!is_named_stream(smb_fname)); + + req = tevent_req_create(mem_ctx, &state, + struct vfswrap_get_dos_attributes_state); + if (req == NULL) { + return NULL; + } + + *state = (struct vfswrap_get_dos_attributes_state) { + .conn = dir_fsp->conn, + .mem_ctx = mem_ctx, + .ev = ev, + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + }; + + if (!lp_store_dos_attributes(SNUM(dir_fsp->conn))) { + DBG_ERR("%s: \"smbd async dosmode\" enabled, but " + "\"store dos attributes\" is disabled\n", + dir_fsp->conn->connectpath); + tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_GETXATTRAT_SEND(state, + ev, + dir_fsp, + smb_fname, + SAMBA_XATTR_DOS_ATTRIB, + sizeof(fstring)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + vfswrap_get_dos_attributes_getxattr_done, + req); + + return req; +} + +static void vfswrap_get_dos_attributes_getxattr_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct vfswrap_get_dos_attributes_state *state = + tevent_req_data(req, + struct vfswrap_get_dos_attributes_state); + ssize_t xattr_size; + DATA_BLOB blob = {0}; + char *path = NULL; + char *tofree = NULL; + char pathbuf[PATH_MAX+1]; + ssize_t pathlen; + struct smb_filename smb_fname; + bool offline; + NTSTATUS status; + + xattr_size = SMB_VFS_GETXATTRAT_RECV(subreq, + &state->aio_state, + state, + &blob.data); + TALLOC_FREE(subreq); + if (xattr_size == -1) { + status = map_nt_error_from_unix(state->aio_state.error); + + if (state->as_root) { + tevent_req_nterror(req, status); + return; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + tevent_req_nterror(req, status); + return; + } + + state->as_root = true; + + become_root(); + subreq = SMB_VFS_GETXATTRAT_SEND(state, + state->ev, + state->dir_fsp, + state->smb_fname, + SAMBA_XATTR_DOS_ATTRIB, + sizeof(fstring)); + unbecome_root(); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + vfswrap_get_dos_attributes_getxattr_done, + req); + return; + } + + blob.length = xattr_size; + + status = parse_dos_attribute_blob(state->smb_fname, + blob, + &state->dosmode); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + pathlen = full_path_tos(state->dir_fsp->fsp_name->base_name, + state->smb_fname->base_name, + pathbuf, + sizeof(pathbuf), + &path, + &tofree); + if (pathlen == -1) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + + smb_fname = (struct smb_filename) { + .base_name = path, + .st = state->smb_fname->st, + .flags = state->smb_fname->flags, + .twrp = state->smb_fname->twrp, + }; + + offline = vfswrap_is_offline(state->conn, &smb_fname); + if (offline) { + state->dosmode |= FILE_ATTRIBUTE_OFFLINE; + } + TALLOC_FREE(tofree); + + tevent_req_done(req); + return; +} + +static NTSTATUS vfswrap_get_dos_attributes_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + uint32_t *dosmode) +{ + struct vfswrap_get_dos_attributes_state *state = + tevent_req_data(req, + struct vfswrap_get_dos_attributes_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *aio_state = state->aio_state; + *dosmode = state->dosmode; + tevent_req_received(req); + return NT_STATUS_OK; +} + +static NTSTATUS vfswrap_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + bool offline; + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + offline = vfswrap_is_offline(handle->conn, fsp->fsp_name); + if (offline) { + *dosmode |= FILE_ATTRIBUTE_OFFLINE; + } + + return fget_ea_dos_attribute(fsp, dosmode); +} + +static NTSTATUS vfswrap_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + return set_ea_dos_attribute(handle->conn, fsp->fsp_name, dosmode); +} + +static struct vfs_offload_ctx *vfswrap_offload_ctx; + +struct vfswrap_offload_read_state { + DATA_BLOB token; +}; + +static struct tevent_req *vfswrap_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct vfswrap_offload_read_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct vfswrap_offload_read_state); + if (req == NULL) { + return NULL; + } + + status = vfs_offload_token_ctx_init(fsp->conn->sconn->client, + &vfswrap_offload_ctx); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) { + tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST); + return tevent_req_post(req, ev); + } + + status = vfs_offload_token_create_blob(state, fsp, fsctl, + &state->token); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = vfs_offload_token_db_store_fsp(vfswrap_offload_ctx, fsp, + &state->token); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS vfswrap_offload_read_recv(struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *token) +{ + struct vfswrap_offload_read_state *state = tevent_req_data( + req, struct vfswrap_offload_read_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *flags = 0; + *xferlen = 0; + token->length = state->token.length; + token->data = talloc_move(mem_ctx, &state->token.data); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct vfswrap_offload_write_state { + uint8_t *buf; + bool read_lck_locked; + bool write_lck_locked; + DATA_BLOB *token; + struct tevent_context *src_ev; + struct files_struct *src_fsp; + off_t src_off; + struct tevent_context *dst_ev; + struct files_struct *dst_fsp; + off_t dst_off; + off_t to_copy; + off_t remaining; + off_t copied; + size_t next_io_size; +}; + +static void vfswrap_offload_write_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + bool ok; + + if (state->dst_fsp == NULL) { + return; + } + + ok = change_to_user_and_service_by_fsp(state->dst_fsp); + SMB_ASSERT(ok); + state->dst_fsp = NULL; +} + +static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req); +static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req); + +static struct tevent_req *vfswrap_offload_write_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t to_copy) +{ + struct tevent_req *req; + struct vfswrap_offload_write_state *state = NULL; + /* off_t is signed! */ + off_t max_offset = INT64_MAX - to_copy; + size_t num = MIN(to_copy, COPYCHUNK_MAX_TOTAL_LEN); + files_struct *src_fsp = NULL; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct vfswrap_offload_write_state); + if (req == NULL) { + return NULL; + } + + *state = (struct vfswrap_offload_write_state) { + .token = token, + .src_off = transfer_offset, + .dst_ev = ev, + .dst_fsp = dest_fsp, + .dst_off = dest_off, + .to_copy = to_copy, + .remaining = to_copy, + }; + + tevent_req_set_cleanup_fn(req, vfswrap_offload_write_cleanup); + + switch (fsctl) { + case FSCTL_SRV_COPYCHUNK: + case FSCTL_SRV_COPYCHUNK_WRITE: + break; + + case FSCTL_OFFLOAD_WRITE: + tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED); + return tevent_req_post(req, ev); + + case FSCTL_DUP_EXTENTS_TO_FILE: + DBG_DEBUG("COW clones not supported by vfs_default\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + + default: + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return tevent_req_post(req, ev); + } + + /* + * From here on we assume a copy-chunk fsctl + */ + + if (to_copy == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (state->src_off > max_offset) { + /* + * Protect integer checks below. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (state->src_off < 0) { + /* + * Protect integer checks below. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (state->dst_off > max_offset) { + /* + * Protect integer checks below. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (state->dst_off < 0) { + /* + * Protect integer checks below. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + status = vfs_offload_token_db_fetch_fsp(vfswrap_offload_ctx, + token, &src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + DBG_DEBUG("server side copy chunk of length %" PRIu64 "\n", to_copy); + + status = vfs_offload_token_check_handles(fsctl, src_fsp, dest_fsp); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + ok = change_to_user_and_service_by_fsp(src_fsp); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return tevent_req_post(req, ev); + } + + state->src_ev = src_fsp->conn->sconn->ev_ctx; + state->src_fsp = src_fsp; + + status = vfs_stat_fsp(src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (src_fsp->fsp_name->st.st_ex_size < state->src_off + to_copy) { + /* + * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request + * If the SourceOffset or SourceOffset + Length extends beyond + * the end of file, the server SHOULD<240> treat this as a + * STATUS_END_OF_FILE error. + * ... + * <240> Section 3.3.5.15.6: Windows servers will return + * STATUS_INVALID_VIEW_SIZE instead of STATUS_END_OF_FILE. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE); + return tevent_req_post(req, ev); + } + + status = vfswrap_offload_copy_file_range(req); + if (NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + state->buf = talloc_array(state, uint8_t, num); + if (tevent_req_nomem(state->buf, req)) { + return tevent_req_post(req, ev); + } + + status = vfswrap_offload_write_loop(req); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + return req; +} + +static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req) +{ + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + struct lock_struct lck; + ssize_t nwritten; + NTSTATUS status; + bool same_file; + bool ok; + static bool try_copy_file_range = true; + + if (!try_copy_file_range) { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + same_file = file_id_equal(&state->src_fsp->file_id, + &state->dst_fsp->file_id); + if (same_file && + sys_io_ranges_overlap(state->remaining, + state->src_off, + state->remaining, + state->dst_off)) + { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + if (fsp_is_alternate_stream(state->src_fsp) || + fsp_is_alternate_stream(state->dst_fsp)) + { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + init_strict_lock_struct(state->src_fsp, + state->src_fsp->op->global->open_persistent_id, + state->src_off, + state->remaining, + READ_LOCK, + lp_posix_cifsu_locktype(state->src_fsp), + &lck); + + ok = SMB_VFS_STRICT_LOCK_CHECK(state->src_fsp->conn, + state->src_fsp, + &lck); + if (!ok) { + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + ok = change_to_user_and_service_by_fsp(state->dst_fsp); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + + init_strict_lock_struct(state->dst_fsp, + state->dst_fsp->op->global->open_persistent_id, + state->dst_off, + state->remaining, + WRITE_LOCK, + lp_posix_cifsu_locktype(state->dst_fsp), + &lck); + + ok = SMB_VFS_STRICT_LOCK_CHECK(state->dst_fsp->conn, + state->dst_fsp, + &lck); + if (!ok) { + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + while (state->remaining > 0) { + nwritten = copy_file_range(fsp_get_io_fd(state->src_fsp), + &state->src_off, + fsp_get_io_fd(state->dst_fsp), + &state->dst_off, + state->remaining, + 0); + if (nwritten == -1) { + DBG_DEBUG("copy_file_range src [%s]:[%jd] dst [%s]:[%jd] " + "n [%jd] failed: %s\n", + fsp_str_dbg(state->src_fsp), + (intmax_t)state->src_off, + fsp_str_dbg(state->dst_fsp), + (intmax_t)state->dst_off, + (intmax_t)state->remaining, + strerror(errno)); + switch (errno) { + case EOPNOTSUPP: + case ENOSYS: + try_copy_file_range = false; + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + break; + case EXDEV: + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + break; + default: + status = map_nt_error_from_unix(errno); + if (NT_STATUS_EQUAL( + status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) + { + /* Avoid triggering the fallback */ + status = NT_STATUS_INTERNAL_ERROR; + } + break; + } + return status; + } + + if (state->remaining < nwritten) { + DBG_DEBUG("copy_file_range src [%s] dst [%s] " + "n [%jd] remaining [%jd]\n", + fsp_str_dbg(state->src_fsp), + fsp_str_dbg(state->dst_fsp), + (intmax_t)nwritten, + (intmax_t)state->remaining); + return NT_STATUS_INTERNAL_ERROR; + } + + if (nwritten == 0) { + break; + } + state->copied += nwritten; + state->remaining -= nwritten; + } + + /* + * Tell the req cleanup function there's no need to call + * change_to_user_and_service_by_fsp() on the dst handle. + */ + state->dst_fsp = NULL; + return NT_STATUS_OK; +} + +static void vfswrap_offload_write_read_done(struct tevent_req *subreq); + +static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req) +{ + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + struct tevent_req *subreq = NULL; + struct lock_struct read_lck; + bool ok; + + /* + * This is called under the context of state->src_fsp. + */ + + state->next_io_size = MIN(state->remaining, talloc_array_length(state->buf)); + + init_strict_lock_struct(state->src_fsp, + state->src_fsp->op->global->open_persistent_id, + state->src_off, + state->next_io_size, + READ_LOCK, + lp_posix_cifsu_locktype(state->src_fsp), + &read_lck); + + ok = SMB_VFS_STRICT_LOCK_CHECK(state->src_fsp->conn, + state->src_fsp, + &read_lck); + if (!ok) { + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + subreq = SMB_VFS_PREAD_SEND(state, + state->src_ev, + state->src_fsp, + state->buf, + state->next_io_size, + state->src_off); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, vfswrap_offload_write_read_done, req); + + return NT_STATUS_OK; +} + +static void vfswrap_offload_write_write_done(struct tevent_req *subreq); + +static void vfswrap_offload_write_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + struct vfs_aio_state aio_state; + struct lock_struct write_lck; + ssize_t nread; + bool ok; + + nread = SMB_VFS_PREAD_RECV(subreq, &aio_state); + TALLOC_FREE(subreq); + if (nread == -1) { + DBG_ERR("read failed: %s\n", strerror(aio_state.error)); + tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error)); + return; + } + if (nread != state->next_io_size) { + DBG_ERR("Short read, only %zd of %zu\n", + nread, state->next_io_size); + tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR); + return; + } + + state->src_off += nread; + + ok = change_to_user_and_service_by_fsp(state->dst_fsp); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + init_strict_lock_struct(state->dst_fsp, + state->dst_fsp->op->global->open_persistent_id, + state->dst_off, + state->next_io_size, + WRITE_LOCK, + lp_posix_cifsu_locktype(state->dst_fsp), + &write_lck); + + ok = SMB_VFS_STRICT_LOCK_CHECK(state->dst_fsp->conn, + state->dst_fsp, + &write_lck); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT); + return; + } + + subreq = SMB_VFS_PWRITE_SEND(state, + state->dst_ev, + state->dst_fsp, + state->buf, + state->next_io_size, + state->dst_off); + if (subreq == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + tevent_req_set_callback(subreq, vfswrap_offload_write_write_done, req); +} + +static void vfswrap_offload_write_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + struct vfs_aio_state aio_state; + ssize_t nwritten; + NTSTATUS status; + bool ok; + + nwritten = SMB_VFS_PWRITE_RECV(subreq, &aio_state); + TALLOC_FREE(subreq); + if (nwritten == -1) { + DBG_ERR("write failed: %s\n", strerror(aio_state.error)); + tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error)); + return; + } + if (nwritten != state->next_io_size) { + DBG_ERR("Short write, only %zd of %zu\n", nwritten, state->next_io_size); + tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR); + return; + } + + state->dst_off += nwritten; + + if (state->remaining < nwritten) { + /* Paranoia check */ + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + state->copied += nwritten; + state->remaining -= nwritten; + if (state->remaining == 0) { + tevent_req_done(req); + return; + } + + ok = change_to_user_and_service_by_fsp(state->src_fsp); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + status = vfswrap_offload_write_loop(req); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + return; +} + +static NTSTATUS vfswrap_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + struct vfswrap_offload_write_state *state = tevent_req_data( + req, struct vfswrap_offload_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DBG_DEBUG("copy chunk failed: %s\n", nt_errstr(status)); + *copied = 0; + tevent_req_received(req); + return status; + } + + *copied = state->copied; + DBG_DEBUG("copy chunk copied %lu\n", (unsigned long)*copied); + tevent_req_received(req); + + return NT_STATUS_OK; +} + +static NTSTATUS vfswrap_fget_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + return NT_STATUS_INVALID_DEVICE_REQUEST; +} + +static NTSTATUS vfswrap_set_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + return NT_STATUS_INVALID_DEVICE_REQUEST; +} + +/******************************************************************** + Given a stat buffer return the allocated size on disk, taking into + account sparse files. +********************************************************************/ +static uint64_t vfswrap_get_alloc_size(vfs_handle_struct *handle, + struct files_struct *fsp, + const SMB_STRUCT_STAT *sbuf) +{ + uint64_t result; + + START_PROFILE(syscall_get_alloc_size); + + if(S_ISDIR(sbuf->st_ex_mode)) { + result = 0; + goto out; + } + +#if defined(HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE) + /* The type of st_blocksize is blkcnt_t which *MUST* be + signed (according to POSIX) and can be less than 64-bits. + Ensure when we're converting to 64 bits wide we don't + sign extend. */ +#if defined(SIZEOF_BLKCNT_T_8) + result = (uint64_t)STAT_ST_BLOCKSIZE * (uint64_t)sbuf->st_ex_blocks; +#elif defined(SIZEOF_BLKCNT_T_4) + { + uint64_t bs = ((uint64_t)sbuf->st_ex_blocks) & 0xFFFFFFFFLL; + result = (uint64_t)STAT_ST_BLOCKSIZE * bs; + } +#else +#error SIZEOF_BLKCNT_T_NOT_A_SUPPORTED_VALUE +#endif + if (result == 0) { + /* + * Some file systems do not allocate a block for very + * small files. But for non-empty file should report a + * positive size. + */ + + uint64_t filesize = get_file_size_stat(sbuf); + if (filesize > 0) { + result = MIN((uint64_t)STAT_ST_BLOCKSIZE, filesize); + } + } +#else + result = get_file_size_stat(sbuf); +#endif + + if (fsp && fsp->initial_allocation_size) + result = MAX(result,fsp->initial_allocation_size); + + result = smb_roundup(handle->conn, result); + + out: + END_PROFILE(syscall_get_alloc_size); + return result; +} + +static int vfswrap_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int result = -1; + + START_PROFILE(syscall_unlinkat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = unlinkat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + flags); + + END_PROFILE(syscall_unlinkat); + return result; +} + +static int vfswrap_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) +{ + int result; + + START_PROFILE(syscall_fchmod); + + if (!fsp->fsp_flags.is_pathref) { + result = fchmod(fsp_get_io_fd(fsp), mode); + END_PROFILE(syscall_fchmod); + return result; + } + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + result = chmod(sys_proc_fd_path(fd, &buf), mode); + + END_PROFILE(syscall_fchmod); + return result; + } + + /* + * This is no longer a handle based call. + */ + result = chmod(fsp->fsp_name->base_name, mode); + + END_PROFILE(syscall_fchmod); + return result; +} + +static int vfswrap_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid) +{ +#ifdef HAVE_FCHOWN + int result; + + START_PROFILE(syscall_fchown); + if (!fsp->fsp_flags.is_pathref) { + result = fchown(fsp_get_io_fd(fsp), uid, gid); + END_PROFILE(syscall_fchown); + return result; + } + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + result = chown(sys_proc_fd_path(fd, &buf), uid, gid); + + END_PROFILE(syscall_fchown); + return result; + } + + /* + * This is no longer a handle based call. + */ + result = chown(fsp->fsp_name->base_name, uid, gid); + END_PROFILE(syscall_fchown); + return result; +#else + errno = ENOSYS; + return -1; +#endif +} + +static int vfswrap_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int result; + + START_PROFILE(syscall_lchown); + result = lchown(smb_fname->base_name, uid, gid); + END_PROFILE(syscall_lchown); + return result; +} + +static int vfswrap_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int result; + + START_PROFILE(syscall_chdir); + result = chdir(smb_fname->base_name); + END_PROFILE(syscall_chdir); + return result; +} + +static struct smb_filename *vfswrap_getwd(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + char *result; + struct smb_filename *smb_fname = NULL; + + START_PROFILE(syscall_getwd); + result = sys_getwd(); + END_PROFILE(syscall_getwd); + + if (result == NULL) { + return NULL; + } + smb_fname = synthetic_smb_fname(ctx, + result, + NULL, + NULL, + 0, + 0); + /* + * sys_getwd() *always* returns malloced memory. + * We must free here to avoid leaks: + * BUG:https://bugzilla.samba.org/show_bug.cgi?id=13372 + */ + SAFE_FREE(result); + return smb_fname; +} + +/********************************************************************* + nsec timestamp resolution call. Convert down to whatever the underlying + system will support. +**********************************************************************/ + +static int vfswrap_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int result = -1; + struct timespec ts[2]; + struct timespec *times = NULL; + + START_PROFILE(syscall_fntimes); + + if (fsp_is_alternate_stream(fsp)) { + errno = ENOENT; + goto out; + } + + if (ft != NULL) { + if (is_omit_timespec(&ft->atime)) { + ft->atime = fsp->fsp_name->st.st_ex_atime; + } + + if (is_omit_timespec(&ft->mtime)) { + ft->mtime = fsp->fsp_name->st.st_ex_mtime; + } + + if (!is_omit_timespec(&ft->create_time)) { + set_create_timespec_ea(fsp, + ft->create_time); + } + + if ((timespec_compare(&ft->atime, + &fsp->fsp_name->st.st_ex_atime) == 0) && + (timespec_compare(&ft->mtime, + &fsp->fsp_name->st.st_ex_mtime) == 0)) { + result = 0; + goto out; + } + + ts[0] = ft->atime; + ts[1] = ft->mtime; + times = ts; + } else { + times = NULL; + } + + if (!fsp->fsp_flags.is_pathref) { + result = futimens(fsp_get_io_fd(fsp), times); + goto out; + } + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + result = utimensat(AT_FDCWD, + sys_proc_fd_path(fd, &buf), + times, + 0); + + goto out; + } + + /* + * The fd is a pathref (opened with O_PATH) and there isn't fd to + * path translation mechanism. Fallback to path based call. + */ + result = utimensat(AT_FDCWD, fsp->fsp_name->base_name, times, 0); + +out: + END_PROFILE(syscall_fntimes); + + return result; +} + + +/********************************************************************* + A version of ftruncate that will write the space on disk if strict + allocate is set. +**********************************************************************/ + +static int strict_allocate_ftruncate(vfs_handle_struct *handle, files_struct *fsp, off_t len) +{ + off_t space_to_write; + uint64_t space_avail; + uint64_t bsize,dfree,dsize; + int ret; + NTSTATUS status; + SMB_STRUCT_STAT *pst; + bool ok; + + ok = vfs_valid_pwrite_range(len, 0); + if (!ok) { + errno = EINVAL; + return -1; + } + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + pst = &fsp->fsp_name->st; + +#ifdef S_ISFIFO + if (S_ISFIFO(pst->st_ex_mode)) + return 0; +#endif + + if (pst->st_ex_size == len) + return 0; + + /* Shrink - just ftruncate. */ + if (pst->st_ex_size > len) + return ftruncate(fsp_get_io_fd(fsp), len); + + space_to_write = len - pst->st_ex_size; + + /* for allocation try fallocate first. This can fail on some + platforms e.g. when the filesystem doesn't support it and no + emulation is being done by the libc (like on AIX with JFS1). In that + case we do our own emulation. fallocate implementations can + return ENOTSUP or EINVAL in cases like that. */ + ret = SMB_VFS_FALLOCATE(fsp, 0, pst->st_ex_size, space_to_write); + if (ret == -1 && errno == ENOSPC) { + return -1; + } + if (ret == 0) { + return 0; + } + DBG_DEBUG("strict_allocate_ftruncate: SMB_VFS_FALLOCATE failed with " + "error %d. Falling back to slow manual allocation\n", errno); + + /* available disk space is enough or not? */ + space_avail = + get_dfree_info(fsp->conn, fsp->fsp_name, &bsize, &dfree, &dsize); + /* space_avail is 1k blocks */ + if (space_avail == (uint64_t)-1 || + ((uint64_t)space_to_write/1024 > space_avail) ) { + errno = ENOSPC; + return -1; + } + + /* Write out the real space on disk. */ + ret = vfs_slow_fallocate(fsp, pst->st_ex_size, space_to_write); + if (ret != 0) { + return -1; + } + + return 0; +} + +static int vfswrap_ftruncate(vfs_handle_struct *handle, files_struct *fsp, off_t len) +{ + int result = -1; + SMB_STRUCT_STAT *pst; + NTSTATUS status; + char c = 0; + + START_PROFILE(syscall_ftruncate); + + if (lp_strict_allocate(SNUM(fsp->conn)) && !fsp->fsp_flags.is_sparse) { + result = strict_allocate_ftruncate(handle, fsp, len); + END_PROFILE(syscall_ftruncate); + return result; + } + + /* we used to just check HAVE_FTRUNCATE_EXTEND and only use + ftruncate if the system supports it. Then I discovered that + you can have some filesystems that support ftruncate + expansion and some that don't! On Linux fat can't do + ftruncate extend but ext2 can. */ + + result = ftruncate(fsp_get_io_fd(fsp), len); + + /* According to W. R. Stevens advanced UNIX prog. Pure 4.3 BSD cannot + extend a file with ftruncate. Provide alternate implementation + for this */ + + /* Do an fstat to see if the file is longer than the requested + size in which case the ftruncate above should have + succeeded or shorter, in which case seek to len - 1 and + write 1 byte of zero */ + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* We need to update the files_struct after successful ftruncate */ + if (result == 0) { + goto done; + } + + pst = &fsp->fsp_name->st; + +#ifdef S_ISFIFO + if (S_ISFIFO(pst->st_ex_mode)) { + result = 0; + goto done; + } +#endif + + if (pst->st_ex_size == len) { + result = 0; + goto done; + } + + if (pst->st_ex_size > len) { + /* the ftruncate should have worked */ + goto done; + } + + if (SMB_VFS_PWRITE(fsp, &c, 1, len-1)!=1) { + goto done; + } + + result = 0; + + done: + + END_PROFILE(syscall_ftruncate); + return result; +} + +static int vfswrap_fallocate(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + int result; + + START_PROFILE(syscall_fallocate); + if (mode == 0) { + result = sys_posix_fallocate(fsp_get_io_fd(fsp), offset, len); + /* + * posix_fallocate returns 0 on success, errno on error + * and doesn't set errno. Make it behave like fallocate() + * which returns -1, and sets errno on failure. + */ + if (result != 0) { + errno = result; + result = -1; + } + } else { + /* sys_fallocate handles filtering of unsupported mode flags */ + result = sys_fallocate(fsp_get_io_fd(fsp), mode, offset, len); + } + END_PROFILE(syscall_fallocate); + return result; +} + +static bool vfswrap_lock(vfs_handle_struct *handle, files_struct *fsp, int op, off_t offset, off_t count, int type) +{ + bool result; + + START_PROFILE(syscall_fcntl_lock); + + if (fsp->fsp_flags.use_ofd_locks) { + op = map_process_lock_to_ofd_lock(op); + } + + result = fcntl_lock(fsp_get_io_fd(fsp), op, offset, count, type); + END_PROFILE(syscall_fcntl_lock); + return result; +} + +static int vfswrap_filesystem_sharemode(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + errno = ENOTSUP; + return -1; +} + +static int vfswrap_fcntl(vfs_handle_struct *handle, files_struct *fsp, int cmd, + va_list cmd_arg) +{ + void *argp; + va_list dup_cmd_arg; + int result; + int val; + + START_PROFILE(syscall_fcntl); + + va_copy(dup_cmd_arg, cmd_arg); + + switch(cmd) { + case F_SETLK: + case F_SETLKW: + case F_GETLK: +#if defined(HAVE_OFD_LOCKS) + case F_OFD_SETLK: + case F_OFD_SETLKW: + case F_OFD_GETLK: +#endif +#if defined(HAVE_F_OWNER_EX) + case F_GETOWN_EX: + case F_SETOWN_EX: +#endif +#if defined(HAVE_RW_HINTS) + case F_GET_RW_HINT: + case F_SET_RW_HINT: + case F_GET_FILE_RW_HINT: + case F_SET_FILE_RW_HINT: +#endif + argp = va_arg(dup_cmd_arg, void *); + result = sys_fcntl_ptr(fsp_get_io_fd(fsp), cmd, argp); + break; + default: + val = va_arg(dup_cmd_arg, int); + result = sys_fcntl_int(fsp_get_io_fd(fsp), cmd, val); + } + + va_end(dup_cmd_arg); + + END_PROFILE(syscall_fcntl); + return result; +} + +static bool vfswrap_getlock(vfs_handle_struct *handle, files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid) +{ + bool result; + int op = F_GETLK; + + START_PROFILE(syscall_fcntl_getlock); + + if (fsp->fsp_flags.use_ofd_locks) { + op = map_process_lock_to_ofd_lock(op); + } + + result = fcntl_getlock(fsp_get_io_fd(fsp), op, poffset, pcount, ptype, ppid); + END_PROFILE(syscall_fcntl_getlock); + return result; +} + +static int vfswrap_linux_setlease(vfs_handle_struct *handle, files_struct *fsp, + int leasetype) +{ + int result = -1; + + START_PROFILE(syscall_linux_setlease); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + +#ifdef HAVE_KERNEL_OPLOCKS_LINUX + result = linux_setlease(fsp_get_io_fd(fsp), leasetype); +#else + errno = ENOSYS; +#endif + END_PROFILE(syscall_linux_setlease); + return result; +} + +static int vfswrap_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_target, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int result; + + START_PROFILE(syscall_symlinkat); + + SMB_ASSERT(!is_named_stream(new_smb_fname)); + + result = symlinkat(link_target->base_name, + fsp_get_pathref_fd(dirfsp), + new_smb_fname->base_name); + END_PROFILE(syscall_symlinkat); + return result; +} + +static int vfswrap_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + int result; + + START_PROFILE(syscall_readlinkat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = readlinkat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + buf, + bufsiz); + + END_PROFILE(syscall_readlinkat); + return result; +} + +static int vfswrap_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int result; + + START_PROFILE(syscall_linkat); + + SMB_ASSERT(!is_named_stream(old_smb_fname)); + SMB_ASSERT(!is_named_stream(new_smb_fname)); + + result = linkat(fsp_get_pathref_fd(srcfsp), + old_smb_fname->base_name, + fsp_get_pathref_fd(dstfsp), + new_smb_fname->base_name, + flags); + + END_PROFILE(syscall_linkat); + return result; +} + +static int vfswrap_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + int result; + + START_PROFILE(syscall_mknodat); + + SMB_ASSERT(!is_named_stream(smb_fname)); + + result = sys_mknodat(fsp_get_pathref_fd(dirfsp), + smb_fname->base_name, + mode, + dev); + + END_PROFILE(syscall_mknodat); + return result; +} + +static struct smb_filename *vfswrap_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + char *result; + struct smb_filename *result_fname = NULL; + + START_PROFILE(syscall_realpath); + result = sys_realpath(smb_fname->base_name); + END_PROFILE(syscall_realpath); + if (result) { + result_fname = synthetic_smb_fname(ctx, + result, + NULL, + NULL, + 0, + 0); + SAFE_FREE(result); + } + return result_fname; +} + +static int vfswrap_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ +#ifdef HAVE_FCHFLAGS + int fd = fsp_get_pathref_fd(fsp); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (!fsp->fsp_flags.is_pathref) { + return fchflags(fd, flags); + } + + if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + return chflags(sys_proc_fd_path(fd, &buf), flags); + } + + /* + * This is no longer a handle based call. + */ + return chflags(fsp->fsp_name->base_name, flags); +#else + errno = ENOSYS; + return -1; +#endif +} + +static struct file_id vfswrap_file_id_create(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id key; + + /* the ZERO_STRUCT ensures padding doesn't break using the key as a + * blob */ + ZERO_STRUCT(key); + + key.devid = sbuf->st_ex_dev; + key.inode = sbuf->st_ex_ino; + /* key.extid is unused by default. */ + + return key; +} + +static uint64_t vfswrap_fs_file_id(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *psbuf) +{ + uint64_t file_id; + + if (handle->conn->base_share_dev == psbuf->st_ex_dev) { + return (uint64_t)psbuf->st_ex_ino; + } + + /* FileIDLow */ + file_id = ((psbuf->st_ex_ino) & UINT32_MAX); + + /* FileIDHigh */ + file_id |= ((uint64_t)((psbuf->st_ex_dev) & UINT32_MAX)) << 32; + + return file_id; +} + +static NTSTATUS vfswrap_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *tmp_streams = NULL; + unsigned int num_streams = *pnum_streams; + struct stream_struct *streams = *pstreams; + NTSTATUS status; + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (fsp->fsp_flags.is_directory) { + /* + * No default streams on directories + */ + goto done; + } + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_streams + 1 < 1) { + /* Integer wrap. */ + return NT_STATUS_INVALID_PARAMETER; + } + + tmp_streams = talloc_realloc(mem_ctx, + streams, + struct stream_struct, + num_streams + 1); + if (tmp_streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + tmp_streams[num_streams].name = talloc_strdup(tmp_streams, "::$DATA"); + if (tmp_streams[num_streams].name == NULL) { + return NT_STATUS_NO_MEMORY; + } + tmp_streams[num_streams].size = fsp->fsp_name->st.st_ex_size; + tmp_streams[num_streams].alloc_size = SMB_VFS_GET_ALLOC_SIZE( + handle->conn, + fsp, + &fsp->fsp_name->st); + num_streams += 1; + + *pnum_streams = num_streams; + *pstreams = tmp_streams; + done: + return NT_STATUS_OK; +} + +static NTSTATUS vfswrap_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + /* + * Don't fall back to get_real_filename so callers can differentiate + * between a full directory scan and an actual case-insensitive stat. + */ + return NT_STATUS_NOT_SUPPORTED; +} + +static const char *vfswrap_connectpath(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + return handle->conn->connectpath; +} + +static NTSTATUS vfswrap_brl_lock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + struct lock_struct *plock) +{ + SMB_ASSERT(plock->lock_flav == WINDOWS_LOCK); + + /* Note: blr is not used in the default implementation. */ + return brl_lock_windows_default(br_lck, plock); +} + +static bool vfswrap_brl_unlock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + const struct lock_struct *plock) +{ + SMB_ASSERT(plock->lock_flav == WINDOWS_LOCK); + + return brl_unlock_windows_default(br_lck, plock); +} + +static bool vfswrap_strict_lock_check(struct vfs_handle_struct *handle, + files_struct *fsp, + struct lock_struct *plock) +{ + SMB_ASSERT(plock->lock_type == READ_LOCK || + plock->lock_type == WRITE_LOCK); + + return strict_lock_check_default(fsp, plock); +} + +/* NT ACL operations. */ + +static NTSTATUS vfswrap_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS result; + + START_PROFILE(fget_nt_acl); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + result = posix_fget_nt_acl(fsp, security_info, + mem_ctx, ppdesc); + END_PROFILE(fget_nt_acl); + return result; +} + +static NTSTATUS vfswrap_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) +{ + NTSTATUS result; + + START_PROFILE(fset_nt_acl); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + result = set_nt_acl(fsp, security_info_sent, psd); + END_PROFILE(fset_nt_acl); + return result; +} + +static NTSTATUS vfswrap_audit_file(struct vfs_handle_struct *handle, + struct smb_filename *file, + struct security_acl *sacl, + uint32_t access_requested, + uint32_t access_denied) +{ + return NT_STATUS_OK; /* Nothing to do here ... */ +} + +static SMB_ACL_T vfswrap_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + return sys_acl_get_fd(handle, fsp, type, mem_ctx); +} + +static int vfswrap_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + return sys_acl_set_fd(handle, fsp, type, theacl); +} + +static int vfswrap_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + return sys_acl_delete_def_fd(handle, fsp); +} + +/**************************************************************** + Extended attribute operations. +*****************************************************************/ + +static ssize_t vfswrap_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + void *value, + size_t size) +{ + int fd = fsp_get_pathref_fd(fsp); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (!fsp->fsp_flags.is_pathref) { + return fgetxattr(fd, name, value, size); + } + + if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + return getxattr(sys_proc_fd_path(fd, &buf), name, value, size); + } + + /* + * This is no longer a handle based call. + */ + return getxattr(fsp->fsp_name->base_name, name, value, size); +} + +struct vfswrap_getxattrat_state { + struct tevent_context *ev; + struct vfs_handle_struct *handle; + files_struct *dir_fsp; + const struct smb_filename *smb_fname; + + /* + * The following variables are talloced off "state" which is protected + * by a destructor and thus are guaranteed to be safe to be used in the + * job function in the worker thread. + */ + char *name; + const char *xattr_name; + uint8_t *xattr_value; + struct security_unix_token *token; + + ssize_t xattr_size; + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static int vfswrap_getxattrat_state_destructor( + struct vfswrap_getxattrat_state *state) +{ + return -1; +} + +static void vfswrap_getxattrat_do_sync(struct tevent_req *req); +static void vfswrap_getxattrat_do_async(void *private_data); +static void vfswrap_getxattrat_done(struct tevent_req *subreq); + +static struct tevent_req *vfswrap_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct vfswrap_getxattrat_state *state = NULL; + size_t max_threads = 0; + bool have_per_thread_cwd = false; + bool have_per_thread_creds = false; + bool do_async = false; + + SMB_ASSERT(!is_named_stream(smb_fname)); + + req = tevent_req_create(mem_ctx, &state, + struct vfswrap_getxattrat_state); + if (req == NULL) { + return NULL; + } + *state = (struct vfswrap_getxattrat_state) { + .ev = ev, + .handle = handle, + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + }; + + max_threads = pthreadpool_tevent_max_threads(dir_fsp->conn->sconn->pool); + if (max_threads >= 1) { + /* + * We need a non sync threadpool! + */ + have_per_thread_cwd = per_thread_cwd_supported(); + } +#ifdef HAVE_LINUX_THREAD_CREDENTIALS + have_per_thread_creds = true; +#endif + if (have_per_thread_cwd && have_per_thread_creds) { + do_async = true; + } + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_getxattrat, profile_p, + state->profile_bytes, 0); + + if (fsp_get_pathref_fd(dir_fsp) == -1) { + DBG_ERR("Need a valid directory fd\n"); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + if (alloc_hint > 0) { + state->xattr_value = talloc_zero_array(state, + uint8_t, + alloc_hint); + if (tevent_req_nomem(state->xattr_value, req)) { + return tevent_req_post(req, ev); + } + } + + if (!do_async) { + vfswrap_getxattrat_do_sync(req); + return tevent_req_post(req, ev); + } + + /* + * Now allocate all parameters from a memory context that won't go away + * no matter what. These parameters will get used in threads and we + * can't reliably cancel threads, so all buffers passed to the threads + * must not be freed before all referencing threads terminate. + */ + + state->name = talloc_strdup(state, smb_fname->base_name); + if (tevent_req_nomem(state->name, req)) { + return tevent_req_post(req, ev); + } + + state->xattr_name = talloc_strdup(state, xattr_name); + if (tevent_req_nomem(state->xattr_name, req)) { + return tevent_req_post(req, ev); + } + + /* + * This is a hot codepath so at first glance one might think we should + * somehow optimize away the token allocation and do a + * talloc_reference() or similar black magic instead. But due to the + * talloc_stackframe pool per SMB2 request this should be a simple copy + * without a malloc in most cases. + */ + if (geteuid() == sec_initial_uid()) { + state->token = root_unix_token(state); + } else { + state->token = copy_unix_token( + state, + dir_fsp->conn->session_info->unix_token); + } + if (tevent_req_nomem(state->token, req)) { + return tevent_req_post(req, ev); + } + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, + ev, + dir_fsp->conn->sconn->pool, + vfswrap_getxattrat_do_async, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfswrap_getxattrat_done, req); + + talloc_set_destructor(state, vfswrap_getxattrat_state_destructor); + + return req; +} + +static void vfswrap_getxattrat_do_sync(struct tevent_req *req) +{ + struct vfswrap_getxattrat_state *state = tevent_req_data( + req, struct vfswrap_getxattrat_state); + + state->xattr_size = vfswrap_fgetxattr(state->handle, + state->smb_fname->fsp, + state->xattr_name, + state->xattr_value, + talloc_array_length(state->xattr_value)); + if (state->xattr_size == -1) { + tevent_req_error(req, errno); + return; + } + + tevent_req_done(req); + return; +} + +static void vfswrap_getxattrat_do_async(void *private_data) +{ + struct vfswrap_getxattrat_state *state = talloc_get_type_abort( + private_data, struct vfswrap_getxattrat_state); + struct timespec start_time; + struct timespec end_time; + int ret; + + PROFILE_TIMESTAMP(&start_time); + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + /* + * Here we simulate a getxattrat() + * call using fchdir();getxattr() + */ + + per_thread_cwd_activate(); + + /* Become the correct credential on this thread. */ + ret = set_thread_credentials(state->token->uid, + state->token->gid, + (size_t)state->token->ngroups, + state->token->groups); + if (ret != 0) { + state->xattr_size = -1; + state->vfs_aio_state.error = errno; + goto end_profile; + } + + state->xattr_size = vfswrap_fgetxattr(state->handle, + state->smb_fname->fsp, + state->xattr_name, + state->xattr_value, + talloc_array_length(state->xattr_value)); + if (state->xattr_size == -1) { + state->vfs_aio_state.error = errno; + } + +end_profile: + PROFILE_TIMESTAMP(&end_time); + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static void vfswrap_getxattrat_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfswrap_getxattrat_state *state = tevent_req_data( + req, struct vfswrap_getxattrat_state); + int ret; + bool ok; + + /* + * Make sure we run as the user again + */ + ok = change_to_user_and_service_by_fsp(state->dir_fsp); + SMB_ASSERT(ok); + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfswrap_getxattrat_do_sync(req); + return; + } + + if (state->xattr_size == -1) { + tevent_req_error(req, state->vfs_aio_state.error); + return; + } + + if (state->xattr_value == NULL) { + /* + * The caller only wanted the size. + */ + tevent_req_done(req); + return; + } + + /* + * shrink the buffer to the returned size. + * (can't fail). It means NULL if size is 0. + */ + state->xattr_value = talloc_realloc(state, + state->xattr_value, + uint8_t, + state->xattr_size); + + tevent_req_done(req); +} + +static ssize_t vfswrap_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct vfswrap_getxattrat_state *state = tevent_req_data( + req, struct vfswrap_getxattrat_state); + ssize_t xattr_size; + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *aio_state = state->vfs_aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +static ssize_t vfswrap_flistxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size) +{ + int fd = fsp_get_pathref_fd(fsp); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (!fsp->fsp_flags.is_pathref) { + return flistxattr(fd, list, size); + } + + if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + return listxattr(sys_proc_fd_path(fd, &buf), list, size); + } + + /* + * This is no longer a handle based call. + */ + return listxattr(fsp->fsp_name->base_name, list, size); +} + +static int vfswrap_fremovexattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name) +{ + int fd = fsp_get_pathref_fd(fsp); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (!fsp->fsp_flags.is_pathref) { + return fremovexattr(fd, name); + } + + if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + return removexattr(sys_proc_fd_path(fd, &buf), name); + } + + /* + * This is no longer a handle based call. + */ + return removexattr(fsp->fsp_name->base_name, name); +} + +static int vfswrap_fsetxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags) +{ + int fd = fsp_get_pathref_fd(fsp); + + SMB_ASSERT(!fsp_is_alternate_stream(fsp)); + + if (!fsp->fsp_flags.is_pathref) { + return fsetxattr(fd, name, value, size, flags); + } + + if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + return setxattr(sys_proc_fd_path(fd, &buf), + name, + value, + size, + flags); + } + + /* + * This is no longer a handle based call. + */ + return setxattr(fsp->fsp_name->base_name, name, value, size, flags); +} + +static bool vfswrap_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp) +{ + return false; +} + +static bool vfswrap_is_offline(struct connection_struct *conn, + const struct smb_filename *fname) +{ + NTSTATUS status; + char *path; + bool offline = false; + + if (ISDOT(fname->base_name) || ISDOTDOT(fname->base_name)) { + return false; + } + + if (!lp_dmapi_support(SNUM(conn)) || !dmapi_have_session()) { +#if defined(ENOTSUP) + errno = ENOTSUP; +#endif + return false; + } + + status = get_full_smb_filename(talloc_tos(), fname, &path); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return false; + } + + offline = (dmapi_file_flags(path) & FILE_ATTRIBUTE_OFFLINE) != 0; + + TALLOC_FREE(path); + + return offline; +} + +static NTSTATUS vfswrap_durable_cookie(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cookie) +{ + return vfs_default_durable_cookie(fsp, mem_ctx, cookie); +} + +static NTSTATUS vfswrap_durable_disconnect(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + DATA_BLOB *new_cookie) +{ + return vfs_default_durable_disconnect(fsp, old_cookie, mem_ctx, + new_cookie); +} + +static NTSTATUS vfswrap_durable_reconnect(struct vfs_handle_struct *handle, + struct smb_request *smb1req, + struct smbXsrv_open *op, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + struct files_struct **fsp, + DATA_BLOB *new_cookie) +{ + return vfs_default_durable_reconnect(handle->conn, smb1req, op, + old_cookie, mem_ctx, + fsp, new_cookie); +} + +static struct vfs_fn_pointers vfs_default_fns = { + /* Disk operations */ + + .connect_fn = vfswrap_connect, + .disconnect_fn = vfswrap_disconnect, + .disk_free_fn = vfswrap_disk_free, + .get_quota_fn = vfswrap_get_quota, + .set_quota_fn = vfswrap_set_quota, + .get_shadow_copy_data_fn = vfswrap_get_shadow_copy_data, + .statvfs_fn = vfswrap_statvfs, + .fs_capabilities_fn = vfswrap_fs_capabilities, + .get_dfs_referrals_fn = vfswrap_get_dfs_referrals, + .create_dfs_pathat_fn = vfswrap_create_dfs_pathat, + .read_dfs_pathat_fn = vfswrap_read_dfs_pathat, + .snap_check_path_fn = vfswrap_snap_check_path, + .snap_create_fn = vfswrap_snap_create, + .snap_delete_fn = vfswrap_snap_delete, + + /* Directory operations */ + + .fdopendir_fn = vfswrap_fdopendir, + .readdir_fn = vfswrap_readdir, + .freaddir_attr_fn = vfswrap_freaddir_attr, + .rewind_dir_fn = vfswrap_rewinddir, + .mkdirat_fn = vfswrap_mkdirat, + .closedir_fn = vfswrap_closedir, + + /* File operations */ + + .openat_fn = vfswrap_openat, + .create_file_fn = vfswrap_create_file, + .close_fn = vfswrap_close, + .pread_fn = vfswrap_pread, + .pread_send_fn = vfswrap_pread_send, + .pread_recv_fn = vfswrap_pread_recv, + .pwrite_fn = vfswrap_pwrite, + .pwrite_send_fn = vfswrap_pwrite_send, + .pwrite_recv_fn = vfswrap_pwrite_recv, + .lseek_fn = vfswrap_lseek, + .sendfile_fn = vfswrap_sendfile, + .recvfile_fn = vfswrap_recvfile, + .renameat_fn = vfswrap_renameat, + .fsync_send_fn = vfswrap_fsync_send, + .fsync_recv_fn = vfswrap_fsync_recv, + .stat_fn = vfswrap_stat, + .fstat_fn = vfswrap_fstat, + .lstat_fn = vfswrap_lstat, + .fstatat_fn = vfswrap_fstatat, + .get_alloc_size_fn = vfswrap_get_alloc_size, + .unlinkat_fn = vfswrap_unlinkat, + .fchmod_fn = vfswrap_fchmod, + .fchown_fn = vfswrap_fchown, + .lchown_fn = vfswrap_lchown, + .chdir_fn = vfswrap_chdir, + .getwd_fn = vfswrap_getwd, + .fntimes_fn = vfswrap_fntimes, + .ftruncate_fn = vfswrap_ftruncate, + .fallocate_fn = vfswrap_fallocate, + .lock_fn = vfswrap_lock, + .filesystem_sharemode_fn = vfswrap_filesystem_sharemode, + .fcntl_fn = vfswrap_fcntl, + .linux_setlease_fn = vfswrap_linux_setlease, + .getlock_fn = vfswrap_getlock, + .symlinkat_fn = vfswrap_symlinkat, + .readlinkat_fn = vfswrap_readlinkat, + .linkat_fn = vfswrap_linkat, + .mknodat_fn = vfswrap_mknodat, + .realpath_fn = vfswrap_realpath, + .fchflags_fn = vfswrap_fchflags, + .file_id_create_fn = vfswrap_file_id_create, + .fs_file_id_fn = vfswrap_fs_file_id, + .fstreaminfo_fn = vfswrap_fstreaminfo, + .get_real_filename_at_fn = vfswrap_get_real_filename_at, + .connectpath_fn = vfswrap_connectpath, + .brl_lock_windows_fn = vfswrap_brl_lock_windows, + .brl_unlock_windows_fn = vfswrap_brl_unlock_windows, + .strict_lock_check_fn = vfswrap_strict_lock_check, + .translate_name_fn = vfswrap_translate_name, + .parent_pathname_fn = vfswrap_parent_pathname, + .fsctl_fn = vfswrap_fsctl, + .fset_dos_attributes_fn = vfswrap_fset_dos_attributes, + .get_dos_attributes_send_fn = vfswrap_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfswrap_get_dos_attributes_recv, + .fget_dos_attributes_fn = vfswrap_fget_dos_attributes, + .offload_read_send_fn = vfswrap_offload_read_send, + .offload_read_recv_fn = vfswrap_offload_read_recv, + .offload_write_send_fn = vfswrap_offload_write_send, + .offload_write_recv_fn = vfswrap_offload_write_recv, + .fget_compression_fn = vfswrap_fget_compression, + .set_compression_fn = vfswrap_set_compression, + + /* NT ACL operations. */ + + .fget_nt_acl_fn = vfswrap_fget_nt_acl, + .fset_nt_acl_fn = vfswrap_fset_nt_acl, + .audit_file_fn = vfswrap_audit_file, + + /* POSIX ACL operations. */ + + .sys_acl_get_fd_fn = vfswrap_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = vfswrap_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = vfswrap_sys_acl_delete_def_fd, + + /* EA operations. */ + .getxattrat_send_fn = vfswrap_getxattrat_send, + .getxattrat_recv_fn = vfswrap_getxattrat_recv, + .fgetxattr_fn = vfswrap_fgetxattr, + .flistxattr_fn = vfswrap_flistxattr, + .fremovexattr_fn = vfswrap_fremovexattr, + .fsetxattr_fn = vfswrap_fsetxattr, + + /* aio operations */ + .aio_force_fn = vfswrap_aio_force, + + /* durable handle operations */ + .durable_cookie_fn = vfswrap_durable_cookie, + .durable_disconnect_fn = vfswrap_durable_disconnect, + .durable_reconnect_fn = vfswrap_durable_reconnect, +}; + +static_decl_vfs; +NTSTATUS vfs_default_init(TALLOC_CTX *ctx) +{ + /* + * Here we need to implement every call! + * + * As this is the end of the vfs module chain. + */ + smb_vfs_assert_all_fns(&vfs_default_fns, DEFAULT_VFS_MODULE_NAME); + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + DEFAULT_VFS_MODULE_NAME, &vfs_default_fns); +} + + diff --git a/source3/modules/vfs_default_quota.c b/source3/modules/vfs_default_quota.c new file mode 100644 index 0000000..326eb8c --- /dev/null +++ b/source3/modules/vfs_default_quota.c @@ -0,0 +1,236 @@ +/* + * Store default Quotas in a specified quota record + * + * Copyright (C) Stefan (metze) Metzmacher 2003 + * + * 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 . + */ + +/* + * This module allows the default quota values, + * in the windows explorer GUI, to be stored on a samba server. + * The problem is that linux filesystems only store quotas + * for users and groups, but no default quotas. + * + * Samba returns NO_LIMIT as the default quotas by default + * and refuses to update them. + * + * With this module you can store the default quotas that are reported to + * a windows client, in the quota record of a user. By default the root user + * is taken because quota limits for root are typically not enforced. + * + * This module takes 2 parametric parameters in smb.conf: + * (the default prefix for them is 'default_quota', + * it can be overwrittem when you load the module in + * the 'vfs objects' parameter like this: + * vfs objects = default_quota:myprefix) + * + * ":uid" parameter takes a integer argument, + * it specifies the uid of the quota record, that will be taken for + * storing the default USER-quotas. + * + * - default value: '0' (for root user) + * - e.g.: default_quota:uid = 65534 + * + * ":uid nolimit" parameter takes a boolean argument, + * it specifies if we should report the stored default quota values, + * also for the user record, or if you should just report NO_LIMIT + * to the windows client for the user specified by the ":uid" parameter. + * + * - default value: yes (that means to report NO_LIMIT) + * - e.g.: default_quota:uid nolimit = no + * + * ":gid" parameter takes a integer argument, + * it's just like ":uid" but for group quotas. + * (NOTE: group quotas are not supported from the windows explorer!) + * + * - default value: '0' (for root group) + * - e.g.: default_quota:gid = 65534 + * + * ":gid nolimit" parameter takes a boolean argument, + * it's just like ":uid nolimit" but for group quotas. + * (NOTE: group quotas are not supported from the windows explorer!) + * + * - default value: yes (that means to report NO_LIMIT) + * - e.g.: default_quota:uid nolimit = no + * + */ + +#include "includes.h" +#include "smbd/smbd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_QUOTA + +#define DEFAULT_QUOTA_NAME "default_quota" + +#define DEFAULT_QUOTA_UID_DEFAULT 0 +#define DEFAULT_QUOTA_UID_NOLIMIT_DEFAULT True +#define DEFAULT_QUOTA_GID_DEFAULT 0 +#define DEFAULT_QUOTA_GID_NOLIMIT_DEFAULT True + +#define DEFAULT_QUOTA_UID(handle) \ + (uid_t)lp_parm_int(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"uid",DEFAULT_QUOTA_UID_DEFAULT) + +#define DEFAULT_QUOTA_UID_NOLIMIT(handle) \ + lp_parm_bool(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"uid nolimit",DEFAULT_QUOTA_UID_NOLIMIT_DEFAULT) + +#define DEFAULT_QUOTA_GID(handle) \ + (gid_t)lp_parm_int(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"gid",DEFAULT_QUOTA_GID_DEFAULT) + +#define DEFAULT_QUOTA_GID_NOLIMIT(handle) \ + lp_parm_bool(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"gid nolimit",DEFAULT_QUOTA_GID_NOLIMIT_DEFAULT) + +static int default_quota_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + int ret = -1; + + if ((ret = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, + qtype, id, dq)) != 0) { + return ret; + } + + switch (qtype) { + case SMB_USER_QUOTA_TYPE: + /* we use id.uid == 0 for default quotas */ + if ((id.uid==DEFAULT_QUOTA_UID(handle)) && + DEFAULT_QUOTA_UID_NOLIMIT(handle)) { + SMB_QUOTAS_SET_NO_LIMIT(dq); + } + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_QUOTA_TYPE: + /* we use id.gid == 0 for default quotas */ + if ((id.gid==DEFAULT_QUOTA_GID(handle)) && + DEFAULT_QUOTA_GID_NOLIMIT(handle)) { + SMB_QUOTAS_SET_NO_LIMIT(dq); + } + break; +#endif /* HAVE_GROUP_QUOTA */ + case SMB_USER_FS_QUOTA_TYPE: + { + unid_t qid; + uint32_t qflags = dq->qflags; + qid.uid = DEFAULT_QUOTA_UID(handle); + SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, + SMB_USER_QUOTA_TYPE, qid, dq); + dq->qflags = qflags; + } + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_FS_QUOTA_TYPE: + { + unid_t qid; + uint32_t qflags = dq->qflags; + qid.gid = DEFAULT_QUOTA_GID(handle); + SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, + SMB_GROUP_QUOTA_TYPE, + qid, dq); + dq->qflags = qflags; + } + break; +#endif /* HAVE_GROUP_QUOTA */ + default: + errno = ENOSYS; + return -1; + break; + } + + return ret; +} + +static int default_quota_set_quota(vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dq) +{ + int ret = -1; + + switch (qtype) { + case SMB_USER_QUOTA_TYPE: + /* we use id.uid == 0 for default quotas */ + if ((id.uid==DEFAULT_QUOTA_UID(handle)) && + DEFAULT_QUOTA_UID_NOLIMIT(handle)) { + return -1; + } + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_QUOTA_TYPE: + /* we use id.gid == 0 for default quotas */ + if ((id.gid==DEFAULT_QUOTA_GID(handle)) && + DEFAULT_QUOTA_GID_NOLIMIT(handle)) { + return -1; + } + break; +#endif /* HAVE_GROUP_QUOTA */ + case SMB_USER_FS_QUOTA_TYPE: + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_FS_QUOTA_TYPE: + break; +#endif /* HAVE_GROUP_QUOTA */ + default: + errno = ENOSYS; + return -1; + break; + } + + if ((ret=SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, dq))!=0) { + return ret; + } + + switch (qtype) { + case SMB_USER_QUOTA_TYPE: + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_QUOTA_TYPE: + break; +#endif /* HAVE_GROUP_QUOTA */ + case SMB_USER_FS_QUOTA_TYPE: + { + unid_t qid; + qid.uid = DEFAULT_QUOTA_UID(handle); + ret = SMB_VFS_NEXT_SET_QUOTA(handle, SMB_USER_QUOTA_TYPE, qid, dq); + } + break; +#ifdef HAVE_GROUP_QUOTA + case SMB_GROUP_FS_QUOTA_TYPE: + { + unid_t qid; + qid.gid = DEFAULT_QUOTA_GID(handle); + ret = SMB_VFS_NEXT_SET_QUOTA(handle, SMB_GROUP_QUOTA_TYPE, qid, dq); + } + break; +#endif /* HAVE_GROUP_QUOTA */ + default: + errno = ENOSYS; + return -1; + break; + } + + return ret; +} + +static struct vfs_fn_pointers vfs_default_quota_fns = { + .get_quota_fn = default_quota_get_quota, + .set_quota_fn = default_quota_set_quota +}; + +static_decl_vfs; +NTSTATUS vfs_default_quota_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, DEFAULT_QUOTA_NAME, + &vfs_default_quota_fns); +} diff --git a/source3/modules/vfs_delay_inject.c b/source3/modules/vfs_delay_inject.c new file mode 100644 index 0000000..e0a881e --- /dev/null +++ b/source3/modules/vfs_delay_inject.c @@ -0,0 +1,440 @@ +/* + * Unix SMB/CIFS implementation. + * Samba VFS module for delay injection in VFS calls + * Copyright (C) Ralph Boehme 2018 + * + * 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 "locking/share_mode_lock.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_unix.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static void inject_delay(const char *vfs_func, vfs_handle_struct *handle) +{ + int delay; + + delay = lp_parm_int(SNUM(handle->conn), "delay_inject", vfs_func, 0); + if (delay == 0) { + return; + } + + DBG_DEBUG("Injected delay for [%s] of [%d] ms\n", vfs_func, delay); + + smb_msleep(delay); +} + +static int vfs_delay_inject_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + inject_delay("fntimes", handle); + + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +struct vfs_delay_inject_pread_state { + struct tevent_context *ev; + struct vfs_handle_struct *handle; + struct files_struct *fsp; + void *data; + size_t n; + off_t offset; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void vfs_delay_inject_pread_wait_done(struct tevent_req *subreq); +static void vfs_delay_inject_pread_done(struct tevent_req *subreq); + +static struct tevent_req *vfs_delay_inject_pread_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, + off_t offset) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct vfs_delay_inject_pread_state *state = NULL; + int delay; + struct timeval delay_tv; + + delay = lp_parm_int( + SNUM(handle->conn), "delay_inject", "pread_send", 0); + delay_tv = tevent_timeval_current_ofs(delay / 1000, + (delay * 1000) % 1000000); + + req = tevent_req_create(mem_ctx, &state, + struct vfs_delay_inject_pread_state); + if (req == NULL) { + return NULL; + } + *state = (struct vfs_delay_inject_pread_state) { + .ev = ev, + .handle = handle, + .fsp = fsp, + .data = data, + .n = n, + .offset = offset, + }; + + if (delay == 0) { + subreq = SMB_VFS_NEXT_PREAD_SEND(state, + state->ev, + state->handle, + state->fsp, + state->data, + state->n, + state->offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + vfs_delay_inject_pread_done, + req); + return req; + } + + subreq = tevent_wakeup_send(state, ev, delay_tv); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_delay_inject_pread_wait_done, req); + return req; +} + + +static void vfs_delay_inject_pread_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_delay_inject_pread_state *state = tevent_req_data( + req, struct vfs_delay_inject_pread_state); + bool ok; + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, EIO); + return; + } + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, + state->ev, + state->handle, + state->fsp, + state->data, + state->n, + state->offset); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, vfs_delay_inject_pread_done, req); +} + +static void vfs_delay_inject_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_delay_inject_pread_state *state = tevent_req_data( + req, struct vfs_delay_inject_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + tevent_req_done(req); +} + +static ssize_t vfs_delay_inject_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_delay_inject_pread_state *state = tevent_req_data( + req, struct vfs_delay_inject_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct vfs_delay_inject_pwrite_state { + struct tevent_context *ev; + struct vfs_handle_struct *handle; + struct files_struct *fsp; + const void *data; + size_t n; + off_t offset; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void vfs_delay_inject_pwrite_wait_done(struct tevent_req *subreq); +static void vfs_delay_inject_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *vfs_delay_inject_pwrite_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, + off_t offset) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct vfs_delay_inject_pwrite_state *state = NULL; + int delay; + struct timeval delay_tv; + + delay = lp_parm_int( + SNUM(handle->conn), "delay_inject", "pwrite_send", 0); + delay_tv = tevent_timeval_current_ofs(delay / 1000, + (delay * 1000) % 1000000); + + req = tevent_req_create(mem_ctx, &state, + struct vfs_delay_inject_pwrite_state); + if (req == NULL) { + return NULL; + } + *state = (struct vfs_delay_inject_pwrite_state) { + .ev = ev, + .handle = handle, + .fsp = fsp, + .data = data, + .n = n, + .offset = offset, + }; + + if (delay == 0) { + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, + state->ev, + state->handle, + state->fsp, + state->data, + state->n, + state->offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + vfs_delay_inject_pwrite_done, + req); + return req; + } + + subreq = tevent_wakeup_send(state, ev, delay_tv); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, vfs_delay_inject_pwrite_wait_done, req); + return req; +} + + +static void vfs_delay_inject_pwrite_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_delay_inject_pwrite_state *state = tevent_req_data( + req, struct vfs_delay_inject_pwrite_state); + bool ok; + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, EIO); + return; + } + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, + state->ev, + state->handle, + state->fsp, + state->data, + state->n, + state->offset); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, vfs_delay_inject_pwrite_done, req); +} + +static void vfs_delay_inject_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_delay_inject_pwrite_state *state = tevent_req_data( + req, struct vfs_delay_inject_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + tevent_req_done(req); +} + +static ssize_t vfs_delay_inject_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_delay_inject_pwrite_state *state = tevent_req_data( + req, struct vfs_delay_inject_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct vfs_delay_inject_brl_lock_state { + struct vfs_delay_inject_brl_lock_state *prev, *next; + struct files_struct *fsp; + struct GUID req_guid; + struct timeval delay_tv; + struct tevent_timer *delay_te; +}; + +static struct vfs_delay_inject_brl_lock_state *brl_lock_states; + +static int vfs_delay_inject_brl_lock_state_destructor(struct vfs_delay_inject_brl_lock_state *state) +{ + DLIST_REMOVE(brl_lock_states, state); + return 0; +} + +static void vfs_delay_inject_brl_lock_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct vfs_delay_inject_brl_lock_state *state = + talloc_get_type_abort(private_data, + struct vfs_delay_inject_brl_lock_state); + NTSTATUS status; + + TALLOC_FREE(state->delay_te); + + status = share_mode_wakeup_waiters(state->fsp->file_id); + if (!NT_STATUS_IS_OK(status)) { + struct file_id_buf idbuf; + DBG_ERR("share_mode_wakeup_waiters(%s) %s\n", + file_id_str_buf(state->fsp->file_id, &idbuf), + nt_errstr(status)); + } +} + +static NTSTATUS vfs_delay_inject_brl_lock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + struct lock_struct *plock) +{ + struct files_struct *fsp = brl_fsp(br_lck); + TALLOC_CTX *req_mem_ctx = brl_req_mem_ctx(br_lck); + const struct GUID *req_guid = brl_req_guid(br_lck); + struct vfs_delay_inject_brl_lock_state *state = NULL; + bool expired; + + for (state = brl_lock_states; state != NULL; state = state->next) { + bool match; + + match = GUID_equal(&state->req_guid, req_guid); + if (match) { + break; + } + } + + if (state == NULL) { + int delay; + bool use_timer; + + state = talloc_zero(req_mem_ctx, + struct vfs_delay_inject_brl_lock_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->fsp = fsp; + state->req_guid = *req_guid; + + delay = lp_parm_int(SNUM(handle->conn), + "delay_inject", "brl_lock_windows", 0); + state->delay_tv = timeval_current_ofs_msec(delay); + + use_timer = lp_parm_bool(SNUM(handle->conn), + "delay_inject", "brl_lock_windows_use_timer", true); + + if (use_timer) { + state->delay_te = tevent_add_timer( + global_event_context(), + state, + state->delay_tv, + vfs_delay_inject_brl_lock_timer, + state); + if (state->delay_te == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + talloc_set_destructor(state, + vfs_delay_inject_brl_lock_state_destructor); + DLIST_ADD_END(brl_lock_states, state); + } + + if (state->delay_te != NULL) { + plock->context.smblctx = 0; + return NT_STATUS_RETRY; + } + + expired = timeval_expired(&state->delay_tv); + if (!expired) { + plock->context.smblctx = UINT64_MAX; + return NT_STATUS_RETRY; + } + + TALLOC_FREE(state); + + return SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock); +} + +static bool vfs_delay_inject_brl_unlock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + const struct lock_struct *plock) +{ + return SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock); +} + +static struct vfs_fn_pointers vfs_delay_inject_fns = { + .fntimes_fn = vfs_delay_inject_fntimes, + .pread_send_fn = vfs_delay_inject_pread_send, + .pread_recv_fn = vfs_delay_inject_pread_recv, + .pwrite_send_fn = vfs_delay_inject_pwrite_send, + .pwrite_recv_fn = vfs_delay_inject_pwrite_recv, + + .brl_lock_windows_fn = vfs_delay_inject_brl_lock_windows, + .brl_unlock_windows_fn = vfs_delay_inject_brl_unlock_windows, +}; + +static_decl_vfs; +NTSTATUS vfs_delay_inject_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "delay_inject", + &vfs_delay_inject_fns); +} diff --git a/source3/modules/vfs_dfs_samba4.c b/source3/modules/vfs_dfs_samba4.c new file mode 100644 index 0000000..8b4724f --- /dev/null +++ b/source3/modules/vfs_dfs_samba4.c @@ -0,0 +1,162 @@ +/* + * VFS module to retrieve DFS referrals from AD + * + * Copyright (C) 2007, Stefan Metzmacher + * + * 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 "smbd/smbd.h" +#include "smbd/globals.h" +#include "system/filesys.h" +#include "source3/include/msdfs.h" +#include "librpc/gen_ndr/ndr_dfsblobs.h" +#include "source4/lib/events/events.h" +#include "source4/auth/session.h" +#include "lib/param/param.h" +#include "source4/dsdb/samdb/samdb.h" +#include "dfs_server/dfs_server_ad.h" + +static int vfs_dfs_samba4_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_dfs_samba4_debug_level + +struct dfs_samba4_handle_data { + struct tevent_context *ev; + struct loadparm_context *lp_ctx; + struct ldb_context *sam_ctx; +}; + +static int dfs_samba4_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct dfs_samba4_handle_data *data; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + data = talloc_zero(handle->conn, struct dfs_samba4_handle_data); + if (!data) { + DEBUG(0, ("talloc_zero() failed\n")); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + data->ev = s4_event_context_init(data); + if (!data->ev) { + DEBUG(0, ("s4_event_context_init failed\n")); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + data->lp_ctx = loadparm_init_s3(data, loadparm_s3_helpers()); + if (data->lp_ctx == NULL) { + DEBUG(0, ("loadparm_init_s3 failed\n")); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + data->sam_ctx = samdb_connect(data, + data->ev, + data->lp_ctx, + system_session(data->lp_ctx), + NULL, + 0); + if (!data->sam_ctx) { + DEBUG(0, ("samdb_connect failed\n")); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + SMB_VFS_HANDLE_SET_DATA(handle, data, NULL, + struct dfs_samba4_handle_data, + return -1); + + DEBUG(10,("dfs_samba4: connect to service[%s]\n", + service)); + + return 0; +} + +static void dfs_samba4_disconnect(struct vfs_handle_struct *handle) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(10,("dfs_samba4_disconnect() connect to service[%s].\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn)))); + + SMB_VFS_NEXT_DISCONNECT(handle); +} + +static NTSTATUS dfs_samba4_get_referrals(struct vfs_handle_struct *handle, + struct dfs_GetDFSReferral *r) +{ + struct dfs_samba4_handle_data *data; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, data, + struct dfs_samba4_handle_data, + return NT_STATUS_INTERNAL_ERROR); + + DEBUG(8, ("dfs_samba4: Requested DFS name: %s utf16-length: %u\n", + r->in.req.servername, + (unsigned int)strlen_m(r->in.req.servername)*2)); + + status = dfs_server_ad_get_referrals(data->lp_ctx, + data->sam_ctx, + handle->conn->sconn->remote_address, + r); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static struct vfs_fn_pointers vfs_dfs_samba4_fns = { + .connect_fn = dfs_samba4_connect, + .disconnect_fn = dfs_samba4_disconnect, + .get_dfs_referrals_fn = dfs_samba4_get_referrals, +}; + +static_decl_vfs; +NTSTATUS vfs_dfs_samba4_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "dfs_samba4", + &vfs_dfs_samba4_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + vfs_dfs_samba4_debug_level = debug_add_class("dfs_samba4"); + if (vfs_dfs_samba4_debug_level == -1) { + vfs_dfs_samba4_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_dfs_samba4: Couldn't register custom debugging class!\n")); + } else { + DEBUG(10, ("vfs_dfs_samba4: Debug class number of 'fileid': %d\n", + vfs_dfs_samba4_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_dirsort.c b/source3/modules/vfs_dirsort.c new file mode 100644 index 0000000..c4baf81 --- /dev/null +++ b/source3/modules/vfs_dirsort.c @@ -0,0 +1,267 @@ +/* + * VFS module to provide a sorted directory list. + * + * Copyright (C) Andy Kelk (andy@mopoke.co.uk), 2009 + * + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" + +static int compare_dirent (const struct dirent *da, const struct dirent *db) +{ + return strcasecmp_m(da->d_name, db->d_name); +} + +struct dirsort_privates { + struct dirsort_privates *prev, *next; + long pos; + struct dirent *directory_list; + unsigned int number_of_entries; + struct timespec mtime; + DIR *source_directory; + files_struct *fsp; /* If open via FDOPENDIR. */ + struct smb_filename *smb_fname; /* If open via OPENDIR */ +}; + +static bool get_sorted_dir_mtime(vfs_handle_struct *handle, + struct dirsort_privates *data, + struct timespec *ret_mtime) +{ + int ret; + struct timespec mtime; + NTSTATUS status; + + if (data->fsp) { + status = vfs_stat_fsp(data->fsp); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + mtime = data->fsp->fsp_name->st.st_ex_mtime; + } else { + ret = SMB_VFS_STAT(handle->conn, data->smb_fname); + if (ret == -1) { + return false; + } + mtime = data->smb_fname->st.st_ex_mtime; + } + + *ret_mtime = mtime; + + return true; +} + +static bool open_and_sort_dir(vfs_handle_struct *handle, + struct dirsort_privates *data) +{ + uint32_t total_count = 0; + /* This should be enough for most use cases */ + uint32_t dirent_allocated = 64; + struct dirent *dp; + + data->number_of_entries = 0; + + if (get_sorted_dir_mtime(handle, data, &data->mtime) == false) { + return false; + } + + dp = SMB_VFS_NEXT_READDIR(handle, data->fsp, data->source_directory); + if (dp == NULL) { + return false; + } + + /* Set up an array and read the directory entries into it */ + TALLOC_FREE(data->directory_list); /* destroy previous cache if needed */ + data->directory_list = talloc_zero_array(data, + struct dirent, + dirent_allocated); + if (data->directory_list == NULL) { + return false; + } + + do { + if (total_count >= dirent_allocated) { + struct dirent *dlist; + + /* + * Be memory friendly. + * + * We should not double the amount of memory. With a lot + * of files we reach easily 50MB, and doubling will + * get much bigger just for a few files more. + * + * For 200k files this means 50 memory reallocations. + */ + dirent_allocated += 4096; + + dlist = talloc_realloc(data, + data->directory_list, + struct dirent, + dirent_allocated); + if (dlist == NULL) { + break; + } + data->directory_list = dlist; + } + data->directory_list[total_count] = *dp; + + total_count++; + dp = SMB_VFS_NEXT_READDIR(handle, + data->fsp, + data->source_directory); + } while (dp != NULL); + + data->number_of_entries = total_count; + + /* Sort the directory entries by name */ + TYPESAFE_QSORT(data->directory_list, data->number_of_entries, compare_dirent); + return true; +} + +static DIR *dirsort_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, + const char *mask, + uint32_t attr) +{ + struct dirsort_privates *list_head = NULL; + struct dirsort_privates *data = NULL; + + if (SMB_VFS_HANDLE_TEST_DATA(handle)) { + /* Find the list head of all open directories. */ + SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates, + return NULL); + } + + /* set up our private data about this directory */ + data = talloc_zero(handle->conn, struct dirsort_privates); + if (!data) { + return NULL; + } + + data->fsp = fsp; + + /* Open the underlying directory and count the number of entries */ + data->source_directory = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, + attr); + + if (data->source_directory == NULL) { + TALLOC_FREE(data); + return NULL; + } + + if (!open_and_sort_dir(handle, data)) { + SMB_VFS_NEXT_CLOSEDIR(handle,data->source_directory); + TALLOC_FREE(data); + /* fd is now closed. */ + fsp_set_fd(fsp, -1); + return NULL; + } + + /* Add to the private list of all open directories. */ + DLIST_ADD(list_head, data); + SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL, + struct dirsort_privates, return NULL); + + return data->source_directory; +} + +static struct dirent *dirsort_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + struct dirsort_privates *data = NULL; + struct timespec current_mtime; + + SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates, + return NULL); + + while(data && (data->source_directory != dirp)) { + data = data->next; + } + if (data == NULL) { + return NULL; + } + + if (get_sorted_dir_mtime(handle, data, ¤t_mtime) == false) { + return NULL; + } + + /* throw away cache and re-read the directory if we've changed */ + if (timespec_compare(¤t_mtime, &data->mtime)) { + SMB_VFS_NEXT_REWINDDIR(handle, data->source_directory); + open_and_sort_dir(handle, data); + } + + if (data->pos >= data->number_of_entries) { + return NULL; + } + + return &data->directory_list[data->pos++]; +} + +static void dirsort_rewinddir(vfs_handle_struct *handle, DIR *dirp) +{ + struct dirsort_privates *data = NULL; + SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates, return); + + /* Find the entry holding dirp. */ + while(data && (data->source_directory != dirp)) { + data = data->next; + } + if (data == NULL) { + return; + } + data->pos = 0; +} + +static int dirsort_closedir(vfs_handle_struct *handle, DIR *dirp) +{ + struct dirsort_privates *list_head = NULL; + struct dirsort_privates *data = NULL; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates, return -1); + /* Find the entry holding dirp. */ + for(data = list_head; data && (data->source_directory != dirp); data = data->next) { + ; + } + if (data == NULL) { + return -1; + } + /* Remove from the list and re-store the list head. */ + DLIST_REMOVE(list_head, data); + SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL, + struct dirsort_privates, return -1); + + ret = SMB_VFS_NEXT_CLOSEDIR(handle, dirp); + TALLOC_FREE(data); + return ret; +} + +static struct vfs_fn_pointers vfs_dirsort_fns = { + .fdopendir_fn = dirsort_fdopendir, + .readdir_fn = dirsort_readdir, + .rewind_dir_fn = dirsort_rewinddir, + .closedir_fn = dirsort_closedir, +}; + +static_decl_vfs; +NTSTATUS vfs_dirsort_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "dirsort", + &vfs_dirsort_fns); +} diff --git a/source3/modules/vfs_error_inject.c b/source3/modules/vfs_error_inject.c new file mode 100644 index 0000000..529504f --- /dev/null +++ b/source3/modules/vfs_error_inject.c @@ -0,0 +1,219 @@ +/* + * Unix SMB/CIFS implementation. + * Samba VFS module for error injection in VFS calls + * Copyright (C) Christof Schmitt 2017 + * + * 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 "smbd/smbd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +struct unix_error_map { + const char *err_str; + int error; +} unix_error_map_array[] = { + { "ESTALE", ESTALE }, + { "EBADF", EBADF }, + { "EINTR", EINTR }, + { "EACCES", EACCES }, + { "EROFS", EROFS }, +}; + +static int find_unix_error_from_string(const char *err_str) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(unix_error_map_array); i++) { + struct unix_error_map *m = &unix_error_map_array[i]; + + if (strequal(err_str, m->err_str)) { + return m->error; + } + } + + return 0; +} + +static int inject_unix_error(const char *vfs_func, vfs_handle_struct *handle) +{ + const char *err_str; + int error; + + err_str = lp_parm_const_string(SNUM(handle->conn), + "error_inject", vfs_func, NULL); + if (err_str == NULL) { + return 0; + } + + error = find_unix_error_from_string(err_str); + if (error != 0) { + DBG_WARNING("Returning error %s for VFS function %s\n", + err_str, vfs_func); + return error; + } + + if (strequal(err_str, "panic")) { + DBG_ERR("Panic in VFS function %s\n", vfs_func); + smb_panic("error_inject"); + } + + DBG_ERR("Unknown error inject %s requested " + "for vfs function %s\n", err_str, vfs_func); + + return 0; +} + +static int vfs_error_inject_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int error; + + error = inject_unix_error("chdir", handle); + if (error != 0) { + errno = error; + return -1; + } + + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); +} + +static ssize_t vfs_error_inject_pwrite(vfs_handle_struct *handle, + files_struct *fsp, + const void *data, + size_t n, + off_t offset) +{ + int error; + + error = inject_unix_error("pwrite", handle); + if (error != 0) { + errno = error; + return -1; + } + + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); +} + +static int vfs_error_inject_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int error = inject_unix_error("openat", handle); + int create_error = inject_unix_error("openat_create", handle); + int dirfsp_flags = (O_NOFOLLOW|O_DIRECTORY); + bool return_error; + +#ifdef O_PATH + dirfsp_flags |= O_PATH; +#else +#ifdef O_SEARCH + dirfsp_flags |= O_SEARCH; +#endif +#endif + + if ((create_error != 0) && (how->flags & O_CREAT)) { + struct stat_ex st = { + .st_ex_nlink = 0, + }; + int ret; + + ret = SMB_VFS_FSTATAT(handle->conn, + dirfsp, + smb_fname, + &st, + AT_SYMLINK_NOFOLLOW); + + if ((ret == -1) && (errno == ENOENT)) { + errno = create_error; + return -1; + } + } + + return_error = (error != 0); + return_error &= !fsp->fsp_flags.is_pathref; + return_error &= ((how->flags & dirfsp_flags) != dirfsp_flags); + + if (return_error) { + errno = error; + return -1; + } + return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); +} + +static int vfs_error_inject_unlinkat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *parent_fname = NULL; + int error = inject_unix_error("unlinkat", handle); + int ret; + NTSTATUS status; + + if (error == 0) { + return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags); + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + status = SMB_VFS_PARENT_PATHNAME(handle->conn, + full_fname, /* TALLOC_CTX. */ + full_fname, + &parent_fname, + NULL); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(full_fname); + errno = map_errno_from_nt_status(status); + return -1; + } + + ret = SMB_VFS_STAT(handle->conn, parent_fname); + if (ret != 0) { + TALLOC_FREE(full_fname); + return -1; + } + + if (parent_fname->st.st_ex_uid == get_current_uid(dirfsp->conn)) { + return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags); + } + + errno = error; + return -1; +} + +static struct vfs_fn_pointers vfs_error_inject_fns = { + .chdir_fn = vfs_error_inject_chdir, + .pwrite_fn = vfs_error_inject_pwrite, + .openat_fn = vfs_error_inject_openat, + .unlinkat_fn = vfs_error_inject_unlinkat, +}; + +static_decl_vfs; +NTSTATUS vfs_error_inject_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "error_inject", + &vfs_error_inject_fns); +} diff --git a/source3/modules/vfs_expand_msdfs.c b/source3/modules/vfs_expand_msdfs.c new file mode 100644 index 0000000..503ee84 --- /dev/null +++ b/source3/modules/vfs_expand_msdfs.c @@ -0,0 +1,270 @@ +/* + * Expand msdfs targets based on client IP + * + * Copyright (C) Volker Lendecke, 2004 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "smbd/globals.h" +#include "auth.h" +#include "../lib/tsocket/tsocket.h" +#include "msdfs.h" +#include "source3/lib/substitute.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/********************************************************** + Under mapfile we expect a table of the following format: + + IP-Prefix whitespace expansion + + For example: + 192.168.234 local.samba.org + 192.168 remote.samba.org + default.samba.org + + This is to redirect a DFS client to a host close to it. +***********************************************************/ + +static char *read_target_host(TALLOC_CTX *ctx, const char *mapfile, + const char *clientaddr) +{ + FILE *f; + char buf[1024]; + char *space = buf; + bool found = false; + + f = fopen(mapfile, "r"); + + if (f == NULL) { + DEBUG(0,("can't open IP map %s. Error %s\n", + mapfile, strerror(errno) )); + return NULL; + } + + DEBUG(10, ("Scanning mapfile [%s]\n", mapfile)); + + while (fgets(buf, sizeof(buf), f) != NULL) { + + if ((strlen(buf) > 0) && (buf[strlen(buf)-1] == '\n')) + buf[strlen(buf)-1] = '\0'; + + DEBUG(10, ("Scanning line [%s]\n", buf)); + + space = strchr_m(buf, ' '); + + if (space == NULL) { + DEBUG(0, ("Ignoring invalid line %s\n", buf)); + continue; + } + + *space = '\0'; + + if (strncmp(clientaddr, buf, strlen(buf)) == 0) { + found = true; + break; + } + } + + fclose(f); + + if (!found) { + return NULL; + } + + space += 1; + + while (isspace(*space)) + space += 1; + + return talloc_strdup(ctx, space); +} + +/********************************************************** + + Expand the msdfs target host using read_target_host + explained above. The syntax used in the msdfs link is + + msdfs:@table-filename@/share + + Everything between and including the two @-signs is + replaced by the substitution string found in the table + described above. + +***********************************************************/ + +static char *expand_msdfs_target(TALLOC_CTX *ctx, + connection_struct *conn, + char *target) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *mapfilename = NULL; + char *filename_start = strchr_m(target, '@'); + char *filename_end = NULL; + int filename_len = 0; + char *targethost = NULL; + char *new_target = NULL; + char *raddr; + + if (filename_start == NULL) { + DEBUG(10, ("No filename start in %s\n", target)); + return NULL; + } + + filename_end = strchr_m(filename_start+1, '@'); + + if (filename_end == NULL) { + DEBUG(10, ("No filename end in %s\n", target)); + return NULL; + } + + filename_len = PTR_DIFF(filename_end, filename_start+1); + mapfilename = talloc_strdup(ctx, filename_start+1); + if (!mapfilename) { + return NULL; + } + mapfilename[filename_len] = '\0'; + + /* + * dfs links returned have had '/' characters replaced with '\'. + * Return them to '/' so we can have absolute path mapfilenames. + */ + string_replace(mapfilename, '\\', '/'); + + DEBUG(10, ("Expanding from table [%s]\n", mapfilename)); + + raddr = tsocket_address_inet_addr_string(conn->sconn->remote_address, + ctx); + if (raddr == NULL) { + return NULL; + } + + targethost = read_target_host(ctx, mapfilename, raddr); + if (targethost == NULL) { + DEBUG(1, ("Could not expand target host from file %s\n", + mapfilename)); + return NULL; + } + + targethost = talloc_sub_full(ctx, + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name, + conn->connectpath, + conn->session_info->unix_token->gid, + conn->session_info->unix_info->sanitized_username, + conn->session_info->info->domain_name, + targethost); + + DEBUG(10, ("Expanded targethost to %s\n", targethost)); + + /* Replace the part between '@...@' */ + *filename_start = '\0'; + new_target = talloc_asprintf(ctx, + "%s%s%s", + target, + targethost, + filename_end+1); + if (!new_target) { + return NULL; + } + + DEBUG(10, ("New DFS target: %s\n", new_target)); + return new_target; +} + +static NTSTATUS expand_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + NTSTATUS status; + size_t i; + struct referral *reflist = NULL; + size_t count = 0; + TALLOC_CTX *frame = talloc_stackframe(); + + /* + * Always call the NEXT function first, then + * modify the return if needed. + */ + status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + dirfsp, + smb_fname, + ppreflist, + preferral_count); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + /* + * This function can be called to check if a pathname + * is an MSDFS link, but not return the values of it. + * In this case ppreflist and preferral_count are NULL, + * so don't bother trying to look at any returns. + */ + if (ppreflist == NULL || preferral_count == NULL) { + TALLOC_FREE(frame); + return status; + } + + /* + * We are always returning the values returned + * returned by the NEXT call, but we might mess + * with the reflist[i].alternate_path values, + * so use local pointers to minimise indirections. + */ + count = *preferral_count; + reflist = *ppreflist; + + for (i = 0; i < count; i++) { + if (strchr_m(reflist[i].alternate_path, '@') != NULL) { + char *new_altpath = expand_msdfs_target(frame, + handle->conn, + reflist[i].alternate_path); + if (new_altpath == NULL) { + TALLOC_FREE(*ppreflist); + *preferral_count = 0; + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + reflist[i].alternate_path = talloc_move(reflist, + &new_altpath); + } + } + TALLOC_FREE(frame); + return status; +} + +static struct vfs_fn_pointers vfs_expand_msdfs_fns = { + .read_dfs_pathat_fn = expand_read_dfs_pathat, +}; + +static_decl_vfs; +NTSTATUS vfs_expand_msdfs_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "expand_msdfs", + &vfs_expand_msdfs_fns); +} diff --git a/source3/modules/vfs_extd_audit.c b/source3/modules/vfs_extd_audit.c new file mode 100644 index 0000000..ea784ff --- /dev/null +++ b/source3/modules/vfs_extd_audit.c @@ -0,0 +1,418 @@ +/* + * Auditing VFS module for samba. Log selected file operations to syslog + * facility. + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) John H Terpstra, 2003 + * Copyright (C) Stefan (metze) Metzmacher, 2003 + * + * 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/filesys.h" +#include "system/syslog.h" +#include "smbd/smbd.h" +#include "lib/param/loadparm.h" + +static int vfs_extd_audit_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_extd_audit_debug_level + +static int audit_syslog_facility(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_facilities[] = { +#ifdef LOG_AUTH + { LOG_AUTH, "AUTH" }, +#endif +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "AUTHPRIV" }, +#endif +#ifdef LOG_AUDIT + { LOG_AUDIT, "AUDIT" }, +#endif +#ifdef LOG_CONSOLE + { LOG_CONSOLE, "CONSOLE" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "CRON" }, +#endif +#ifdef LOG_DAEMON + { LOG_DAEMON, "DAEMON" }, +#endif +#ifdef LOG_FTP + { LOG_FTP, "FTP" }, +#endif +#ifdef LOG_INSTALL + { LOG_INSTALL, "INSTALL" }, +#endif +#ifdef LOG_KERN + { LOG_KERN, "KERN" }, +#endif +#ifdef LOG_LAUNCHD + { LOG_LAUNCHD, "LAUNCHD" }, +#endif +#ifdef LOG_LFMT + { LOG_LFMT, "LFMT" }, +#endif +#ifdef LOG_LPR + { LOG_LPR, "LPR" }, +#endif +#ifdef LOG_MAIL + { LOG_MAIL, "MAIL" }, +#endif +#ifdef LOG_MEGASAFE + { LOG_MEGASAFE, "MEGASAFE" }, +#endif +#ifdef LOG_NETINFO + { LOG_NETINFO, "NETINFO" }, +#endif +#ifdef LOG_NEWS + { LOG_NEWS, "NEWS" }, +#endif +#ifdef LOG_NFACILITIES + { LOG_NFACILITIES, "NFACILITIES" }, +#endif +#ifdef LOG_NTP + { LOG_NTP, "NTP" }, +#endif +#ifdef LOG_RAS + { LOG_RAS, "RAS" }, +#endif +#ifdef LOG_REMOTEAUTH + { LOG_REMOTEAUTH, "REMOTEAUTH" }, +#endif +#ifdef LOG_SECURITY + { LOG_SECURITY, "SECURITY" }, +#endif +#ifdef LOG_SYSLOG + { LOG_SYSLOG, "SYSLOG" }, +#endif +#ifdef LOG_USER + { LOG_USER, "USER" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "UUCP" }, +#endif + { LOG_LOCAL0, "LOCAL0" }, + { LOG_LOCAL1, "LOCAL1" }, + { LOG_LOCAL2, "LOCAL2" }, + { LOG_LOCAL3, "LOCAL3" }, + { LOG_LOCAL4, "LOCAL4" }, + { LOG_LOCAL5, "LOCAL5" }, + { LOG_LOCAL6, "LOCAL6" }, + { LOG_LOCAL7, "LOCAL7" }, + { -1, NULL } + }; + + int facility; + + facility = lp_parm_enum(SNUM(handle->conn), "extd_audit", "facility", enum_log_facilities, LOG_USER); + + return facility; +} + + +static int audit_syslog_priority(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_priorities[] = { + { LOG_EMERG, "EMERG" }, + { LOG_ALERT, "ALERT" }, + { LOG_CRIT, "CRIT" }, + { LOG_ERR, "ERR" }, + { LOG_WARNING, "WARNING" }, + { LOG_NOTICE, "NOTICE" }, + { LOG_INFO, "INFO" }, + { LOG_DEBUG, "DEBUG" }, + { -1, NULL } + }; + + int priority; + + priority = lp_parm_enum(SNUM(handle->conn), "extd_audit", "priority", + enum_log_priorities, LOG_NOTICE); + if (priority == -1) { + priority = LOG_WARNING; + } + + return priority; +} + +/* Implementation of vfs_ops. Pass everything on to the default + operation but log event first. */ + +static int audit_connect(vfs_handle_struct *handle, const char *svc, const char *user) +{ + int result = SMB_VFS_NEXT_CONNECT(handle, svc, user); + + if (result < 0) { + return result; + } + + openlog("smbd_audit", LOG_PID, audit_syslog_facility(handle)); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), + "connect to service %s by user %s\n", + svc, user); + } + DEBUG(10, ("Connected to service %s as user %s\n", + svc, user)); + + return 0; +} + +static void audit_disconnect(vfs_handle_struct *handle) +{ + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "disconnected\n"); + } + DEBUG(10, ("Disconnected from VFS module extd_audit\n")); + SMB_VFS_NEXT_DISCONNECT(handle); + + return; +} + +static int audit_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "mkdirat %s %s%s\n", + full_fname->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + } + DEBUG(0, ("vfs_extd_audit: mkdirat %s %s %s\n", + full_fname->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : "")); + + TALLOC_FREE(full_fname); + return result; +} + +static int audit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int ret; + + ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), + "openat %s/%s (fd %d) %s%s%s\n", + smb_fname_str_dbg(fsp->fsp_name), + smb_fname->base_name, + ret, + ((how->flags & O_WRONLY) || (how->flags & O_RDWR)) ? + "for writing " : "", + (ret < 0) ? "failed: " : "", + (ret < 0) ? strerror(errno) : ""); + } + DEBUG(2, ("vfs_extd_audit: open %s/%s %s %s\n", + smb_fname_str_dbg(fsp->fsp_name), + smb_fname_str_dbg(smb_fname), + (ret < 0) ? "failed: " : "", + (ret < 0) ? strerror(errno) : "")); + + return ret; +} + +static int audit_close(vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + + result = SMB_VFS_NEXT_CLOSE(handle, fsp); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "close fd %d %s%s\n", + fsp_get_pathref_fd(fsp), + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + } + DEBUG(2, ("vfs_extd_audit: close fd %d %s %s\n", + fsp_get_pathref_fd(fsp), + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : "")); + + return result; +} + +static int audit_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + int result; + int saved_errno = 0; + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + return -1; + } + + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (result == -1) { + saved_errno = errno; + } + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "renameat %s -> %s %s%s\n", + full_fname_src->base_name, + full_fname_dst->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(saved_errno) : ""); + } + DEBUG(1, ("vfs_extd_audit: renameat old: %s newname: %s %s %s\n", + smb_fname_str_dbg(full_fname_src), + smb_fname_str_dbg(full_fname_dst), + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(saved_errno) : "")); + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + + if (result == -1) { + errno = saved_errno; + } + return result; +} + +static int audit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "unlinkat %s %s%s\n", + full_fname->base_name, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + } + DBG_ERR("unlinkat %s %s %s\n", + smb_fname_str_dbg(full_fname), + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + + TALLOC_FREE(full_fname); + return result; +} + +static int audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) +{ + int result; + + result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + + if (lp_syslog() > 0) { + syslog(audit_syslog_priority(handle), "fchmod %s mode 0x%x %s%s\n", + fsp->fsp_name->base_name, mode, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : ""); + } + DEBUG(1, ("vfs_extd_audit: fchmod %s mode 0x%x %s %s\n", + fsp_str_dbg(fsp), (unsigned int)mode, + (result < 0) ? "failed: " : "", + (result < 0) ? strerror(errno) : "")); + + return result; +} + +static struct vfs_fn_pointers vfs_extd_audit_fns = { + .connect_fn = audit_connect, + .disconnect_fn = audit_disconnect, + .mkdirat_fn = audit_mkdirat, + .openat_fn = audit_openat, + .close_fn = audit_close, + .renameat_fn = audit_renameat, + .unlinkat_fn = audit_unlinkat, + .fchmod_fn = audit_fchmod, +}; + +static_decl_vfs; +NTSTATUS vfs_extd_audit_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "extd_audit", &vfs_extd_audit_fns); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_extd_audit_debug_level = debug_add_class("extd_audit"); + if (vfs_extd_audit_debug_level == -1) { + vfs_extd_audit_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_extd_audit: Couldn't register custom debugging class!\n")); + } else { + DEBUG(10, ("vfs_extd_audit: Debug class number of 'extd_audit': %d\n", vfs_extd_audit_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_fake_acls.c b/source3/modules/vfs_fake_acls.c new file mode 100644 index 0000000..fefe6c5 --- /dev/null +++ b/source3/modules/vfs_fake_acls.c @@ -0,0 +1,704 @@ +/* + * Fake ACLs VFS module. Implements passthrough operation of all VFS + * calls to disk functions, except for file ownership and ACLs, which + * are stored in xattrs. + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Andrew Bartlett, 2002,2012 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "auth.h" +#include "librpc/gen_ndr/ndr_smb_acl.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define FAKE_UID "system.fake_uid" +#define FAKE_GID "system.fake_gid" +#define FAKE_ACL_ACCESS_XATTR "system.fake_access_acl" +#define FAKE_ACL_DEFAULT_XATTR "system.fake_default_acl" + +struct in_pathref_data { + bool calling_pathref_fsp; +}; + +static int fake_acls_fuid(vfs_handle_struct *handle, + files_struct *fsp, + uid_t *uid) +{ + ssize_t size; + uint8_t uid_buf[4]; + + size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_UID, uid_buf, sizeof(uid_buf)); + if (size == -1 && errno == ENOATTR) { + return 0; + } + if (size != 4) { + return -1; + } + *uid = IVAL(uid_buf, 0); + return 0; +} + +static int fake_acls_fgid(vfs_handle_struct *handle, + files_struct *fsp, + uid_t *gid) +{ + ssize_t size; + uint8_t gid_buf[4]; + + size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_GID, gid_buf, sizeof(gid_buf)); + if (size == -1 && errno == ENOATTR) { + return 0; + } + if (size != 4) { + return -1; + } + *gid = IVAL(gid_buf, 0); + return 0; +} + +static int fake_acls_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret = -1; + struct in_pathref_data *prd = NULL; + struct smb_filename *smb_fname_cp = NULL; + struct files_struct *fsp = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + prd, + struct in_pathref_data, + return -1); + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret != 0) { + return ret; + } + + if (smb_fname->fsp != NULL) { + fsp = metadata_fsp(smb_fname->fsp); + } else { + NTSTATUS status; + + /* + * Ensure openat_pathref_fsp() + * can't recurse into fake_acls_stat(). + * openat_pathref_fsp() doesn't care + * about the uid/gid values, it only + * wants a valid/invalid stat answer + * and we know smb_fname exists as + * the SMB_VFS_NEXT_STAT() returned + * zero above. + */ + if (prd->calling_pathref_fsp) { + return 0; + } + + /* + * openat_pathref_fsp() expects a talloc'ed + * smb_filename. stat can be passed a struct + * from the stack. Make a talloc'ed copy + * so openat_pathref_fsp() can add its + * destructor. + */ + smb_fname_cp = cp_smb_filename(talloc_tos(), + smb_fname); + if (smb_fname_cp == NULL) { + errno = ENOMEM; + return -1; + } + + /* Recursion guard. */ + prd->calling_pathref_fsp = true; + status = openat_pathref_fsp(handle->conn->cwd_fsp, + smb_fname_cp); + /* End recursion guard. */ + prd->calling_pathref_fsp = false; + + if (!NT_STATUS_IS_OK(status)) { + /* + * Ignore errors here. We know + * the path exists (the SMB_VFS_NEXT_STAT() + * above succeeded. So being unable to + * open a pathref fsp can be due to a + * range of errors (startup path beginning + * with '/' for example, path = ".." when + * enumerating a directory. Just treat this + * the same way as the path not having the + * FAKE_UID or FAKE_GID EA's present. For the + * test purposes of this module (fake NT ACLs + * from windows clients) this is close enough. + * Just report for debugging purposes. + */ + DBG_DEBUG("Unable to get pathref fsp on %s. " + "Error %s\n", + smb_fname_str_dbg(smb_fname_cp), + nt_errstr(status)); + TALLOC_FREE(smb_fname_cp); + return 0; + } + fsp = smb_fname_cp->fsp; + } + + ret = fake_acls_fuid(handle, + fsp, + &smb_fname->st.st_ex_uid); + if (ret != 0) { + TALLOC_FREE(smb_fname_cp); + return ret; + } + ret = fake_acls_fgid(handle, + fsp, + &smb_fname->st.st_ex_gid); + if (ret != 0) { + TALLOC_FREE(smb_fname_cp); + return ret; + } + TALLOC_FREE(smb_fname_cp); + return ret; +} + +static int fake_acls_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret = -1; + struct in_pathref_data *prd = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + prd, + struct in_pathref_data, + return -1); + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret == 0) { + struct smb_filename *smb_fname_base = NULL; + SMB_STRUCT_STAT sbuf = { 0 }; + NTSTATUS status; + + /* + * Ensure synthetic_pathref() + * can't recurse into fake_acls_lstat(). + * synthetic_pathref() doesn't care + * about the uid/gid values, it only + * wants a valid/invalid stat answer + * and we know smb_fname exists as + * the SMB_VFS_NEXT_LSTAT() returned + * zero above. + */ + if (prd->calling_pathref_fsp) { + return 0; + } + + /* Recursion guard. */ + prd->calling_pathref_fsp = true; + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + NULL, + &sbuf, + smb_fname->twrp, + 0, /* we want stat, not lstat. */ + &smb_fname_base); + /* End recursion guard. */ + prd->calling_pathref_fsp = false; + if (NT_STATUS_IS_OK(status)) { + /* + * This isn't quite right (calling fgetxattr not + * lgetxattr), but for the test purposes of this + * module (fake NT ACLs from windows clients), it is + * close enough. We removed the l*xattr functions + * because linux doesn't support using them, but we + * could fake them in xattr_tdb if we really wanted + * to. We ignore errors because the link might not + * point anywhere */ + fake_acls_fuid(handle, + smb_fname_base->fsp, + &smb_fname->st.st_ex_uid); + fake_acls_fgid(handle, + smb_fname_base->fsp, + &smb_fname->st.st_ex_gid); + } + TALLOC_FREE(smb_fname_base); + } + + return ret; +} + +static int fake_acls_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int ret = -1; + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == 0) { + ret = fake_acls_fuid(handle, fsp, &sbuf->st_ex_uid); + if (ret != 0) { + return ret; + } + ret = fake_acls_fgid(handle, fsp, &sbuf->st_ex_gid); + if (ret != 0) { + return ret; + } + } + return ret; +} + +static SMB_ACL_T fake_acls_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx) +{ + enum ndr_err_code ndr_err; + struct smb_acl_t *acl = talloc(mem_ctx, struct smb_acl_t); + if (!acl) { + errno = ENOMEM; + return NULL; + } + + ndr_err = ndr_pull_struct_blob(blob, acl, acl, + (ndr_pull_flags_fn_t)ndr_pull_smb_acl_t); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0, ("ndr_pull_acl_t failed: %s\n", + ndr_errstr(ndr_err))); + TALLOC_FREE(acl); + return NULL; + } + return acl; +} + +static DATA_BLOB fake_acls_acl2blob(TALLOC_CTX *mem_ctx, SMB_ACL_T acl) +{ + enum ndr_err_code ndr_err; + DATA_BLOB blob; + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl, + (ndr_push_flags_fn_t)ndr_push_smb_acl_t); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0, ("ndr_push_acl_t failed: %s\n", + ndr_errstr(ndr_err))); + return data_blob_null; + } + return blob; +} + +static SMB_ACL_T fake_acls_sys_acl_get_fd(struct vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + DATA_BLOB blob = data_blob_null; + ssize_t length; + const char *name = NULL; + struct smb_acl_t *acl = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + switch (type) { + case SMB_ACL_TYPE_ACCESS: + name = FAKE_ACL_ACCESS_XATTR; + break; + case SMB_ACL_TYPE_DEFAULT: + name = FAKE_ACL_DEFAULT_XATTR; + break; + default: + DBG_ERR("Illegal ACL type %d\n", (int)type); + break; + } + + if (name == NULL) { + TALLOC_FREE(frame); + return NULL; + } + + do { + blob.length += 1000; + blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length); + if (!blob.data) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + length = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, blob.data, blob.length); + blob.length = length; + } while (length == -1 && errno == ERANGE); + if (length == -1 && errno == ENOATTR) { + TALLOC_FREE(frame); + return NULL; + } + if (length != -1) { + acl = fake_acls_blob2acl(&blob, mem_ctx); + } + TALLOC_FREE(frame); + return acl; +} + +static int fake_acls_sys_acl_set_fd(vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + int ret; + const char *name = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + DATA_BLOB blob = fake_acls_acl2blob(frame, theacl); + if (!blob.data) { + DEBUG(0, ("Failed to convert ACL to linear blob for xattr storage\n")); + TALLOC_FREE(frame); + errno = EINVAL; + return -1; + } + + switch (type) { + case SMB_ACL_TYPE_ACCESS: + name = FAKE_ACL_ACCESS_XATTR; + break; + case SMB_ACL_TYPE_DEFAULT: + name = FAKE_ACL_DEFAULT_XATTR; + break; + default: + errno = EINVAL; + return -1; + } + + ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, blob.data, blob.length, 0); + TALLOC_FREE(frame); + return ret; +} + +static int fake_acls_sys_acl_delete_def_fd(vfs_handle_struct *handle, + struct files_struct *fsp) +{ + int ret; + const char *name = FAKE_ACL_DEFAULT_XATTR; + + if (!fsp->fsp_flags.is_directory) { + errno = EINVAL; + return -1; + } + + ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); + if (ret == -1 && errno == ENOATTR) { + ret = 0; + errno = 0; + } + + return ret; +} + +static int fake_acls_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int ret; + uint8_t id_buf[4]; + if (uid != -1) { + uid_t current_uid = get_current_uid(handle->conn); + + if (current_uid != 0 && current_uid != uid) { + return EACCES; + } + + /* This isn't quite right (calling setxattr not + * lsetxattr), but for the test purposes of this + * module (fake NT ACLs from windows clients), it is + * close enough. We removed the l*xattr functions + * because linux doesn't support using them, but we + * could fake them in xattr_tdb if we really wanted + * to. + */ + SIVAL(id_buf, 0, uid); + ret = SMB_VFS_NEXT_FSETXATTR(handle, + smb_fname->fsp, + FAKE_UID, + id_buf, + sizeof(id_buf), + 0); + if (ret != 0) { + return ret; + } + } + if (gid != -1) { + SIVAL(id_buf, 0, gid); + ret = SMB_VFS_NEXT_FSETXATTR(handle, + smb_fname->fsp, + FAKE_GID, + id_buf, + sizeof(id_buf), + 0); + if (ret != 0) { + return ret; + } + } + return 0; +} + +static int fake_acls_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid) +{ + int ret; + uint8_t id_buf[4]; + if (uid != -1) { + uid_t current_uid = get_current_uid(handle->conn); + + if (current_uid != 0 && current_uid != uid) { + return EACCES; + } + + SIVAL(id_buf, 0, uid); + ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_UID, id_buf, sizeof(id_buf), 0); + if (ret != 0) { + return ret; + } + } + if (gid != -1) { + SIVAL(id_buf, 0, gid); + ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_GID, id_buf, sizeof(id_buf), 0); + if (ret != 0) { + return ret; + } + } + return 0; +} + +/* + * Implement the chmod uid/mask/other mode changes on a fake ACL. + */ + +static int fake_acl_process_chmod(SMB_ACL_T *pp_the_acl, + uid_t owner, + mode_t mode) +{ + bool got_mask = false; + int entry_id = SMB_ACL_FIRST_ENTRY; + mode_t umode = 0; + mode_t mmode = 0; + mode_t omode = 0; + int ret = -1; + SMB_ACL_T the_acl = *pp_the_acl; + + /* Split the mode into u/mask/other masks. */ + umode = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR); + mmode = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP); + omode = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH); + + while (1) { + SMB_ACL_ENTRY_T entry; + SMB_ACL_TAG_T tagtype; + SMB_ACL_PERMSET_T permset; + uid_t *puid = NULL; + + ret = sys_acl_get_entry(the_acl, + entry_id, + &entry); + if (ret == 0) { + /* End of ACL */ + break; + } + if (ret == -1) { + return -1; + } + + ret = sys_acl_get_tag_type(entry, &tagtype); + if (ret == -1) { + return -1; + } + ret = sys_acl_get_permset(entry, &permset); + if (ret == -1) { + return -1; + } + switch (tagtype) { + case SMB_ACL_USER_OBJ: + ret = map_acl_perms_to_permset(umode, &permset); + if (ret == -1) { + return -1; + } + break; + case SMB_ACL_USER: + puid = (uid_t *)sys_acl_get_qualifier(entry); + if (puid == NULL) { + return -1; + } + if (owner != *puid) { + break; + } + ret = map_acl_perms_to_permset(umode, &permset); + if (ret == -1) { + return -1; + } + break; + case SMB_ACL_GROUP_OBJ: + case SMB_ACL_GROUP: + /* Ignore all group entries. */ + break; + case SMB_ACL_MASK: + ret = map_acl_perms_to_permset(mmode, &permset); + if (ret == -1) { + return -1; + } + got_mask = true; + break; + case SMB_ACL_OTHER: + ret = map_acl_perms_to_permset(omode, &permset); + if (ret == -1) { + return -1; + } + break; + default: + errno = EINVAL; + return -1; + } + ret = sys_acl_set_permset(entry, permset); + if (ret == -1) { + return -1; + } + /* Move to next entry. */ + entry_id = SMB_ACL_NEXT_ENTRY; + } + + /* + * If we didn't see a mask entry, add one. + */ + + if (!got_mask) { + SMB_ACL_ENTRY_T mask_entry; + uint32_t mask_perm = 0; + SMB_ACL_PERMSET_T mask_permset = &mask_perm; + ret = sys_acl_create_entry(&the_acl, &mask_entry); + if (ret == -1) { + return -1; + } + ret = map_acl_perms_to_permset(mmode, &mask_permset); + if (ret == -1) { + return -1; + } + ret = sys_acl_set_permset(mask_entry, mask_permset); + if (ret == -1) { + return -1; + } + ret = sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK); + if (ret == -1) { + return -1; + } + /* In case we were realloced and moved. */ + *pp_the_acl = the_acl; + } + + return 0; +} + +static int fake_acls_fchmod(vfs_handle_struct *handle, + files_struct *fsp, + mode_t mode) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret = -1; + SMB_ACL_T the_acl = NULL; + + /* + * Passthrough first to preserve the + * S_ISUID | S_ISGID | S_ISVTX + * bits. + */ + + ret = SMB_VFS_NEXT_FCHMOD(handle, + fsp, + mode); + if (ret == -1) { + TALLOC_FREE(frame); + return -1; + } + + the_acl = fake_acls_sys_acl_get_fd(handle, + fsp, + SMB_ACL_TYPE_ACCESS, + talloc_tos()); + if (the_acl == NULL) { + TALLOC_FREE(frame); + if (errno == ENOATTR) { + /* No ACL on this file. Just passthrough. */ + return 0; + } + return -1; + } + ret = fake_acl_process_chmod(&the_acl, + fsp->fsp_name->st.st_ex_uid, + mode); + if (ret == -1) { + TALLOC_FREE(frame); + return -1; + } + ret = fake_acls_sys_acl_set_fd(handle, + fsp, + SMB_ACL_TYPE_ACCESS, + the_acl); + TALLOC_FREE(frame); + return ret; +} + +static int fake_acls_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + struct in_pathref_data *prd = NULL; + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + /* + * Create a struct can tell us if we're recursing + * into openat_pathref_fsp() in this module. This will + * go away once we have SMB_VFS_STATX() and we will + * have a way for a caller to as for specific stat + * fields in a granular way. Then we will know exactly + * what fields the caller wants, so we won't have to + * fill in everything. + */ + prd = talloc_zero(handle->conn, struct in_pathref_data); + if (prd == NULL) { + return -1; + } + SMB_VFS_HANDLE_SET_DATA(handle, + prd, + NULL, + struct in_pathref_data, + return -1); + return 0; +} + +static struct vfs_fn_pointers vfs_fake_acls_fns = { + .connect_fn = fake_acls_connect, + .stat_fn = fake_acls_stat, + .lstat_fn = fake_acls_lstat, + .fstat_fn = fake_acls_fstat, + .fchmod_fn = fake_acls_fchmod, + .sys_acl_get_fd_fn = fake_acls_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = fake_acls_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = fake_acls_sys_acl_delete_def_fd, + .lchown_fn = fake_acls_lchown, + .fchown_fn = fake_acls_fchown, + +}; + +static_decl_vfs; +NTSTATUS vfs_fake_acls_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_acls", + &vfs_fake_acls_fns); +} diff --git a/source3/modules/vfs_fake_dfq.c b/source3/modules/vfs_fake_dfq.c new file mode 100644 index 0000000..47c0b07 --- /dev/null +++ b/source3/modules/vfs_fake_dfq.c @@ -0,0 +1,281 @@ +/* + * Fake Disk-Free and Quota VFS module. Implements passthrough operation + * of all VFS calls, except for "disk free" and "get quota" which + * are handled by reading a text file named ".dfq" in the current directory. + * + * This module is intended for testing purposes. + * + * Copyright (C) Uri Simchoni, 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 . + */ + +#include "includes.h" +#include "smbd/smbd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static int dfq_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt); + +static uint64_t dfq_load_param(int snum, const char *path, const char *section, + const char *param, uint64_t def_val) +{ + uint64_t ret; + + char *option = + talloc_asprintf(talloc_tos(), "%s/%s/%s", section, param, path); + if (option == NULL) { + return def_val; + } + + ret = (uint64_t)lp_parm_ulonglong(snum, "fake_dfq", option, + (unsigned long long)def_val); + + TALLOC_FREE(option); + + return ret; +} + +static uint64_t dfq_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + uint64_t free_1k; + int snum = SNUM(handle->conn); + uint64_t dfq_bsize = 0; + struct smb_filename *rpath_fname = NULL; + + /* look up the params based on real path to be resilient + * to refactoring of path<->realpath + */ + rpath_fname = SMB_VFS_NEXT_REALPATH(handle, talloc_tos(), smb_fname); + if (rpath_fname != NULL) { + dfq_bsize = dfq_load_param(snum, rpath_fname->base_name, + "df", "block size", 0); + } + if (dfq_bsize == 0) { + TALLOC_FREE(rpath_fname); + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree, + dsize); + } + + *bsize = dfq_bsize; + *dfree = dfq_load_param(snum, rpath_fname->base_name, + "df", "disk free", 0); + *dsize = dfq_load_param(snum, rpath_fname->base_name, + "df", "disk size", 0); + + if ((*bsize) < 1024) { + free_1k = (*dfree) / (1024 / (*bsize)); + } else { + free_1k = ((*bsize) / 1024) * (*dfree); + } + + TALLOC_FREE(rpath_fname); + return free_1k; +} + +static int dfq_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ + int rc = 0; + int save_errno; + char *section = NULL; + int snum = SNUM(handle->conn); + uint64_t bsize = 0; + struct smb_filename *rpath_fname = NULL; + + rpath_fname = SMB_VFS_NEXT_REALPATH(handle, talloc_tos(), smb_fname); + if (rpath_fname == NULL) { + goto dflt; + } + + switch (qtype) { + case SMB_USER_QUOTA_TYPE: + section = talloc_asprintf(talloc_tos(), "u%llu", + (unsigned long long)id.uid); + break; + case SMB_GROUP_QUOTA_TYPE: + section = talloc_asprintf(talloc_tos(), "g%llu", + (unsigned long long)id.gid); + break; + case SMB_USER_FS_QUOTA_TYPE: + section = talloc_strdup(talloc_tos(), "udflt"); + break; + case SMB_GROUP_FS_QUOTA_TYPE: + section = talloc_strdup(talloc_tos(), "gdflt"); + break; + default: + break; + } + + if (section == NULL) { + goto dflt; + } + + bsize = dfq_load_param(snum, rpath_fname->base_name, + section, "block size", 4096); + if (bsize == 0) { + goto dflt; + } + + if (dfq_load_param(snum, rpath_fname->base_name, + section, "err", 0) != 0) { + errno = ENOTSUP; + rc = -1; + goto out; + } + + if (dfq_load_param(snum, rpath_fname->base_name, + section, "nosys", 0) != 0) { + errno = ENOSYS; + rc = -1; + goto out; + } + + ZERO_STRUCTP(qt); + + qt->bsize = bsize; + qt->hardlimit = dfq_load_param(snum, rpath_fname->base_name, + section, "hard limit", 0); + qt->softlimit = dfq_load_param(snum, rpath_fname->base_name, + section, "soft limit", 0); + qt->curblocks = dfq_load_param(snum, rpath_fname->base_name, + section, "cur blocks", 0); + qt->ihardlimit = + dfq_load_param(snum, rpath_fname->base_name, + section, "inode hard limit", 0); + qt->isoftlimit = + dfq_load_param(snum, rpath_fname->base_name, + section, "inode soft limit", 0); + qt->curinodes = dfq_load_param(snum, rpath_fname->base_name, + section, "cur inodes", 0); + qt->qflags = dfq_load_param(snum, rpath_fname->base_name, + section, "qflags", QUOTAS_DENY_DISK); + + goto out; + +dflt: + rc = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt); + +out: + save_errno = errno; + TALLOC_FREE(section); + TALLOC_FREE(rpath_fname); + errno = save_errno; + return rc; +} + +static void dfq_fake_stat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf) +{ + int snum = SNUM(handle->conn); + char *full_path = NULL; + char *to_free = NULL; + char path[PATH_MAX + 1]; + int len; + gid_t gid; + + len = full_path_tos(handle->conn->cwd_fsp->fsp_name->base_name, + smb_fname->base_name, + path, sizeof(path), + &full_path, &to_free); + if (len == -1) { + DBG_ERR("Could not allocate memory in full_path_tos.\n"); + return; + } + + gid = dfq_load_param(snum, full_path, "stat", "sgid", 0); + if (gid != 0) { + sbuf->st_ex_gid = gid; + sbuf->st_ex_mode |= S_ISGID; + } + + TALLOC_FREE(to_free); +} + +static int dfq_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret == -1) { + return ret; + } + + dfq_fake_stat(handle, smb_fname, &smb_fname->st); + + return 0; +} + +static int dfq_fstat(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int ret; + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == -1) { + return ret; + } + + dfq_fake_stat(handle, fsp->fsp_name, sbuf); + + return 0; +} + +static int dfq_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret == -1) { + return ret; + } + + dfq_fake_stat(handle, smb_fname, &smb_fname->st); + + return 0; +} + +struct vfs_fn_pointers vfs_fake_dfq_fns = { + /* Disk operations */ + + .disk_free_fn = dfq_disk_free, + .get_quota_fn = dfq_get_quota, + + .stat_fn = dfq_stat, + .fstat_fn = dfq_fstat, + .lstat_fn = dfq_lstat, +}; + +static_decl_vfs; +NTSTATUS vfs_fake_dfq_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_dfq", + &vfs_fake_dfq_fns); +} diff --git a/source3/modules/vfs_fake_perms.c b/source3/modules/vfs_fake_perms.c new file mode 100644 index 0000000..0089186 --- /dev/null +++ b/source3/modules/vfs_fake_perms.c @@ -0,0 +1,108 @@ +/* + * Fake Perms VFS module. Implements passthrough operation of all VFS + * calls to disk functions, except for file permissions, which are now + * mode 0700 for the current uid/gid. + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Andrew Bartlett, 2002 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static int fake_perms_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int ret; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret != 0) { + return ret; + } + + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + smb_fname->st.st_ex_mode = S_IFDIR | S_IRWXU; + } else { + smb_fname->st.st_ex_mode = S_IRWXU; + } + + if (handle->conn->session_info != NULL) { + struct security_unix_token *utok; + + utok = handle->conn->session_info->unix_token; + smb_fname->st.st_ex_uid = utok->uid; + smb_fname->st.st_ex_gid = utok->gid; + } else { + /* + * We have an artificial connection for dfs for example. It + * sucks, but the current uid/gid is the best we have. + */ + smb_fname->st.st_ex_uid = geteuid(); + smb_fname->st.st_ex_gid = getegid(); + } + + return ret; +} + +static int fake_perms_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int ret; + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret != 0) { + return ret; + } + + if (S_ISDIR(sbuf->st_ex_mode)) { + sbuf->st_ex_mode = S_IFDIR | S_IRWXU; + } else { + sbuf->st_ex_mode = S_IRWXU; + } + if (handle->conn->session_info != NULL) { + struct security_unix_token *utok; + + utok = handle->conn->session_info->unix_token; + sbuf->st_ex_uid = utok->uid; + sbuf->st_ex_gid = utok->gid; + } else { + /* + * We have an artificial connection for dfs for example. It + * sucks, but the current uid/gid is the best we have. + */ + sbuf->st_ex_uid = geteuid(); + sbuf->st_ex_gid = getegid(); + } + + return ret; +} + +static struct vfs_fn_pointers vfs_fake_perms_fns = { + .stat_fn = fake_perms_stat, + .fstat_fn = fake_perms_fstat +}; + +static_decl_vfs; +NTSTATUS vfs_fake_perms_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_perms", + &vfs_fake_perms_fns); +} diff --git a/source3/modules/vfs_fileid.c b/source3/modules/vfs_fileid.c new file mode 100644 index 0000000..2c67946 --- /dev/null +++ b/source3/modules/vfs_fileid.c @@ -0,0 +1,723 @@ +/* + * VFS module to alter the algorithm to calculate + * the struct file_id used as key for the share mode + * and byte range locking db's. + * + * Copyright (C) 2007, Stefan Metzmacher + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" + +static int vfs_fileid_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_fileid_debug_level + +struct fileid_mount_entry { + SMB_DEV_T device; + const char *mnt_fsname; + fsid_t fsid; + uint64_t devid; +}; + +struct fileid_nolock_inode { + dev_t dev; + ino_t ino; +}; + +struct fileid_handle_data { + struct vfs_handle_struct *handle; + struct file_id (*mapping_fn)(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf); + char **fstype_deny_list; + char **fstype_allow_list; + char **mntdir_deny_list; + char **mntdir_allow_list; + unsigned num_mount_entries; + struct fileid_mount_entry *mount_entries; + struct { + bool force_all_inodes; + bool force_all_dirs; + uint64_t extid; + size_t num_inodes; + struct fileid_nolock_inode *inodes; + } nolock; +}; + +/* check if a mount entry is allowed based on fstype and mount directory */ +static bool fileid_mount_entry_allowed(struct fileid_handle_data *data, + struct mntent *m) +{ + int i; + char **fstype_deny = data->fstype_deny_list; + char **fstype_allow = data->fstype_allow_list; + char **mntdir_deny = data->mntdir_deny_list; + char **mntdir_allow = data->mntdir_allow_list; + + if (fstype_deny != NULL) { + for (i = 0; fstype_deny[i] != NULL; i++) { + if (strcmp(m->mnt_type, fstype_deny[i]) == 0) { + return false; + } + } + } + if (fstype_allow != NULL) { + for (i = 0; fstype_allow[i] != NULL; i++) { + if (strcmp(m->mnt_type, fstype_allow[i]) == 0) { + break; + } + } + if (fstype_allow[i] == NULL) { + return false; + } + } + if (mntdir_deny != NULL) { + for (i=0; mntdir_deny[i] != NULL; i++) { + if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) { + return false; + } + } + } + if (mntdir_allow != NULL) { + for (i=0; mntdir_allow[i] != NULL; i++) { + if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) { + break; + } + } + if (mntdir_allow[i] == NULL) { + return false; + } + } + return true; +} + + +/* load all the mount entries from the mtab */ +static void fileid_load_mount_entries(struct fileid_handle_data *data) +{ + FILE *f; + struct mntent *m; + + data->num_mount_entries = 0; + TALLOC_FREE(data->mount_entries); + + f = setmntent("/etc/mtab", "r"); + if (!f) return; + + while ((m = getmntent(f))) { + struct stat st; + struct statfs sfs; + struct fileid_mount_entry *cur; + bool allowed; + + allowed = fileid_mount_entry_allowed(data, m); + if (!allowed) { + DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir); + continue; + } + if (stat(m->mnt_dir, &st) != 0) continue; + if (statfs(m->mnt_dir, &sfs) != 0) continue; + + if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) { + m->mnt_fsname += 5; + } + + data->mount_entries = talloc_realloc(data, + data->mount_entries, + struct fileid_mount_entry, + data->num_mount_entries+1); + if (data->mount_entries == NULL) { + goto nomem; + } + + cur = &data->mount_entries[data->num_mount_entries]; + cur->device = st.st_dev; + cur->mnt_fsname = talloc_strdup(data->mount_entries, + m->mnt_fsname); + if (!cur->mnt_fsname) goto nomem; + cur->fsid = sfs.f_fsid; + cur->devid = (uint64_t)-1; + + data->num_mount_entries++; + } + endmntent(f); + return; + +nomem: + if (f) endmntent(f); + + data->num_mount_entries = 0; + TALLOC_FREE(data->mount_entries); + + return; +} + +/* find a mount entry given a dev_t */ +static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data, + SMB_DEV_T dev) +{ + unsigned i; + + if (data->num_mount_entries == 0) { + fileid_load_mount_entries(data); + } + for (i=0;inum_mount_entries;i++) { + if (data->mount_entries[i].device == dev) { + return &data->mount_entries[i]; + } + } + /* 2nd pass after reloading */ + fileid_load_mount_entries(data); + for (i=0;inum_mount_entries;i++) { + if (data->mount_entries[i].device == dev) { + return &data->mount_entries[i]; + } + } + return NULL; +} + + +/* a 64 bit hash, based on the one in tdb */ +static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len) +{ + uint64_t value; /* Used to compute the hash value. */ + uint32_t i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + for (value = 0x238F13AFLL * len, i=0; i < len; i++) + value = (value + (((uint64_t)s[i]) << (i*5 % 24))); + + return (1103515243LL * value + 12345LL); +} + +/* a device mapping using a fsname */ +static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + struct fileid_mount_entry *m; + + m = fileid_find_mount_entry(data, sbuf->st_ex_dev); + if (!m) return sbuf->st_ex_dev; + + if (m->devid == (uint64_t)-1) { + m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname, + strlen(m->mnt_fsname)); + } + + return m->devid; +} + +static struct file_id fileid_mapping_fsname(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id = { .inode = sbuf->st_ex_ino, }; + + id.devid = fileid_device_mapping_fsname(data, sbuf); + + return id; +} + +/* a device mapping using a hostname */ +static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + char hostname[HOST_NAME_MAX+1]; + char *devname = NULL; + uint64_t id; + size_t devname_len; + int rc; + + rc = gethostname(hostname, HOST_NAME_MAX+1); + if (rc != 0) { + DBG_ERR("gethostname failed\n"); + return UINT64_MAX; + } + + devname = talloc_asprintf(talloc_tos(), "%s%ju", + hostname, (uintmax_t)sbuf->st_ex_dev); + if (devname == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return UINT64_MAX; + } + devname_len = talloc_array_length(devname) - 1; + + id = fileid_uint64_hash((uint8_t *)devname, devname_len); + + TALLOC_FREE(devname); + + return id; +} + +static struct file_id fileid_mapping_hostname(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id = { .inode = sbuf->st_ex_ino, }; + + id.devid = fileid_device_mapping_hostname(data, sbuf); + + return id; +} + +static bool fileid_is_nolock_inode(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + size_t i; + + if (data->nolock.force_all_inodes) { + return true; + } + + if (S_ISDIR(sbuf->st_ex_mode) && data->nolock.force_all_dirs) { + return true; + } + + /* + * We could make this a binary search over an sorted array, + * but for now we keep things simple. + */ + + for (i=0; i < data->nolock.num_inodes; i++) { + if (data->nolock.inodes[i].ino != sbuf->st_ex_ino) { + continue; + } + + if (data->nolock.inodes[i].dev == 0) { + /* + * legacy "fileid:nolockinode" + * handling ignoring dev + */ + return true; + } + + if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) { + continue; + } + + return true; + } + + return false; +} + +static int fileid_add_nolock_inode(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + bool exists = fileid_is_nolock_inode(data, sbuf); + struct fileid_nolock_inode *inodes = NULL; + + if (exists) { + return 0; + } + + inodes = talloc_realloc(data, data->nolock.inodes, + struct fileid_nolock_inode, + data->nolock.num_inodes + 1); + if (inodes == NULL) { + return -1; + } + + inodes[data->nolock.num_inodes] = (struct fileid_nolock_inode) { + .dev = sbuf->st_ex_dev, + .ino = sbuf->st_ex_ino, + }; + data->nolock.inodes = inodes; + data->nolock.num_inodes += 1; + + return 0; +} + +static uint64_t fileid_mapping_nolock_extid(uint64_t max_slots) +{ + char buf[8+4+HOST_NAME_MAX+1] = { 0, }; + uint64_t slot = 0; + uint64_t id; + int rc; + + if (max_slots > 1) { + slot = getpid() % max_slots; + } + + PUSH_LE_U64(buf, 0, slot); + PUSH_LE_U32(buf, 8, get_my_vnn()); + + rc = gethostname(&buf[12], HOST_NAME_MAX+1); + if (rc != 0) { + DBG_ERR("gethostname failed\n"); + return UINT64_MAX; + } + + id = fileid_uint64_hash((uint8_t *)buf, ARRAY_SIZE(buf)); + + return id; +} + +/* device mapping functions using a fsid */ +static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + struct fileid_mount_entry *m; + + m = fileid_find_mount_entry(data, sbuf->st_ex_dev); + if (!m) return sbuf->st_ex_dev; + + if (m->devid == (uint64_t)-1) { + if (sizeof(fsid_t) > sizeof(uint64_t)) { + m->devid = fileid_uint64_hash((uint8_t *)&m->fsid, + sizeof(m->fsid)); + } else { + union { + uint64_t ret; + fsid_t fsid; + } u; + ZERO_STRUCT(u); + u.fsid = m->fsid; + m->devid = u.ret; + } + } + + return m->devid; +} + +static struct file_id fileid_mapping_fsid(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id = { .inode = sbuf->st_ex_ino, }; + + id.devid = fileid_device_mapping_fsid(data, sbuf); + + return id; +} + +static struct file_id fileid_mapping_next_module(struct fileid_handle_data *data, + const SMB_STRUCT_STAT *sbuf) +{ + return SMB_VFS_NEXT_FILE_ID_CREATE(data->handle, sbuf); +} + +static int get_connectpath_ino(struct vfs_handle_struct *handle, + const char *path, + SMB_STRUCT_STAT *psbuf) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct smb_filename *fname = NULL; + const char *fullpath = NULL; + int ret; + + if (path[0] == '/') { + fullpath = path; + } else { + fullpath = talloc_asprintf(frame, + "%s/%s", + handle->conn->connectpath, + path); + if (fullpath == NULL) { + DBG_ERR("talloc_asprintf() failed\n"); + TALLOC_FREE(frame); + return -1; + } + } + + fname = synthetic_smb_fname(frame, + fullpath, + NULL, + NULL, + 0, + 0); + if (fname == NULL) { + DBG_ERR("synthetic_smb_fname(%s) failed - %s\n", + fullpath, strerror(errno)); + TALLOC_FREE(frame); + return -1; + } + + ret = SMB_VFS_NEXT_STAT(handle, fname); + if (ret != 0) { + DBG_ERR("stat failed for %s with %s\n", + fullpath, strerror(errno)); + TALLOC_FREE(frame); + return -1; + } + *psbuf = fname->st; + + TALLOC_FREE(frame); + + return 0; +} + +static int fileid_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct fileid_handle_data *data; + const char *algorithm; + const char **fstype_deny_list = NULL; + const char **fstype_allow_list = NULL; + const char **mntdir_deny_list = NULL; + const char **mntdir_allow_list = NULL; + ino_t nolockinode; + uint64_t max_slots = 0; + bool rootdir_nolock = false; + const char **nolock_paths = NULL; + size_t i; + int saved_errno; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + data = talloc_zero(handle, struct fileid_handle_data); + if (!data) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0, ("talloc_zero() failed\n")); + errno = saved_errno; + return -1; + } + data->handle = handle; + + /* + * "fileid:mapping" is only here as fallback for old setups + * "fileid:algorithm" is the option new setups should use + */ + algorithm = lp_parm_const_string(SNUM(handle->conn), + "fileid", "mapping", + "fsname"); + algorithm = lp_parm_const_string(SNUM(handle->conn), + "fileid", "algorithm", + algorithm); + if (strcmp("fsname", algorithm) == 0) { + data->mapping_fn = fileid_mapping_fsname; + } else if (strcmp("fsname_nodirs", algorithm) == 0) { + data->mapping_fn = fileid_mapping_fsname; + data->nolock.force_all_dirs = true; + } else if (strcmp("fsid", algorithm) == 0) { + data->mapping_fn = fileid_mapping_fsid; + } else if (strcmp("hostname", algorithm) == 0) { + data->mapping_fn = fileid_mapping_hostname; + data->nolock.force_all_inodes = true; + } else if (strcmp("fsname_norootdir", algorithm) == 0) { + data->mapping_fn = fileid_mapping_fsname; + rootdir_nolock = true; + } else if (strcmp("fsname_norootdir_ext", algorithm) == 0) { + data->mapping_fn = fileid_mapping_fsname; + rootdir_nolock = true; + max_slots = UINT64_MAX; + } else if (strcmp("next_module", algorithm) == 0) { + data->mapping_fn = fileid_mapping_next_module; + } else { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm)); + return -1; + } + + fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid", + "fstype deny", NULL); + if (fstype_deny_list != NULL) { + data->fstype_deny_list = str_list_copy(data, fstype_deny_list); + if (data->fstype_deny_list == NULL) { + saved_errno = errno; + DBG_ERR("str_list_copy failed\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid", + "fstype allow", NULL); + if (fstype_allow_list != NULL) { + data->fstype_allow_list = str_list_copy(data, fstype_allow_list); + if (data->fstype_allow_list == NULL) { + saved_errno = errno; + DBG_ERR("str_list_copy failed\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid", + "mntdir deny", NULL); + if (mntdir_deny_list != NULL) { + data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list); + if (data->mntdir_deny_list == NULL) { + saved_errno = errno; + DBG_ERR("str_list_copy failed\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid", + "mntdir allow", NULL); + if (mntdir_allow_list != NULL) { + data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list); + if (data->mntdir_allow_list == NULL) { + saved_errno = errno; + DBG_ERR("str_list_copy failed\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + data->nolock.force_all_inodes = lp_parm_bool(SNUM(handle->conn), + "fileid", "nolock_all_inodes", + data->nolock.force_all_inodes); + data->nolock.force_all_dirs = lp_parm_bool(SNUM(handle->conn), + "fileid", "nolock_all_dirs", + data->nolock.force_all_dirs); + + max_slots = lp_parm_ulonglong(SNUM(handle->conn), + "fileid", "nolock_max_slots", + max_slots); + max_slots = MAX(max_slots, 1); + + data->nolock.extid = fileid_mapping_nolock_extid(max_slots); + + nolockinode = lp_parm_ulong(SNUM(handle->conn), "fileid", "nolockinode", 0); + if (nolockinode != 0) { + SMB_STRUCT_STAT tmpsbuf = { .st_ex_ino = nolockinode, }; + + ret = fileid_add_nolock_inode(data, &tmpsbuf); + if (ret != 0) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + if (rootdir_nolock) { + SMB_STRUCT_STAT rootdirsbuf; + + ret = get_connectpath_ino(handle, ".", &rootdirsbuf); + if (ret != 0) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + + ret = fileid_add_nolock_inode(data, &rootdirsbuf); + if (ret != 0) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + } + + nolock_paths = lp_parm_string_list(SNUM(handle->conn), "fileid", "nolock_paths", NULL); + for (i = 0; nolock_paths != NULL && nolock_paths[i] != NULL; i++) { + SMB_STRUCT_STAT tmpsbuf; + + ret = get_connectpath_ino(handle, nolock_paths[i], &tmpsbuf); + if (ret == -1 && errno == ENOENT) { + DBG_ERR("ignoring non existing nolock_paths[%zu]='%s'\n", + i, nolock_paths[i]); + continue; + } + if (ret != 0) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + + ret = fileid_add_nolock_inode(data, &tmpsbuf); + if (ret != 0) { + saved_errno = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = saved_errno; + return -1; + } + DBG_DEBUG("Adding nolock_paths[%zu]='%s'\n", + i, nolock_paths[i]); + } + + SMB_VFS_HANDLE_SET_DATA(handle, data, NULL, + struct fileid_handle_data, + return -1); + + DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n", + service, algorithm, data->nolock.num_inodes); + + return 0; +} + +static void fileid_disconnect(struct vfs_handle_struct *handle) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(10,("fileid_disconnect() connect to service[%s].\n", + lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn)))); + + SMB_VFS_NEXT_DISCONNECT(handle); +} + +static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct fileid_handle_data *data; + struct file_id id = { .inode = 0, }; + + SMB_VFS_HANDLE_GET_DATA(handle, data, + struct fileid_handle_data, + return id); + + id = data->mapping_fn(data, sbuf); + if (id.extid == 0 && fileid_is_nolock_inode(data, sbuf)) { + id.extid = data->nolock.extid; + } + + DBG_DEBUG("Returning dev [%jx] inode [%jx] extid [%jx]\n", + (uintmax_t)id.devid, (uintmax_t)id.inode, (uintmax_t)id.extid); + + return id; +} + +static struct vfs_fn_pointers vfs_fileid_fns = { + .connect_fn = fileid_connect, + .disconnect_fn = fileid_disconnect, + .file_id_create_fn = fileid_file_id_create +}; + +static_decl_vfs; +NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid", + &vfs_fileid_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + vfs_fileid_debug_level = debug_add_class("fileid"); + if (vfs_fileid_debug_level == -1) { + vfs_fileid_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n")); + } else { + DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c new file mode 100644 index 0000000..4ab0f46 --- /dev/null +++ b/source3/modules/vfs_fruit.c @@ -0,0 +1,5478 @@ +/* + * OS X and Netatalk interoperability VFS module for Samba-3.x + * + * Copyright (C) Ralph Boehme, 2013, 2014 + * + * 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 "MacExtensions.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "lib/util/time.h" +#include "system/shmem.h" +#include "locking/proto.h" +#include "smbd/globals.h" +#include "messages.h" +#include "libcli/security/security.h" +#include "../libcli/smb/smb2_create_ctx.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/tevent_unix.h" +#include "offload_token.h" +#include "string_replace.h" +#include "hash_inode.h" +#include "lib/adouble.h" +#include "lib/util_macstreams.h" +#include "source3/smbd/dir.h" + +/* + * Enhanced OS X and Netatalk compatibility + * ======================================== + * + * This modules takes advantage of vfs_streams_xattr and + * vfs_catia. VFS modules vfs_fruit and vfs_streams_xattr must be + * loaded in the correct order: + * + * vfs modules = catia fruit streams_xattr + * + * The module intercepts the OS X special streams "AFP_AfpInfo" and + * "AFP_Resource" and handles them in a special way. All other named + * streams are deferred to vfs_streams_xattr. + * + * The OS X client maps all NTFS illegal characters to the Unicode + * private range. This module optionally stores the characters using + * their native ASCII encoding using vfs_catia. If you're not enabling + * this feature, you can skip catia from vfs modules. + * + * Finally, open modes are optionally checked against Netatalk AFP + * share modes. + * + * The "AFP_AfpInfo" named stream is a binary blob containing OS X + * extended metadata for files and directories. This module optionally + * reads and stores this metadata in a way compatible with Netatalk 3 + * which stores the metadata in an EA "org.netatalk.metadata". Cf + * source3/include/MacExtensions.h for a description of the binary + * blobs content. + * + * The "AFP_Resource" named stream may be arbitrarily large, thus it + * can't be stored in an xattr on most filesystem. ZFS on Solaris is + * the only available filesystem where xattrs can be of any size and + * the OS supports using the file APIs for xattrs. + * + * The AFP_Resource stream is stored in an AppleDouble file prepending + * "._" to the filename. On Solaris with ZFS the stream is optionally + * stored in an EA "org.netatalk.resource". + * + * + * Extended Attributes + * =================== + * + * The OS X SMB client sends xattrs as ADS too. For xattr interop with + * other protocols you may want to adjust the xattr names the VFS + * module vfs_streams_xattr uses for storing ADS's. This defaults to + * user.DosStream.ADS_NAME:$DATA and can be changed by specifying + * these module parameters: + * + * streams_xattr:prefix = user. + * streams_xattr:store_stream_type = false + * + * + * TODO + * ==== + * + * - log diagnostic if any needed VFS module is not loaded + * (eg with lp_vfs_objects()) + * - add tests + */ + +static int vfs_fruit_debug_level = DBGC_VFS; + +static struct global_fruit_config { + bool nego_aapl; /* client negotiated AAPL */ + +} global_fruit_config; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_fruit_debug_level + +#define FRUIT_PARAM_TYPE_NAME "fruit" + +enum apple_fork {APPLE_FORK_DATA, APPLE_FORK_RSRC}; + +enum fruit_rsrc {FRUIT_RSRC_STREAM, FRUIT_RSRC_ADFILE, FRUIT_RSRC_XATTR}; +enum fruit_meta {FRUIT_META_STREAM, FRUIT_META_NETATALK}; +enum fruit_locking {FRUIT_LOCKING_NETATALK, FRUIT_LOCKING_NONE}; +enum fruit_encoding {FRUIT_ENC_NATIVE, FRUIT_ENC_PRIVATE}; + +struct fruit_config_data { + enum fruit_rsrc rsrc; + enum fruit_meta meta; + enum fruit_locking locking; + enum fruit_encoding encoding; + bool use_aapl; /* config from smb.conf */ + bool use_copyfile; + bool readdir_attr_enabled; + bool unix_info_enabled; + bool copyfile_enabled; + bool veto_appledouble; + bool posix_rename; + bool aapl_zero_file_id; + const char *model; + bool time_machine; + off_t time_machine_max_size; + bool convert_adouble; + bool wipe_intentionally_left_blank_rfork; + bool delete_empty_adfiles; + bool validate_afpinfo; + + /* + * Additional options, all enabled by default, + * possibly useful for analyzing performance. The associated + * operations with each of them may be expensive, so having + * the chance to disable them individually gives a chance + * tweaking the setup for the particular usecase. + */ + bool readdir_attr_rsize; + bool readdir_attr_finder_info; + bool readdir_attr_max_access; + /* Recursion guard. Will go away when we have STATX. */ + bool in_openat_pathref_fsp; +}; + +static const struct enum_list fruit_rsrc[] = { + {FRUIT_RSRC_STREAM, "stream"}, /* pass on to vfs_streams_xattr */ + {FRUIT_RSRC_ADFILE, "file"}, /* ._ AppleDouble file */ + {FRUIT_RSRC_XATTR, "xattr"}, /* Netatalk compatible xattr (ZFS only) */ + { -1, NULL} +}; + +static const struct enum_list fruit_meta[] = { + {FRUIT_META_STREAM, "stream"}, /* pass on to vfs_streams_xattr */ + {FRUIT_META_NETATALK, "netatalk"}, /* Netatalk compatible xattr */ + { -1, NULL} +}; + +static const struct enum_list fruit_locking[] = { + {FRUIT_LOCKING_NETATALK, "netatalk"}, /* synchronize locks with Netatalk */ + {FRUIT_LOCKING_NONE, "none"}, + { -1, NULL} +}; + +static const struct enum_list fruit_encoding[] = { + {FRUIT_ENC_NATIVE, "native"}, /* map unicode private chars to ASCII */ + {FRUIT_ENC_PRIVATE, "private"}, /* keep unicode private chars */ + { -1, NULL} +}; + +struct fio { + vfs_handle_struct *handle; + files_struct *fsp; /* backlink to itself */ + + /* tcon config handle */ + struct fruit_config_data *config; + + /* Backend fsp for AppleDouble file, can be NULL */ + files_struct *ad_fsp; + /* link from adouble_open_from_base_fsp() to fio */ + struct fio *real_fio; + + /* Denote stream type, meta or rsrc */ + adouble_type_t type; + + /* + * AFP_AfpInfo stream created, but not written yet, thus still a fake + * pipe fd. This is set to true in fruit_open_meta if there was no + * existing stream but the caller requested O_CREAT. It is later set to + * false when we get a write on the stream that then does open and + * create the stream. + */ + bool fake_fd; + int flags; + int mode; +}; + +/***************************************************************************** + * Helper functions + *****************************************************************************/ + +static struct adouble *ad_get_meta_fsp(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + NTSTATUS status; + struct adouble *ad = NULL; + struct smb_filename *smb_fname_cp = NULL; + struct fruit_config_data *config = NULL; + + if (smb_fname->fsp != NULL) { + return ad_get(ctx, handle, smb_fname, ADOUBLE_META); + } + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct fruit_config_data, + return NULL); + + if (config->in_openat_pathref_fsp) { + return NULL; + } + + smb_fname_cp = cp_smb_filename(ctx, + smb_fname); + if (smb_fname_cp == NULL) { + return NULL; + } + TALLOC_FREE(smb_fname_cp->stream_name); + config->in_openat_pathref_fsp = true; + status = openat_pathref_fsp(handle->conn->cwd_fsp, + smb_fname_cp); + config->in_openat_pathref_fsp = false; + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname_cp); + return NULL; + } + + ad = ad_get(ctx, handle, smb_fname_cp, ADOUBLE_META); + TALLOC_FREE(smb_fname_cp); + return ad; +} + +static struct fio *fruit_get_complete_fio(vfs_handle_struct *handle, + files_struct *fsp) +{ + struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (fio == NULL) { + return NULL; + } + + if (fio->real_fio != NULL) { + /* + * This is an fsp from adouble_open_from_base_fsp() + * we should just pass this to the next + * module. + */ + return NULL; + } + + return fio; +} + +/** + * Initialize config struct from our smb.conf config parameters + **/ +static int init_fruit_config(vfs_handle_struct *handle) +{ + struct fruit_config_data *config; + int enumval = -1; + const char *tm_size_str = NULL; + + config = talloc_zero(handle->conn, struct fruit_config_data); + if (!config) { + DEBUG(1, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "resource", fruit_rsrc, FRUIT_RSRC_ADFILE); + if (enumval == -1) { + DEBUG(1, ("value for %s: resource type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->rsrc = (enum fruit_rsrc)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "metadata", fruit_meta, FRUIT_META_NETATALK); + if (enumval == -1) { + DEBUG(1, ("value for %s: metadata type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->meta = (enum fruit_meta)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "locking", fruit_locking, FRUIT_LOCKING_NONE); + if (enumval == -1) { + DEBUG(1, ("value for %s: locking type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->locking = (enum fruit_locking)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "encoding", fruit_encoding, FRUIT_ENC_PRIVATE); + if (enumval == -1) { + DEBUG(1, ("value for %s: encoding type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->encoding = (enum fruit_encoding)enumval; + + if (config->rsrc == FRUIT_RSRC_ADFILE) { + config->veto_appledouble = lp_parm_bool(SNUM(handle->conn), + FRUIT_PARAM_TYPE_NAME, + "veto_appledouble", + true); + } + + config->use_aapl = lp_parm_bool( + -1, FRUIT_PARAM_TYPE_NAME, "aapl", true); + + config->time_machine = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "time machine", false); + + config->unix_info_enabled = lp_parm_bool( + -1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true); + + config->use_copyfile = lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME, + "copyfile", false); + + config->posix_rename = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "posix_rename", true); + + config->aapl_zero_file_id = + lp_parm_bool(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "zero_file_id", true); + + config->readdir_attr_rsize = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_rsize", true); + + config->readdir_attr_finder_info = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_finder_info", true); + + config->readdir_attr_max_access = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_max_access", true); + + config->model = lp_parm_const_string( + -1, FRUIT_PARAM_TYPE_NAME, "model", "MacSamba"); + + tm_size_str = lp_parm_const_string( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "time machine max size", NULL); + if (tm_size_str != NULL) { + config->time_machine_max_size = conv_str_size(tm_size_str); + } + + config->convert_adouble = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "convert_adouble", true); + + config->wipe_intentionally_left_blank_rfork = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "wipe_intentionally_left_blank_rfork", false); + + config->delete_empty_adfiles = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "delete_empty_adfiles", false); + + config->validate_afpinfo = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "validate_afpinfo", true); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct fruit_config_data, + return -1); + + return 0; +} + +static bool add_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, off_t size, + off_t alloc_size) +{ + struct stream_struct *tmp; + + tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_asprintf(tmp, "%s:$DATA", name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +static bool filter_empty_rsrc_stream(unsigned int *num_streams, + struct stream_struct **streams) +{ + struct stream_struct *tmp = *streams; + unsigned int i; + + if (*num_streams == 0) { + return true; + } + + for (i = 0; i < *num_streams; i++) { + if (strequal_m(tmp[i].name, AFPRESOURCE_STREAM)) { + break; + } + } + + if (i == *num_streams) { + return true; + } + + if (tmp[i].size > 0) { + return true; + } + + TALLOC_FREE(tmp[i].name); + ARRAY_DEL_ELEMENT(tmp, i, *num_streams); + *num_streams -= 1; + return true; +} + +static bool del_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name) +{ + struct stream_struct *tmp = *streams; + unsigned int i; + + if (*num_streams == 0) { + return true; + } + + for (i = 0; i < *num_streams; i++) { + if (strequal_m(tmp[i].name, name)) { + break; + } + } + + if (i == *num_streams) { + return true; + } + + TALLOC_FREE(tmp[i].name); + ARRAY_DEL_ELEMENT(tmp, i, *num_streams); + *num_streams -= 1; + return true; +} + +static bool ad_empty_finderinfo(const struct adouble *ad) +{ + int cmp; + char emptybuf[ADEDLEN_FINDERI] = {0}; + char *fi = NULL; + + fi = ad_get_entry(ad, ADEID_FINDERI); + if (fi == NULL) { + DBG_ERR("Missing FinderInfo in struct adouble [%p]\n", ad); + return false; + } + + cmp = memcmp(emptybuf, fi, ADEDLEN_FINDERI); + return (cmp == 0); +} + +static bool ai_empty_finderinfo(const AfpInfo *ai) +{ + int cmp; + char emptybuf[ADEDLEN_FINDERI] = {0}; + + cmp = memcmp(emptybuf, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI); + return (cmp == 0); +} + +/** + * Update btime with btime from Netatalk + **/ +static void update_btime(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + uint32_t t; + struct timespec creation_time = {0}; + struct adouble *ad; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return); + + switch (config->meta) { + case FRUIT_META_STREAM: + return; + case FRUIT_META_NETATALK: + /* Handled below */ + break; + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return; + } + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + return; + } + if (ad_getdate(ad, AD_DATE_UNIX | AD_DATE_CREATE, &t) != 0) { + TALLOC_FREE(ad); + return; + } + TALLOC_FREE(ad); + + creation_time.tv_sec = convert_uint32_t_to_time_t(t); + update_stat_ex_create_time(&smb_fname->st, creation_time); + + return; +} + +/** + * Map an access mask to a Netatalk single byte byte range lock + **/ +static off_t access_to_netatalk_brl(enum apple_fork fork_type, + uint32_t access_mask) +{ + off_t offset; + + switch (access_mask) { + case FILE_READ_DATA: + offset = AD_FILELOCK_OPEN_RD; + break; + + case FILE_WRITE_DATA: + case FILE_APPEND_DATA: + offset = AD_FILELOCK_OPEN_WR; + break; + + default: + offset = AD_FILELOCK_OPEN_NONE; + break; + } + + if (fork_type == APPLE_FORK_RSRC) { + if (offset == AD_FILELOCK_OPEN_NONE) { + offset = AD_FILELOCK_RSRC_OPEN_NONE; + } else { + offset += 2; + } + } + + return offset; +} + +/** + * Map a deny mode to a Netatalk brl + **/ +static off_t denymode_to_netatalk_brl(enum apple_fork fork_type, + uint32_t deny_mode) +{ + off_t offset = 0; + + switch (deny_mode) { + case DENY_READ: + offset = AD_FILELOCK_DENY_RD; + break; + + case DENY_WRITE: + offset = AD_FILELOCK_DENY_WR; + break; + + default: + smb_panic("denymode_to_netatalk_brl: bad deny mode\n"); + } + + if (fork_type == APPLE_FORK_RSRC) { + offset += 2; + } + + return offset; +} + +/** + * Call fcntl() with an exclusive F_GETLK request in order to + * determine if there's an existing shared lock + * + * @return true if the requested lock was found or any error occurred + * false if the lock was not found + **/ +static bool test_netatalk_lock(files_struct *fsp, off_t in_offset) +{ + bool result; + off_t offset = in_offset; + off_t len = 1; + int type = F_WRLCK; + pid_t pid = 0; + + result = SMB_VFS_GETLOCK(fsp, &offset, &len, &type, &pid); + if (result == false) { + return true; + } + + if (type != F_UNLCK) { + return true; + } + + return false; +} + +static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t access_mask, + uint32_t share_mode) +{ + NTSTATUS status = NT_STATUS_OK; + off_t off; + bool share_for_read = (share_mode & FILE_SHARE_READ); + bool share_for_write = (share_mode & FILE_SHARE_WRITE); + bool netatalk_already_open_for_reading = false; + bool netatalk_already_open_for_writing = false; + bool netatalk_already_open_with_deny_read = false; + bool netatalk_already_open_with_deny_write = false; + struct GUID req_guid = GUID_random(); + + /* FIXME: hardcoded data fork, add resource fork */ + enum apple_fork fork_type = APPLE_FORK_DATA; + + DBG_DEBUG("fruit_check_access: %s, am: %s/%s, sm: 0x%x\n", + fsp_str_dbg(fsp), + access_mask & FILE_READ_DATA ? "READ" :"-", + access_mask & FILE_WRITE_DATA ? "WRITE" : "-", + share_mode); + + if (fsp_get_io_fd(fsp) == -1) { + return NT_STATUS_OK; + } + + /* Read NetATalk opens and deny modes on the file. */ + netatalk_already_open_for_reading = test_netatalk_lock(fsp, + access_to_netatalk_brl(fork_type, + FILE_READ_DATA)); + + netatalk_already_open_with_deny_read = test_netatalk_lock(fsp, + denymode_to_netatalk_brl(fork_type, + DENY_READ)); + + netatalk_already_open_for_writing = test_netatalk_lock(fsp, + access_to_netatalk_brl(fork_type, + FILE_WRITE_DATA)); + + netatalk_already_open_with_deny_write = test_netatalk_lock(fsp, + denymode_to_netatalk_brl(fork_type, + DENY_WRITE)); + + /* If there are any conflicts - sharing violation. */ + if ((access_mask & FILE_READ_DATA) && + netatalk_already_open_with_deny_read) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!share_for_read && + netatalk_already_open_for_reading) { + return NT_STATUS_SHARING_VIOLATION; + } + + if ((access_mask & FILE_WRITE_DATA) && + netatalk_already_open_with_deny_write) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!share_for_write && + netatalk_already_open_for_writing) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!(access_mask & FILE_READ_DATA)) { + /* + * Nothing we can do here, we need read access + * to set locks. + */ + return NT_STATUS_OK; + } + + /* Set NetAtalk locks matching our access */ + if (access_mask & FILE_READ_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_READ_DATA); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!share_for_read) { + off = denymode_to_netatalk_brl(fork_type, DENY_READ); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (access_mask & FILE_WRITE_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!share_for_write) { + off = denymode_to_netatalk_brl(fork_type, DENY_WRITE); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS check_aapl(vfs_handle_struct *handle, + struct smb_request *req, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + struct fruit_config_data *config; + NTSTATUS status; + struct smb2_create_blob *aapl = NULL; + uint32_t cmd; + bool ok; + uint8_t p[16]; + DATA_BLOB blob = data_blob_talloc(req, NULL, 0); + uint64_t req_bitmap, client_caps; + uint64_t server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED; + smb_ucs2_t *model; + size_t modellen; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!config->use_aapl + || in_context_blobs == NULL + || out_context_blobs == NULL) { + return NT_STATUS_OK; + } + + aapl = smb2_create_blob_find(in_context_blobs, + SMB2_CREATE_TAG_AAPL); + if (aapl == NULL) { + return NT_STATUS_OK; + } + + if (aapl->data.length != 24) { + DEBUG(1, ("unexpected AAPL ctxt length: %ju\n", + (uintmax_t)aapl->data.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + cmd = IVAL(aapl->data.data, 0); + if (cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) { + DEBUG(1, ("unsupported AAPL cmd: %d\n", cmd)); + return NT_STATUS_INVALID_PARAMETER; + } + + req_bitmap = BVAL(aapl->data.data, 8); + client_caps = BVAL(aapl->data.data, 16); + + SIVAL(p, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY); + SIVAL(p, 4, 0); + SBVAL(p, 8, req_bitmap); + ok = data_blob_append(req, &blob, p, 16); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) { + if ((client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) && + (handle->conn->fs_capabilities & FILE_NAMED_STREAMS)) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR; + config->readdir_attr_enabled = true; + } + + if (config->use_copyfile) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE; + config->copyfile_enabled = true; + } + + /* + * The client doesn't set the flag, so we can't check + * for it and just set it unconditionally + */ + if (config->unix_info_enabled) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE; + } + + SBVAL(p, 0, server_caps); + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) { + int val = lp_case_sensitive(SNUM(handle->conn)); + uint64_t caps = 0; + + switch (val) { + case Auto: + break; + + case True: + caps |= SMB2_CRTCTX_AAPL_CASE_SENSITIVE; + break; + + default: + break; + } + + if (config->time_machine) { + caps |= SMB2_CRTCTX_AAPL_FULL_SYNC; + } + + SBVAL(p, 0, caps); + + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) { + ok = convert_string_talloc(req, + CH_UNIX, CH_UTF16LE, + config->model, strlen(config->model), + &model, &modellen); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + SIVAL(p, 0, 0); + SIVAL(p + 4, 0, modellen); + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + talloc_free(model); + return NT_STATUS_UNSUCCESSFUL; + } + + ok = data_blob_append(req, &blob, model, modellen); + talloc_free(model); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + status = smb2_create_blob_add(out_context_blobs, + out_context_blobs, + SMB2_CREATE_TAG_AAPL, + blob); + if (NT_STATUS_IS_OK(status)) { + global_fruit_config.nego_aapl = true; + } + + return status; +} + +static bool readdir_attr_meta_finderi_stream( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + AfpInfo *ai) +{ + struct smb_filename *stream_name = NULL; + files_struct *fsp = NULL; + ssize_t nread; + NTSTATUS status; + bool ok; + uint8_t buf[AFP_INFO_SIZE]; + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + AFPINFO_STREAM_NAME, + NULL, + smb_fname->twrp, + smb_fname->flags, + &stream_name); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = SMB_VFS_CREATE_FILE( + handle->conn, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + stream_name, /* fname */ + FILE_READ_DATA, /* access_mask */ + (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ + FILE_SHARE_DELETE), + FILE_OPEN, /* create_disposition*/ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + TALLOC_FREE(stream_name); + + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + nread = SMB_VFS_PREAD(fsp, &buf[0], AFP_INFO_SIZE, 0); + if (nread != AFP_INFO_SIZE) { + DBG_ERR("short read [%s] [%zd/%d]\n", + smb_fname_str_dbg(stream_name), nread, AFP_INFO_SIZE); + ok = false; + goto fail; + } + + memcpy(&ai->afpi_FinderInfo[0], &buf[AFP_OFF_FinderInfo], + AFP_FinderSize); + + ok = true; + +fail: + if (fsp != NULL) { + close_file_free(NULL, &fsp, NORMAL_CLOSE); + } + + return ok; +} + +static bool readdir_attr_meta_finderi_netatalk( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + AfpInfo *ai) +{ + struct adouble *ad = NULL; + char *p = NULL; + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + return false; + } + + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", smb_fname->base_name); + TALLOC_FREE(ad); + return false; + } + + memcpy(&ai->afpi_FinderInfo[0], p, AFP_FinderSize); + TALLOC_FREE(ad); + return true; +} + +static bool readdir_attr_meta_finderi(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct readdir_attr_data *attr_data) +{ + struct fruit_config_data *config = NULL; + uint32_t date_added; + AfpInfo ai = {0}; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return false); + + switch (config->meta) { + case FRUIT_META_NETATALK: + ok = readdir_attr_meta_finderi_netatalk( + handle, smb_fname, &ai); + break; + + case FRUIT_META_STREAM: + ok = readdir_attr_meta_finderi_stream( + handle, smb_fname, &ai); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return false; + } + + if (!ok) { + /* Don't bother with errors, it's likely ENOENT */ + return true; + } + + if (S_ISREG(smb_fname->st.st_ex_mode)) { + /* finder_type */ + memcpy(&attr_data->attr_data.aapl.finder_info[0], + &ai.afpi_FinderInfo[0], 4); + + /* finder_creator */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 4, + &ai.afpi_FinderInfo[4], 4); + } + + /* finder_flags */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 8, + &ai.afpi_FinderInfo[8], 2); + + /* finder_ext_flags */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 10, + &ai.afpi_FinderInfo[24], 2); + + /* creation date */ + date_added = convert_time_t_to_uint32_t( + smb_fname->st.st_ex_btime.tv_sec - AD_DATE_DELTA); + + RSIVAL(&attr_data->attr_data.aapl.finder_info[0], 12, date_added); + + return true; +} + +static uint64_t readdir_attr_rfork_size_adouble( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct adouble *ad = NULL; + uint64_t rfork_size; + + ad = ad_get(talloc_tos(), handle, smb_fname, + ADOUBLE_RSRC); + if (ad == NULL) { + return 0; + } + + rfork_size = ad_getentrylen(ad, ADEID_RFORK); + TALLOC_FREE(ad); + + return rfork_size; +} + +static uint64_t readdir_attr_rfork_size_stream( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct smb_filename *stream_name = NULL; + int ret; + uint64_t rfork_size; + + stream_name = synthetic_smb_fname(talloc_tos(), + smb_fname->base_name, + AFPRESOURCE_STREAM_NAME, + NULL, + smb_fname->twrp, + 0); + if (stream_name == NULL) { + return 0; + } + + ret = SMB_VFS_STAT(handle->conn, stream_name); + if (ret != 0) { + TALLOC_FREE(stream_name); + return 0; + } + + rfork_size = stream_name->st.st_ex_size; + TALLOC_FREE(stream_name); + + return rfork_size; +} + +static uint64_t readdir_attr_rfork_size(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct fruit_config_data *config = NULL; + uint64_t rfork_size; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return 0); + + switch (config->rsrc) { + case FRUIT_RSRC_ADFILE: + rfork_size = readdir_attr_rfork_size_adouble(handle, + smb_fname); + break; + + case FRUIT_RSRC_XATTR: + case FRUIT_RSRC_STREAM: + rfork_size = readdir_attr_rfork_size_stream(handle, + smb_fname); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + rfork_size = 0; + break; + } + + return rfork_size; +} + +static NTSTATUS readdir_attr_macmeta(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct readdir_attr_data *attr_data) +{ + NTSTATUS status = NT_STATUS_OK; + struct fruit_config_data *config = NULL; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + + /* Ensure we return a default value in the creation_date field */ + RSIVAL(&attr_data->attr_data.aapl.finder_info, 12, AD_DATE_START); + + /* + * Resource fork length + */ + + if (config->readdir_attr_rsize) { + uint64_t rfork_size; + + rfork_size = readdir_attr_rfork_size(handle, smb_fname); + attr_data->attr_data.aapl.rfork_size = rfork_size; + } + + /* + * FinderInfo + */ + + if (config->readdir_attr_finder_info) { + ok = readdir_attr_meta_finderi(handle, smb_fname, attr_data); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + } + } + + return status; +} + +static NTSTATUS remove_virtual_nfs_aces(struct security_descriptor *psd) +{ + NTSTATUS status; + uint32_t i; + + if (psd->dacl == NULL) { + return NT_STATUS_OK; + } + + for (i = 0; i < psd->dacl->num_aces; i++) { + /* MS NFS style mode/uid/gid */ + int cmp = dom_sid_compare_domain( + &global_sid_Unix_NFS, + &psd->dacl->aces[i].trustee); + if (cmp != 0) { + /* Normal ACE entry. */ + continue; + } + + /* + * security_descriptor_dacl_del() + * *must* return NT_STATUS_OK as we know + * we have something to remove. + */ + + status = security_descriptor_dacl_del(psd, + &psd->dacl->aces[i].trustee); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to remove MS NFS style ACE: %s\n", + nt_errstr(status)); + return status; + } + + /* + * security_descriptor_dacl_del() may delete more + * then one entry subsequent to this one if the + * SID matches, but we only need to ensure that + * we stay looking at the same element in the array. + */ + i--; + } + return NT_STATUS_OK; +} + +/* Search MS NFS style ACE with UNIX mode */ +static NTSTATUS check_ms_nfs(vfs_handle_struct *handle, + files_struct *fsp, + struct security_descriptor *psd, + mode_t *pmode, + bool *pdo_chmod) +{ + uint32_t i; + struct fruit_config_data *config = NULL; + + *pdo_chmod = false; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!global_fruit_config.nego_aapl) { + return NT_STATUS_OK; + } + if (psd->dacl == NULL || !config->unix_info_enabled) { + return NT_STATUS_OK; + } + + for (i = 0; i < psd->dacl->num_aces; i++) { + if (dom_sid_compare_domain( + &global_sid_Unix_NFS_Mode, + &psd->dacl->aces[i].trustee) == 0) { + *pmode = (mode_t)psd->dacl->aces[i].trustee.sub_auths[2]; + *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO); + *pdo_chmod = true; + + DEBUG(10, ("MS NFS chmod request %s, %04o\n", + fsp_str_dbg(fsp), (unsigned)(*pmode))); + break; + } + } + + /* + * Remove any incoming virtual ACE entries generated by + * fruit_fget_nt_acl(). + */ + + return remove_virtual_nfs_aces(psd); +} + +/**************************************************************************** + * VFS ops + ****************************************************************************/ + +static int fruit_connect(vfs_handle_struct *handle, + const char *service, + const char *user) +{ + int rc; + char *list = NULL, *newlist = NULL; + struct fruit_config_data *config; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(10, ("fruit_connect\n")); + + rc = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (rc < 0) { + return rc; + } + + rc = init_fruit_config(handle); + if (rc != 0) { + return rc; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (config->veto_appledouble) { + list = lp_veto_files(talloc_tos(), lp_sub, SNUM(handle->conn)); + + if (list) { + if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) { + newlist = talloc_asprintf( + list, + "%s/" ADOUBLE_NAME_PREFIX "*/", + list); + lp_do_parameter(SNUM(handle->conn), + "veto files", + newlist); + } + } else { + lp_do_parameter(SNUM(handle->conn), + "veto files", + "/" ADOUBLE_NAME_PREFIX "*/"); + } + + TALLOC_FREE(list); + } + + if (config->encoding == FRUIT_ENC_NATIVE) { + lp_do_parameter(SNUM(handle->conn), + "catia:mappings", + macos_string_replace_map); + } + + if (config->time_machine) { + DBG_NOTICE("Enabling durable handles for Time Machine " + "support on [%s]\n", service); + lp_do_parameter(SNUM(handle->conn), "durable handles", "yes"); + lp_do_parameter(SNUM(handle->conn), "kernel oplocks", "no"); + lp_do_parameter(SNUM(handle->conn), "kernel share modes", "no"); + if (!lp_strict_sync(SNUM(handle->conn))) { + DBG_WARNING("Time Machine without strict sync is not " + "recommended!\n"); + } + lp_do_parameter(SNUM(handle->conn), "posix locking", "no"); + } + + return rc; +} + +static void fio_ref_destroy_fn(void *p_data) +{ + struct fio *ref_fio = (struct fio *)p_data; + if (ref_fio->real_fio != NULL) { + SMB_ASSERT(ref_fio->real_fio->ad_fsp == ref_fio->fsp); + ref_fio->real_fio->ad_fsp = NULL; + ref_fio->real_fio = NULL; + } +} + +static void fio_close_ad_fsp(struct fio *fio) +{ + if (fio->ad_fsp != NULL) { + fd_close(fio->ad_fsp); + file_free(NULL, fio->ad_fsp); + /* fio_ref_destroy_fn() should have cleared this */ + SMB_ASSERT(fio->ad_fsp == NULL); + } +} + +static void fio_destroy_fn(void *p_data) +{ + struct fio *fio = (struct fio *)p_data; + fio_close_ad_fsp(fio); +} + +static int fruit_open_meta_stream(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + struct vfs_open_how how = { + .flags = flags & ~O_CREAT, + .mode = mode, + }; + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_META; + fio->config = config; + + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + if (fd != -1) { + return fd; + } + + if (!(flags & O_CREAT)) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return -1; + } + + fd = vfs_fake_fd(); + if (fd == -1) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return -1; + } + + fio->fake_fd = true; + fio->flags = flags; + fio->mode = mode; + + return fd; +} + +static int fruit_open_meta_netatalk(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + struct adouble *ad = NULL; + bool meta_exists = false; + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + /* + * We know this is a stream open, so fsp->base_fsp must + * already be open. + */ + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + SMB_ASSERT(fsp->base_fsp->fsp_name->fsp == fsp->base_fsp); + + ad = ad_get(talloc_tos(), handle, fsp->base_fsp->fsp_name, ADOUBLE_META); + if (ad != NULL) { + meta_exists = true; + } + + TALLOC_FREE(ad); + + if (!meta_exists && !(flags & O_CREAT)) { + errno = ENOENT; + return -1; + } + + fd = vfs_fake_fd(); + if (fd == -1) { + return -1; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_META; + fio->config = config; + fio->fake_fd = true; + fio->flags = flags; + fio->mode = mode; + + return fd; +} + +static int fruit_open_meta(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, int flags, mode_t mode) +{ + int fd; + struct fruit_config_data *config = NULL; + + DBG_DEBUG("path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + fd = fruit_open_meta_stream(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + case FRUIT_META_NETATALK: + fd = fruit_open_meta_netatalk(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + DBG_DEBUG("path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + return fd; +} + +static int fruit_open_rsrc_adouble(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + int rc = 0; + struct fruit_config_data *config = NULL; + struct files_struct *ad_fsp = NULL; + struct fio *fio = NULL; + struct fio *ref_fio = NULL; + NTSTATUS status; + int fd = -1; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if ((!(flags & O_CREAT)) && + S_ISDIR(fsp->base_fsp->fsp_name->st.st_ex_mode)) + { + /* sorry, but directories don't have a resource fork */ + errno = ENOENT; + rc = -1; + goto exit; + } + + /* + * We return a fake_fd to the vfs modules above, + * while we open an internal backend fsp for the + * '._' file for the next vfs modules. + * + * Note that adouble_open_from_base_fsp() recurses + * into fruit_openat(), but it'll just pass to + * the next module as just opens a flat file on + * disk. + */ + + fd = vfs_fake_fd(); + if (fd == -1) { + rc = fd; + goto exit; + } + + status = adouble_open_from_base_fsp(fsp->conn->cwd_fsp, + fsp->base_fsp, + ADOUBLE_RSRC, + flags, + mode, + &ad_fsp); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + rc = -1; + goto exit; + } + + /* + * Now we need to glue both handles together, + * so that they automatically detach each other + * on close. + */ + fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + DBG_ERR("fio=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + rc = -1; + goto exit; + } + + ref_fio = VFS_ADD_FSP_EXTENSION(handle, ad_fsp, + struct fio, + fio_ref_destroy_fn); + if (ref_fio == NULL) { + int saved_errno = errno; + fd_close(ad_fsp); + file_free(NULL, ad_fsp); + ad_fsp = NULL; + errno = saved_errno; + rc = -1; + goto exit; + } + + SMB_ASSERT(ref_fio->fsp == NULL); + ref_fio->handle = handle; + ref_fio->fsp = ad_fsp; + ref_fio->type = ADOUBLE_RSRC; + ref_fio->config = config; + ref_fio->real_fio = fio; + SMB_ASSERT(fio->ad_fsp == NULL); + fio->ad_fsp = ad_fsp; + fio->fake_fd = true; + +exit: + + DEBUG(10, ("fruit_open resource fork: rc=%d\n", rc)); + if (rc != 0) { + int saved_errno = errno; + if (fd != -1) { + vfs_fake_fd_close(fd); + } + errno = saved_errno; + return rc; + } + return fd; +} + +static int fruit_open_rsrc_xattr(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ +#ifdef HAVE_ATTROPEN + int fd = -1; + + /* + * As there's no attropenat() this is only going to work with AT_FDCWD. + */ + SMB_ASSERT(fsp_get_pathref_fd(dirfsp) == AT_FDCWD); + + fd = attropen(smb_fname->base_name, + AFPRESOURCE_EA_NETATALK, + flags, + mode); + if (fd == -1) { + return -1; + } + + return fd; + +#else + errno = ENOSYS; + return -1; +#endif +} + +static int fruit_open_rsrc(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, int flags, mode_t mode) +{ + int fd; + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_RSRC; + fio->config = config; + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: { + struct vfs_open_how how = { + .flags = flags, .mode = mode, + }; + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + break; + } + + case FRUIT_RSRC_ADFILE: + fd = fruit_open_rsrc_adouble(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + case FRUIT_RSRC_XATTR: + fd = fruit_open_rsrc_xattr(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + errno = EINVAL; + return -1; + } + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + if (fd == -1) { + return -1; + } + + return fd; +} + +static int fruit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + + if (is_afpinfo_stream(smb_fname->stream_name)) { + fd = fruit_open_meta(handle, + dirfsp, + smb_fname, + fsp, + how->flags, + how->mode); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + fd = fruit_open_rsrc(handle, + dirfsp, + smb_fname, + fsp, + how->flags, + how->mode); + } else { + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + /* Prevent reopen optimisation */ + fsp->fsp_flags.have_proc_fds = false; + return fd; +} + +static int fruit_close_meta(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + { + struct fio *fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + return -1; + } + if (fio->fake_fd) { + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + } else { + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + } + break; + } + case FRUIT_META_NETATALK: + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + return ret; +} + + +static int fruit_close_rsrc(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + break; + + case FRUIT_RSRC_ADFILE: + { + struct fio *fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + return -1; + } + fio_close_ad_fsp(fio); + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + } + + case FRUIT_RSRC_XATTR: + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_close(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + int fd; + + fd = fsp_get_pathref_fd(fsp); + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(fsp->fsp_name), fd); + + if (!fsp_is_alternate_stream(fsp)) { + return SMB_VFS_NEXT_CLOSE(handle, fsp); + } + + if (is_afpinfo_stream(fsp->fsp_name->stream_name)) { + ret = fruit_close_meta(handle, fsp); + } else if (is_afpresource_stream(fsp->fsp_name->stream_name)) { + ret = fruit_close_rsrc(handle, fsp); + } else { + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + } + + return ret; +} + +static int fruit_renameat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int rc = -1; + struct fruit_config_data *config = NULL; + struct smb_filename *src_adp_smb_fname = NULL; + struct smb_filename *dst_adp_smb_fname = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (!VALID_STAT(smb_fname_src->st)) { + DBG_ERR("Need valid stat for [%s]\n", + smb_fname_str_dbg(smb_fname_src)); + return -1; + } + + rc = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (rc != 0) { + return -1; + } + + if ((config->rsrc != FRUIT_RSRC_ADFILE) || + (!S_ISREG(smb_fname_src->st.st_ex_mode))) + { + return 0; + } + + rc = adouble_path(talloc_tos(), smb_fname_src, &src_adp_smb_fname); + if (rc != 0) { + goto done; + } + + rc = adouble_path(talloc_tos(), smb_fname_dst, &dst_adp_smb_fname); + if (rc != 0) { + goto done; + } + + DBG_DEBUG("%s -> %s\n", + smb_fname_str_dbg(src_adp_smb_fname), + smb_fname_str_dbg(dst_adp_smb_fname)); + + rc = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + src_adp_smb_fname, + dstfsp, + dst_adp_smb_fname); + if (errno == ENOENT) { + rc = 0; + } + +done: + TALLOC_FREE(src_adp_smb_fname); + TALLOC_FREE(dst_adp_smb_fname); + return rc; +} + +static int fruit_unlink_meta_stream(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); +} + +static int fruit_unlink_meta_netatalk(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + SMB_ASSERT(smb_fname->fsp != NULL); + SMB_ASSERT(fsp_is_alternate_stream(smb_fname->fsp)); + return SMB_VFS_FREMOVEXATTR(smb_fname->fsp->base_fsp, + AFPINFO_EA_NETATALK); +} + +static int fruit_unlink_meta(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + struct fruit_config_data *config = NULL; + int rc; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + rc = fruit_unlink_meta_stream(handle, + dirfsp, + smb_fname); + break; + + case FRUIT_META_NETATALK: + rc = fruit_unlink_meta_netatalk(handle, smb_fname); + break; + + default: + DBG_ERR("Unsupported meta config [%d]\n", config->meta); + return -1; + } + + return rc; +} + +static int fruit_unlink_rsrc_stream(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + int ret; + + if (!force_unlink) { + struct smb_filename *full_fname = NULL; + off_t size; + + /* + * TODO: use SMB_VFS_STATX() once we have it. + */ + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + /* + * 0 byte resource fork streams are not listed by + * vfs_streaminfo, as a result stream cleanup/deletion of file + * deletion doesn't remove the resourcefork stream. + */ + + ret = SMB_VFS_NEXT_STAT(handle, full_fname); + if (ret != 0) { + TALLOC_FREE(full_fname); + DBG_ERR("stat [%s] failed [%s]\n", + smb_fname_str_dbg(full_fname), strerror(errno)); + return -1; + } + + size = full_fname->st.st_ex_size; + TALLOC_FREE(full_fname); + + if (size > 0) { + /* OS X ignores resource fork stream delete requests */ + return 0; + } + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + if ((ret != 0) && (errno == ENOENT) && force_unlink) { + ret = 0; + } + + return ret; +} + +static int fruit_unlink_rsrc_adouble(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + int rc; + struct adouble *ad = NULL; + struct smb_filename *adp_smb_fname = NULL; + + if (!force_unlink) { + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + ad = ad_get(talloc_tos(), handle, full_fname, + ADOUBLE_RSRC); + TALLOC_FREE(full_fname); + if (ad == NULL) { + errno = ENOENT; + return -1; + } + + + /* + * 0 byte resource fork streams are not listed by + * vfs_streaminfo, as a result stream cleanup/deletion of file + * deletion doesn't remove the resourcefork stream. + */ + + if (ad_getentrylen(ad, ADEID_RFORK) > 0) { + /* OS X ignores resource fork stream delete requests */ + TALLOC_FREE(ad); + return 0; + } + + TALLOC_FREE(ad); + } + + rc = adouble_path(talloc_tos(), smb_fname, &adp_smb_fname); + if (rc != 0) { + return -1; + } + + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + adp_smb_fname, + 0); + TALLOC_FREE(adp_smb_fname); + if ((rc != 0) && (errno == ENOENT || errno == ENAMETOOLONG) && force_unlink) { + rc = 0; + } + + return rc; +} + +static int fruit_unlink_rsrc_xattr(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + /* + * OS X ignores resource fork stream delete requests, so nothing to do + * here. Removing the file will remove the xattr anyway, so we don't + * have to take care of removing 0 byte resource forks that could be + * left behind. + */ + return 0; +} + +static int fruit_unlink_rsrc(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + struct fruit_config_data *config = NULL; + int rc; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + rc = fruit_unlink_rsrc_stream(handle, + dirfsp, + smb_fname, + force_unlink); + break; + + case FRUIT_RSRC_ADFILE: + rc = fruit_unlink_rsrc_adouble(handle, + dirfsp, + smb_fname, + force_unlink); + break; + + case FRUIT_RSRC_XATTR: + rc = fruit_unlink_rsrc_xattr(handle, smb_fname, force_unlink); + break; + + default: + DBG_ERR("Unsupported rsrc config [%d]\n", config->rsrc); + return -1; + } + + return rc; +} + +static int fruit_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + int rc = -1; + struct fruit_config_data *config = NULL; + struct smb_filename *smb_fname_adp = NULL; + const struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + rc = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + if (rc != 0) { + return rc; + } + + smb_fname = fsp->fsp_name; + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (config->rsrc != FRUIT_RSRC_ADFILE) { + return 0; + } + + if (!VALID_STAT(smb_fname->st)) { + return 0; + } + + if (!S_ISREG(smb_fname->st.st_ex_mode)) { + return 0; + } + + rc = adouble_path(talloc_tos(), smb_fname, &smb_fname_adp); + if (rc != 0) { + return -1; + } + + status = openat_pathref_fsp(handle->conn->cwd_fsp, + smb_fname_adp); + if (!NT_STATUS_IS_OK(status)) { + /* detect ENOENT (mapped to OBJECT_NAME_NOT_FOUND) */ + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)){ + rc = 0; + goto out; + } + rc = -1; + goto out; + } + + DBG_DEBUG("%s\n", smb_fname_adp->base_name); + + rc = SMB_VFS_NEXT_FCHMOD(handle, smb_fname_adp->fsp, mode); + if (errno == ENOENT) { + rc = 0; + } +out: + TALLOC_FREE(smb_fname_adp); + return rc; +} + +static int fruit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct fruit_config_data *config = NULL; + struct smb_filename *rsrc_smb_fname = NULL; + int ret; + + if (flags & AT_REMOVEDIR) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (is_afpinfo_stream(smb_fname->stream_name)) { + return fruit_unlink_meta(handle, + dirfsp, + smb_fname); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + return fruit_unlink_rsrc(handle, + dirfsp, + smb_fname, + false); + } else if (is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + } else if (is_adouble_file(smb_fname->base_name)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + } + + /* + * A request to delete the base file. Because 0 byte resource + * fork streams are not listed by fruit_streaminfo, + * delete_all_streams() can't remove 0 byte resource fork + * streams, so we have to cleanup this here. + */ + rsrc_smb_fname = synthetic_smb_fname(talloc_tos(), + smb_fname->base_name, + AFPRESOURCE_STREAM_NAME, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (rsrc_smb_fname == NULL) { + return -1; + } + + ret = fruit_unlink_rsrc(handle, dirfsp, rsrc_smb_fname, true); + if ((ret != 0) && (errno != ENOENT)) { + DBG_ERR("Forced unlink of [%s] failed [%s]\n", + smb_fname_str_dbg(rsrc_smb_fname), strerror(errno)); + TALLOC_FREE(rsrc_smb_fname); + return -1; + } + TALLOC_FREE(rsrc_smb_fname); + + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); +} + +static ssize_t fruit_pread_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + int ret; + + if ((fio == NULL) || fio->fake_fd) { + return -1; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + if (nread == -1 || nread == n) { + return nread; + } + + DBG_ERR("Removing [%s] after short read [%zd]\n", + fsp_str_dbg(fsp), nread); + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + fsp->conn->cwd_fsp, + fsp->fsp_name, + 0); + if (ret != 0) { + DBG_ERR("Removing [%s] failed\n", fsp_str_dbg(fsp)); + return -1; + } + + errno = EINVAL; + return -1; +} + +static ssize_t fruit_pread_meta_adouble(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + AfpInfo *ai = NULL; + struct adouble *ad = NULL; + char afpinfo_buf[AFP_INFO_SIZE]; + char *p = NULL; + ssize_t nread; + + ai = afpinfo_new(talloc_tos()); + if (ai == NULL) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + nread = -1; + goto fail; + } + + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp)); + nread = -1; + goto fail; + } + + memcpy(&ai->afpi_FinderInfo[0], p, ADEDLEN_FINDERI); + + nread = afpinfo_pack(ai, afpinfo_buf); + if (nread != AFP_INFO_SIZE) { + nread = -1; + goto fail; + } + + memcpy(data, afpinfo_buf, n); + nread = n; + +fail: + TALLOC_FREE(ai); + return nread; +} + +static ssize_t fruit_pread_meta(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + ssize_t to_return; + + /* + * OS X has a off-by-1 error in the offset calculation, so we're + * bug compatible here. It won't hurt, as any relevant real + * world read requests from the AFP_AfpInfo stream will be + * offset=0 n=60. offset is ignored anyway, see below. + */ + if ((offset < 0) || (offset >= AFP_INFO_SIZE + 1)) { + return 0; + } + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + /* Yes, macOS always reads from offset 0 */ + offset = 0; + to_return = MIN(n, AFP_INFO_SIZE); + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + nread = fruit_pread_meta_stream(handle, fsp, data, + to_return, offset); + break; + + case FRUIT_META_NETATALK: + nread = fruit_pread_meta_adouble(handle, fsp, data, + to_return, offset); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + if (nread == -1 && fio->fake_fd) { + AfpInfo *ai = NULL; + char afpinfo_buf[AFP_INFO_SIZE]; + + ai = afpinfo_new(talloc_tos()); + if (ai == NULL) { + return -1; + } + + nread = afpinfo_pack(ai, afpinfo_buf); + TALLOC_FREE(ai); + if (nread != AFP_INFO_SIZE) { + return -1; + } + + memcpy(data, afpinfo_buf, to_return); + return to_return; + } + + return nread; +} + +static ssize_t fruit_pread_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pread_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pread_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + ssize_t nread; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fio->ad_fsp, data, n, + offset + ad_getentryoff(ad, ADEID_RFORK)); + + TALLOC_FREE(ad); + return nread; +} + +static ssize_t fruit_pread_rsrc(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + + if (fio == NULL) { + errno = EINVAL; + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + nread = fruit_pread_rsrc_stream(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_ADFILE: + nread = fruit_pread_rsrc_adouble(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_XATTR: + nread = fruit_pread_rsrc_xattr(handle, fsp, data, n, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return nread; +} + +static ssize_t fruit_pread(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + } + + if (fio->type == ADOUBLE_META) { + nread = fruit_pread_meta(handle, fsp, data, n, offset); + } else { + nread = fruit_pread_rsrc(handle, fsp, data, n, offset); + } + + DBG_DEBUG("Path [%s] nread [%zd]\n", fsp_str_dbg(fsp), nread); + return nread; +} + +static bool fruit_must_handle_aio_stream(struct fio *fio) +{ + if (fio == NULL) { + return false; + }; + + if (fio->type == ADOUBLE_META) { + return true; + } + + if ((fio->type == ADOUBLE_RSRC) && + (fio->config->rsrc == FRUIT_RSRC_ADFILE)) + { + return true; + } + + return false; +} + +struct fruit_pread_state { + ssize_t nread; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_pread_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_pread_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_pread_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_pread_state); + if (req == NULL) { + return NULL; + } + + if (fruit_must_handle_aio_stream(fio)) { + state->nread = SMB_VFS_PREAD(fsp, data, n, offset); + if (state->nread != n) { + if (state->nread != -1) { + errno = EIO; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_pread_done, req); + return req; +} + +static void fruit_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_pread_state *state = tevent_req_data( + req, struct fruit_pread_state); + + state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t fruit_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_pread_state *state = tevent_req_data( + req, struct fruit_pread_state); + ssize_t retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->nread; + tevent_req_received(req); + return retval; +} + +static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, const void *indata, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + const void *data = indata; + char afpinfo_buf[AFP_INFO_SIZE]; + AfpInfo *ai = NULL; + size_t nwritten; + int ret; + bool ok; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return -1; + } + + if (fio->fake_fd) { + struct vfs_open_how how = { + .flags = fio->flags, .mode = fio->mode, + }; + int fd = fsp_get_pathref_fd(fsp); + + ret = vfs_fake_fd_close(fd); + fsp_set_fd(fsp, -1); + if (ret != 0) { + DBG_ERR("Close [%s] failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return -1; + } + + fd = SMB_VFS_NEXT_OPENAT(handle, + NULL, /* opening a stream */ + fsp->fsp_name, + fsp, + &how); + if (fd == -1) { + DBG_ERR("On-demand create [%s] in write failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return -1; + } + fsp_set_fd(fsp, fd); + fio->fake_fd = false; + } + + ai = afpinfo_unpack(talloc_tos(), data, fio->config->validate_afpinfo); + if (ai == NULL) { + return -1; + } + + if (ai_empty_finderinfo(ai)) { + /* + * Writing an all 0 blob to the metadata stream results in the + * stream being removed on a macOS server. This ensures we + * behave the same and it verified by the "delete AFP_AfpInfo by + * writing all 0" test. + */ + ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, 0); + if (ret != 0) { + DBG_ERR("SMB_VFS_NEXT_FTRUNCATE on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + + ok = set_delete_on_close( + fsp, + true, + handle->conn->session_info->security_token, + handle->conn->session_info->unix_token); + if (!ok) { + DBG_ERR("set_delete_on_close on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + return n; + } + + if (!fio->config->validate_afpinfo) { + /* + * Ensure the buffer contains a valid header, so marshall + * the data from the afpinfo struck back into a buffer + * and write that instead of the possibly malformed data + * we got from the client. + */ + nwritten = afpinfo_pack(ai, afpinfo_buf); + if (nwritten != AFP_INFO_SIZE) { + errno = EINVAL; + return -1; + } + data = afpinfo_buf; + } + + nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + if (nwritten != n) { + return -1; + } + + return n; +} + +static ssize_t fruit_pwrite_meta_netatalk(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fruit_config_data *config = NULL; + struct adouble *ad = NULL; + AfpInfo *ai = NULL; + char *p = NULL; + int ret; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + ai = afpinfo_unpack(talloc_tos(), data, config->validate_afpinfo); + if (ai == NULL) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + ad = ad_init(talloc_tos(), ADOUBLE_META); + if (ad == NULL) { + return -1; + } + } + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp)); + TALLOC_FREE(ad); + return -1; + } + + memcpy(p, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI); + + ret = ad_fset(handle, ad, fsp); + if (ret != 0) { + DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fsp)); + TALLOC_FREE(ad); + return -1; + } + + TALLOC_FREE(ad); + + if (!ai_empty_finderinfo(ai)) { + return n; + } + + /* + * Writing an all 0 blob to the metadata stream results in the stream + * being removed on a macOS server. This ensures we behave the same and + * it verified by the "delete AFP_AfpInfo by writing all 0" test. + */ + + ok = set_delete_on_close( + fsp, + true, + handle->conn->session_info->security_token, + handle->conn->session_info->unix_token); + if (!ok) { + DBG_ERR("set_delete_on_close on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + + return n; +} + +static ssize_t fruit_pwrite_meta(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + uint8_t buf[AFP_INFO_SIZE]; + size_t to_write; + size_t to_copy; + int cmp; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + if (n < 3) { + errno = EINVAL; + return -1; + } + + if (offset != 0 && n < 60) { + errno = EINVAL; + return -1; + } + + if (fio->config->validate_afpinfo) { + cmp = memcmp(data, "AFP", 3); + if (cmp != 0) { + errno = EINVAL; + return -1; + } + } + + if (n <= AFP_OFF_FinderInfo) { + /* + * Nothing to do here really, just return + */ + return n; + } + + offset = 0; + + to_copy = n; + if (to_copy > AFP_INFO_SIZE) { + to_copy = AFP_INFO_SIZE; + } + memcpy(buf, data, to_copy); + + to_write = n; + if (to_write != AFP_INFO_SIZE) { + to_write = AFP_INFO_SIZE; + } + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + nwritten = fruit_pwrite_meta_stream(handle, + fsp, + buf, + to_write, + offset); + break; + + case FRUIT_META_NETATALK: + nwritten = fruit_pwrite_meta_netatalk(handle, + fsp, + buf, + to_write, + offset); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + if (nwritten != to_write) { + return -1; + } + + /* + * Return the requested amount, verified against macOS SMB server + */ + return n; +} + +static ssize_t fruit_pwrite_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pwrite_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pwrite_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + ssize_t nwritten; + int ret; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + nwritten = SMB_VFS_NEXT_PWRITE(handle, fio->ad_fsp, data, n, + offset + ad_getentryoff(ad, ADEID_RFORK)); + if (nwritten != n) { + DBG_ERR("Short write on [%s] [%zd/%zd]\n", + fsp_str_dbg(fio->ad_fsp), nwritten, n); + TALLOC_FREE(ad); + return -1; + } + + if ((n + offset) > ad_getentrylen(ad, ADEID_RFORK)) { + ad_setentrylen(ad, ADEID_RFORK, n + offset); + ret = ad_fset(handle, ad, fio->ad_fsp); + if (ret != 0) { + DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fio->ad_fsp)); + TALLOC_FREE(ad); + return -1; + } + } + + TALLOC_FREE(ad); + return n; +} + +static ssize_t fruit_pwrite_rsrc(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + nwritten = fruit_pwrite_rsrc_stream(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_ADFILE: + nwritten = fruit_pwrite_rsrc_adouble(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_XATTR: + nwritten = fruit_pwrite_rsrc_xattr(handle, fsp, data, n, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return nwritten; +} + +static ssize_t fruit_pwrite(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + } + + if (fio->type == ADOUBLE_META) { + nwritten = fruit_pwrite_meta(handle, fsp, data, n, offset); + } else { + nwritten = fruit_pwrite_rsrc(handle, fsp, data, n, offset); + } + + DBG_DEBUG("Path [%s] nwritten=%zd\n", fsp_str_dbg(fsp), nwritten); + return nwritten; +} + +struct fruit_pwrite_state { + ssize_t nwritten; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_pwrite_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_pwrite_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_pwrite_state); + if (req == NULL) { + return NULL; + } + + if (fruit_must_handle_aio_stream(fio)) { + state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset); + if (state->nwritten != n) { + if (state->nwritten != -1) { + errno = EIO; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_pwrite_done, req); + return req; +} + +static void fruit_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_pwrite_state *state = tevent_req_data( + req, struct fruit_pwrite_state); + + state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t fruit_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_pwrite_state *state = tevent_req_data( + req, struct fruit_pwrite_state); + ssize_t retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->nwritten; + tevent_req_received(req); + return retval; +} + +struct fruit_fsync_state { + int ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_fsync_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_fsync_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_fsync_state); + if (req == NULL) { + return NULL; + } + + if (fruit_must_handle_aio_stream(fio)) { + struct adouble *ad = NULL; + + if (fio->type == ADOUBLE_META) { + /* + * We must never pass a fake_fd + * to lower level fsync calls. + * Everything is already done + * synchronously, so just return + * true. + */ + SMB_ASSERT(fio->fake_fd); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* + * We know the following must be true, + * as it's the condition for fruit_must_handle_aio_stream() + * to return true if fio->type == ADOUBLE_RSRC. + */ + SMB_ASSERT(fio->config->rsrc == FRUIT_RSRC_ADFILE); + if (fio->ad_fsp == NULL) { + tevent_req_error(req, EBADF); + return tevent_req_post(req, ev); + } + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + fsp = fio->ad_fsp; + } + + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_fsync_done, req); + return req; +} + +static void fruit_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_fsync_state *state = tevent_req_data( + req, struct fruit_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + if (state->ret != 0) { + tevent_req_error(req, errno); + return; + } + tevent_req_done(req); +} + +static int fruit_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_fsync_state *state = tevent_req_data( + req, struct fruit_fsync_state); + int retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->ret; + tevent_req_received(req); + return retval; +} + +/** + * Helper to stat/lstat the base file of an smb_fname. + */ +static int fruit_stat_base(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + char *tmp_stream_name; + int rc; + + tmp_stream_name = smb_fname->stream_name; + smb_fname->stream_name = NULL; + if (follow_links) { + rc = SMB_VFS_NEXT_STAT(handle, smb_fname); + } else { + rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + smb_fname->stream_name = tmp_stream_name; + + DBG_DEBUG("fruit_stat_base [%s] dev [%ju] ino [%ju]\n", + smb_fname->base_name, + (uintmax_t)smb_fname->st.st_ex_dev, + (uintmax_t)smb_fname->st.st_ex_ino); + return rc; +} + +static int fruit_stat_meta_stream(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + int ret; + ino_t ino; + + ret = fruit_stat_base(handle, smb_fname, false); + if (ret != 0) { + return -1; + } + + ino = hash_inode(&smb_fname->st, smb_fname->stream_name); + + if (follow_links) { + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + } else { + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + smb_fname->st.st_ex_ino = ino; + + return ret; +} + +static int fruit_stat_meta_netatalk(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct adouble *ad = NULL; + + /* Populate the stat struct with info from the base file. */ + if (fruit_stat_base(handle, smb_fname, follow_links) == -1) { + return -1; + } + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + DBG_INFO("fruit_stat_meta %s: %s\n", + smb_fname_str_dbg(smb_fname), strerror(errno)); + errno = ENOENT; + return -1; + } + TALLOC_FREE(ad); + + smb_fname->st.st_ex_size = AFP_INFO_SIZE; + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, + smb_fname->stream_name); + return 0; +} + +static int fruit_stat_meta(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct fruit_config_data *config = NULL; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + ret = fruit_stat_meta_stream(handle, smb_fname, follow_links); + break; + + case FRUIT_META_NETATALK: + ret = fruit_stat_meta_netatalk(handle, smb_fname, follow_links); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + return ret; +} + +static int fruit_stat_rsrc_netatalk(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct adouble *ad = NULL; + int ret; + + ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC); + if (ad == NULL) { + errno = ENOENT; + return -1; + } + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, smb_fname, follow_links); + if (ret != 0) { + TALLOC_FREE(ad); + return -1; + } + + smb_fname->st.st_ex_size = ad_getentrylen(ad, ADEID_RFORK); + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, + smb_fname->stream_name); + TALLOC_FREE(ad); + return 0; +} + +static int fruit_stat_rsrc_stream(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + int ret; + + if (follow_links) { + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + } else { + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + return ret; +} + +static int fruit_stat_rsrc_xattr(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ +#ifdef HAVE_ATTROPEN + int ret; + int fd = -1; + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, smb_fname, follow_links); + if (ret != 0) { + return -1; + } + + fd = attropen(smb_fname->base_name, + AFPRESOURCE_EA_NETATALK, + O_RDONLY); + if (fd == -1) { + return 0; + } + + ret = sys_fstat(fd, &smb_fname->st, false); + if (ret != 0) { + close(fd); + DBG_ERR("fstat [%s:%s] failed\n", smb_fname->base_name, + AFPRESOURCE_EA_NETATALK); + return -1; + } + close(fd); + fd = -1; + + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, + smb_fname->stream_name); + + return ret; + +#else + errno = ENOSYS; + return -1; +#endif +} + +static int fruit_stat_rsrc(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct fruit_config_data *config = NULL; + int ret; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = fruit_stat_rsrc_stream(handle, smb_fname, follow_links); + break; + + case FRUIT_RSRC_XATTR: + ret = fruit_stat_rsrc_xattr(handle, smb_fname, follow_links); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_stat_rsrc_netatalk(handle, smb_fname, follow_links); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int rc = -1; + + DEBUG(10, ("fruit_stat called for %s\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + rc = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (rc == 0) { + update_btime(handle, smb_fname); + } + return rc; + } + + /* + * Note if lp_posix_paths() is true, we can never + * get here as is_ntfs_stream_smb_fname() is + * always false. So we never need worry about + * not following links here. + */ + + if (is_afpinfo_stream(smb_fname->stream_name)) { + rc = fruit_stat_meta(handle, smb_fname, true); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + rc = fruit_stat_rsrc(handle, smb_fname, true); + } else { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + if (rc == 0) { + update_btime(handle, smb_fname); + smb_fname->st.st_ex_mode &= ~S_IFMT; + smb_fname->st.st_ex_mode |= S_IFREG; + smb_fname->st.st_ex_blocks = + smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + return rc; +} + +static int fruit_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int rc = -1; + + DEBUG(10, ("fruit_lstat called for %s\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (rc == 0) { + update_btime(handle, smb_fname); + } + return rc; + } + + if (is_afpinfo_stream(smb_fname->stream_name)) { + rc = fruit_stat_meta(handle, smb_fname, false); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + rc = fruit_stat_rsrc(handle, smb_fname, false); + } else { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + if (rc == 0) { + update_btime(handle, smb_fname); + smb_fname->st.st_ex_mode &= ~S_IFMT; + smb_fname->st.st_ex_mode |= S_IFREG; + smb_fname->st.st_ex_blocks = + smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + return rc; +} + +static int fruit_fstat_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct smb_filename smb_fname; + ino_t ino; + int ret; + + if (fio == NULL) { + return -1; + } + + if (fio->fake_fd) { + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret != 0) { + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = AFP_INFO_SIZE; + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + return 0; + } + + smb_fname = (struct smb_filename) { + .base_name = fsp->fsp_name->base_name, + .twrp = fsp->fsp_name->twrp, + }; + + ret = fruit_stat_base(handle, &smb_fname, false); + if (ret != 0) { + return -1; + } + *sbuf = smb_fname.st; + + ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret != 0) { + return -1; + } + + sbuf->st_ex_ino = ino; + return 0; +} + +static int fruit_fstat_meta_netatalk(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int ret; + + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret != 0) { + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = AFP_INFO_SIZE; + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + return 0; +} + +static int fruit_fstat_meta(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf, + struct fio *fio) +{ + int ret; + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + ret = fruit_fstat_meta_stream(handle, fsp, sbuf); + break; + + case FRUIT_META_NETATALK: + ret = fruit_fstat_meta_netatalk(handle, fsp, sbuf); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + DBG_DEBUG("Path [%s] ret [%d]\n", fsp_str_dbg(fsp), ret); + return ret; +} + +static int fruit_fstat_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); +} + +static int fruit_fstat_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); +} + +static int fruit_fstat_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + int ret; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret == -1) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = ad_getentrylen(ad, ADEID_RFORK); + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + TALLOC_FREE(ad); + return 0; +} + +static int fruit_fstat_rsrc(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf, struct fio *fio) +{ + int ret; + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = fruit_fstat_rsrc_stream(handle, fsp, sbuf); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_fstat_rsrc_adouble(handle, fsp, sbuf); + break; + + case FRUIT_RSRC_XATTR: + ret = fruit_fstat_rsrc_xattr(handle, fsp, sbuf); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int rc; + + if (fio == NULL) { + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + } + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + if (fio->type == ADOUBLE_META) { + rc = fruit_fstat_meta(handle, fsp, sbuf, fio); + } else { + rc = fruit_fstat_rsrc(handle, fsp, sbuf, fio); + } + + if (rc == 0) { + sbuf->st_ex_mode &= ~S_IFMT; + sbuf->st_ex_mode |= S_IFREG; + sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + + DBG_DEBUG("Path [%s] rc [%d] size [%"PRIdMAX"]\n", + fsp_str_dbg(fsp), rc, (intmax_t)sbuf->st_ex_size); + return rc; +} + +static NTSTATUS delete_invalid_meta_stream( + vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams, + off_t size) +{ + struct smb_filename *sname = NULL; + NTSTATUS status; + int ret; + bool ok; + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, AFPINFO_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (size == 0) { + return NT_STATUS_OK; + } + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + AFPINFO_STREAM_NAME, + NULL, + smb_fname->twrp, + 0, + &sname); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + handle->conn->cwd_fsp, + sname, + 0); + if (ret != 0) { + DBG_ERR("Removing [%s] failed\n", smb_fname_str_dbg(sname)); + TALLOC_FREE(sname); + return map_nt_error_from_unix(errno); + } + + TALLOC_FREE(sname); + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta_stream( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + int i; + + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPINFO_STREAM)) { + break; + } + } + + if (i == num_streams) { + return NT_STATUS_OK; + } + + if (stream[i].size != AFP_INFO_SIZE) { + DBG_ERR("Removing invalid AFPINFO_STREAM size [%jd] from [%s]\n", + (intmax_t)stream[i].size, smb_fname_str_dbg(smb_fname)); + + return delete_invalid_meta_stream(handle, + smb_fname, + mem_ctx, + pnum_streams, + pstreams, + stream[i].size); + } + + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta_netatalk( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + struct adouble *ad = NULL; + bool is_fi_empty; + int i; + bool ok; + + /* Remove the Netatalk xattr from the list */ + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + ":" NETATALK_META_XATTR ":$DATA"); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Check if there's a AFPINFO_STREAM from the VFS streams + * backend and if yes, remove it from the list + */ + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPINFO_STREAM)) { + break; + } + } + + if (i < num_streams) { + DBG_WARNING("Unexpected AFPINFO_STREAM on [%s]\n", + smb_fname_str_dbg(smb_fname)); + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPINFO_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + return NT_STATUS_OK; + } + + is_fi_empty = ad_empty_finderinfo(ad); + TALLOC_FREE(ad); + + if (is_fi_empty) { + return NT_STATUS_OK; + } + + ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPINFO_STREAM_NAME, AFP_INFO_SIZE, + smb_roundup(handle->conn, AFP_INFO_SIZE)); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta(vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_INTERNAL_ERROR); + + switch (config->meta) { + case FRUIT_META_NETATALK: + status = fruit_streaminfo_meta_netatalk(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_META_STREAM: + status = fruit_streaminfo_meta_stream(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + return status; +} + +static NTSTATUS fruit_streaminfo_rsrc_stream( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + bool ok; + + ok = filter_empty_rsrc_stream(pnum_streams, pstreams); + if (!ok) { + DBG_ERR("Filtering resource stream failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc_xattr( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + bool ok; + + ok = filter_empty_rsrc_stream(pnum_streams, pstreams); + if (!ok) { + DBG_ERR("Filtering resource stream failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc_adouble( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + struct adouble *ad = NULL; + bool ok; + size_t rlen; + int i; + + /* + * Check if there's a AFPRESOURCE_STREAM from the VFS streams backend + * and if yes, remove it from the list + */ + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPRESOURCE_STREAM)) { + break; + } + } + + if (i < num_streams) { + DBG_WARNING("Unexpected AFPRESOURCE_STREAM on [%s]\n", + smb_fname_str_dbg(smb_fname)); + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPRESOURCE_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC); + if (ad == NULL) { + return NT_STATUS_OK; + } + + rlen = ad_getentrylen(ad, ADEID_RFORK); + TALLOC_FREE(ad); + + if (rlen == 0) { + return NT_STATUS_OK; + } + + ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPRESOURCE_STREAM_NAME, rlen, + smb_roundup(handle->conn, rlen)); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc(vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + NTSTATUS status; + + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + return NT_STATUS_OK; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_INTERNAL_ERROR); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + status = fruit_streaminfo_rsrc_stream(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_RSRC_XATTR: + status = fruit_streaminfo_rsrc_xattr(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_RSRC_ADFILE: + status = fruit_streaminfo_rsrc_adouble(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + return status; +} + +static void fruit_filter_empty_streams(unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + unsigned num_streams = *pnum_streams; + struct stream_struct *streams = *pstreams; + unsigned i = 0; + + if (!global_fruit_config.nego_aapl) { + return; + } + + while (i < num_streams) { + struct smb_filename smb_fname = (struct smb_filename) { + .stream_name = streams[i].name, + }; + + if (is_ntfs_default_stream_smb_fname(&smb_fname) + || streams[i].size > 0) + { + i++; + continue; + } + + streams[i] = streams[num_streams - 1]; + num_streams--; + } + + *pnum_streams = num_streams; +} + +static NTSTATUS fruit_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + const struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + smb_fname = fsp->fsp_name; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + status = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx, + pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + fruit_filter_empty_streams(pnum_streams, pstreams); + + status = fruit_streaminfo_meta(handle, fsp, smb_fname, + mem_ctx, pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = fruit_streaminfo_rsrc(handle, fsp, smb_fname, + mem_ctx, pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static int fruit_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int rc = 0; + struct adouble *ad = NULL; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return -1); + + if ((config->meta != FRUIT_META_NETATALK) || + is_omit_timespec(&ft->create_time)) + { + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + } + + DBG_DEBUG("set btime for %s to %s", fsp_str_dbg(fsp), + time_to_asc(convert_timespec_to_time_t(ft->create_time))); + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + goto exit; + } + + ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, + convert_time_t_to_uint32_t(ft->create_time.tv_sec)); + + rc = ad_fset(handle, ad, fsp); + +exit: + + TALLOC_FREE(ad); + if (rc != 0) { + DBG_WARNING("%s\n", fsp_str_dbg(fsp)); + return -1; + } + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int fruit_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + if (fio == NULL) { + return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + } + + /* Let the pwrite code path handle it. */ + errno = ENOSYS; + return -1; +} + +static int fruit_ftruncate_rsrc_xattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ +#ifdef HAVE_ATTROPEN + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); +#endif + return 0; +} + +static int fruit_ftruncate_rsrc_adouble(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int rc; + struct adouble *ad = NULL; + off_t ad_off; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + ad_off = ad_getentryoff(ad, ADEID_RFORK); + + rc = SMB_VFS_NEXT_FTRUNCATE(handle, fio->ad_fsp, offset + ad_off); + if (rc != 0) { + TALLOC_FREE(ad); + return -1; + } + + ad_setentrylen(ad, ADEID_RFORK, offset); + + rc = ad_fset(handle, ad, fio->ad_fsp); + if (rc != 0) { + DBG_ERR("ad_fset [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + TALLOC_FREE(ad); + return -1; + } + + TALLOC_FREE(ad); + return 0; +} + +static int fruit_ftruncate_rsrc_stream(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); +} + +static int fruit_ftruncate_rsrc(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int ret; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_XATTR: + ret = fruit_ftruncate_rsrc_xattr(handle, fsp, offset); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_ftruncate_rsrc_adouble(handle, fsp, offset); + break; + + case FRUIT_RSRC_STREAM: + ret = fruit_ftruncate_rsrc_stream(handle, fsp, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + + return ret; +} + +static int fruit_ftruncate_meta(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + if (offset > 60) { + DBG_WARNING("ftruncate %s to %jd\n", + fsp_str_dbg(fsp), (intmax_t)offset); + /* OS X returns NT_STATUS_ALLOTTED_SPACE_EXCEEDED */ + errno = EOVERFLOW; + return -1; + } + + /* OS X returns success but does nothing */ + DBG_INFO("ignoring ftruncate %s to %jd\n", + fsp_str_dbg(fsp), (intmax_t)offset); + return 0; +} + +static int fruit_ftruncate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int ret; + + DBG_DEBUG("Path [%s] offset [%"PRIdMAX"]\n", fsp_str_dbg(fsp), + (intmax_t)offset); + + if (fio == NULL) { + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); + } + + if (fio->type == ADOUBLE_META) { + ret = fruit_ftruncate_meta(handle, fsp, offset); + } else { + ret = fruit_ftruncate_rsrc(handle, fsp, offset); + } + + DBG_DEBUG("Path [%s] result [%d]\n", fsp_str_dbg(fsp), ret); + return ret; +} + +static NTSTATUS fruit_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS status; + struct fruit_config_data *config = NULL; + files_struct *fsp = NULL; + bool internal_open = (oplock_request & INTERNAL_OPEN_ONLY); + int ret; + + status = check_aapl(handle, req, in_context_blobs, out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (is_apple_stream(smb_fname->stream_name) && + !internal_open && + config->convert_adouble) + { + uint32_t conv_flags = 0; + + if (config->wipe_intentionally_left_blank_rfork) { + conv_flags |= AD_CONV_WIPE_BLANK; + } + if (config->delete_empty_adfiles) { + conv_flags |= AD_CONV_DELETE; + } + + ret = ad_convert(handle, + smb_fname, + macos_string_replace_map, + conv_flags); + if (ret != 0) { + DBG_ERR("ad_convert(\"%s\") failed\n", + smb_fname_str_dbg(smb_fname)); + } + } + + status = SMB_VFS_NEXT_CREATE_FILE( + handle, req, dirfsp, smb_fname, + access_mask, share_access, + create_disposition, create_options, + file_attributes, oplock_request, + lease, + allocation_size, private_flags, + sd, ea_list, result, + pinfo, in_context_blobs, out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + fsp = *result; + + if (global_fruit_config.nego_aapl) { + if (config->posix_rename && fsp->fsp_flags.is_directory) { + /* + * Enable POSIX directory rename behaviour + */ + fsp->posix_flags |= FSP_POSIX_FLAGS_RENAME; + } + } + + /* + * If this is a plain open for existing files, opening an 0 + * byte size resource fork MUST fail with + * NT_STATUS_OBJECT_NAME_NOT_FOUND. + * + * Cf the vfs_fruit torture tests in test_rfork_create(). + */ + if (global_fruit_config.nego_aapl && + create_disposition == FILE_OPEN && + smb_fname->st.st_ex_size == 0 && + is_named_stream(smb_fname)) + { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + if (is_named_stream(smb_fname) || fsp->fsp_flags.is_directory) { + return status; + } + + if ((config->locking == FRUIT_LOCKING_NETATALK) && + (fsp->op != NULL) && + !fsp->fsp_flags.is_pathref) + { + status = fruit_check_access( + handle, *result, + access_mask, + share_access); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + return status; + +fail: + DEBUG(10, ("fruit_create_file: %s\n", nt_errstr(status))); + + if (fsp) { + close_file_free(req, &fsp, ERROR_CLOSE); + *result = NULL; + } + + return status; +} + +static NTSTATUS fruit_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **pattr_data) +{ + struct fruit_config_data *config = NULL; + struct readdir_attr_data *attr_data; + uint32_t conv_flags = 0; + NTSTATUS status; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!global_fruit_config.nego_aapl) { + return SMB_VFS_NEXT_FREADDIR_ATTR(handle, + fsp, + mem_ctx, + pattr_data); + } + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + if (config->convert_adouble) { + if (config->wipe_intentionally_left_blank_rfork) { + conv_flags |= AD_CONV_WIPE_BLANK; + } + if (config->delete_empty_adfiles) { + conv_flags |= AD_CONV_DELETE; + } + + ret = ad_convert(handle, + fsp->fsp_name, + macos_string_replace_map, + conv_flags); + if (ret != 0) { + DBG_ERR("ad_convert(\"%s\") failed\n", + fsp_str_dbg(fsp)); + } + } + + *pattr_data = talloc_zero(mem_ctx, struct readdir_attr_data); + if (*pattr_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + attr_data = *pattr_data; + attr_data->type = RDATTR_AAPL; + + /* + * Mac metadata: compressed FinderInfo, resource fork length + * and creation date + */ + status = readdir_attr_macmeta(handle, fsp->fsp_name, attr_data); + if (!NT_STATUS_IS_OK(status)) { + /* + * Error handling is tricky: if we return failure from + * this function, the corresponding directory entry + * will to be passed to the client, so we really just + * want to error out on fatal errors. + */ + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + goto fail; + } + } + + /* + * UNIX mode + */ + if (config->unix_info_enabled) { + attr_data->attr_data.aapl.unix_mode = + fsp->fsp_name->st.st_ex_mode; + } + + /* + * max_access + */ + if (!config->readdir_attr_max_access) { + attr_data->attr_data.aapl.max_access = FILE_GENERIC_ALL; + } else { + status = smbd_calculate_access_mask_fsp(fsp->conn->cwd_fsp, + fsp, + false, + SEC_FLAG_MAXIMUM_ALLOWED, + &attr_data->attr_data.aapl.max_access); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + return NT_STATUS_OK; + +fail: + DBG_WARNING("Path [%s], error: %s\n", fsp_str_dbg(fsp), + nt_errstr(status)); + TALLOC_FREE(*pattr_data); + return status; +} + +static NTSTATUS fruit_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + struct security_ace ace; + struct dom_sid sid; + struct fruit_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Add MS NFS style ACEs with uid, gid and mode + */ + if (!global_fruit_config.nego_aapl) { + return NT_STATUS_OK; + } + if (!config->unix_info_enabled) { + return NT_STATUS_OK; + } + + /* First remove any existing ACE's with NFS style mode/uid/gid SIDs. */ + status = remove_virtual_nfs_aces(*ppdesc); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to remove MS NFS style ACEs\n"); + return status; + } + + /* MS NFS style mode */ + sid_compose(&sid, &global_sid_Unix_NFS_Mode, fsp->fsp_name->st.st_ex_mode); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + /* MS NFS style uid */ + sid_compose(&sid, &global_sid_Unix_NFS_Users, fsp->fsp_name->st.st_ex_uid); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + /* MS NFS style gid */ + sid_compose(&sid, &global_sid_Unix_NFS_Groups, fsp->fsp_name->st.st_ex_gid); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *orig_psd) +{ + NTSTATUS status; + bool do_chmod; + mode_t ms_nfs_mode = 0; + int result; + struct security_descriptor *psd = NULL; + uint32_t orig_num_aces = 0; + + if (orig_psd->dacl != NULL) { + orig_num_aces = orig_psd->dacl->num_aces; + } + + psd = security_descriptor_copy(talloc_tos(), orig_psd); + if (psd == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("fruit_fset_nt_acl: %s\n", fsp_str_dbg(fsp)); + + status = check_ms_nfs(handle, fsp, psd, &ms_nfs_mode, &do_chmod); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("fruit_fset_nt_acl: check_ms_nfs failed%s\n", fsp_str_dbg(fsp))); + TALLOC_FREE(psd); + return status; + } + + /* + * If only ms_nfs ACE entries were sent, ensure we set the DACL + * sent/present flags correctly now we've removed them. + */ + + if (orig_num_aces != 0) { + /* + * Are there any ACE's left ? + */ + if (psd->dacl->num_aces == 0) { + /* No - clear the DACL sent/present flags. */ + security_info_sent &= ~SECINFO_DACL; + psd->type &= ~SEC_DESC_DACL_PRESENT; + } + } + + status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("fruit_fset_nt_acl: SMB_VFS_NEXT_FSET_NT_ACL failed%s\n", fsp_str_dbg(fsp))); + TALLOC_FREE(psd); + return status; + } + + if (do_chmod) { + result = SMB_VFS_FCHMOD(fsp, ms_nfs_mode); + if (result != 0) { + DBG_WARNING("%s, result: %d, %04o error %s\n", + fsp_str_dbg(fsp), + result, + (unsigned)ms_nfs_mode, + strerror(errno)); + status = map_nt_error_from_unix(errno); + TALLOC_FREE(psd); + return status; + } + } + + TALLOC_FREE(psd); + return NT_STATUS_OK; +} + +static struct vfs_offload_ctx *fruit_offload_ctx; + +struct fruit_offload_read_state { + struct vfs_handle_struct *handle; + struct tevent_context *ev; + files_struct *fsp; + uint32_t fsctl; + uint32_t flags; + uint64_t xferlen; + DATA_BLOB token; +}; + +static void fruit_offload_read_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_offload_read_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct fruit_offload_read_state); + if (req == NULL) { + return NULL; + } + *state = (struct fruit_offload_read_state) { + .handle = handle, + .ev = ev, + .fsp = fsp, + .fsctl = fsctl, + }; + + subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp, + fsctl, ttl, offset, to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_offload_read_done, req); + return req; +} + +static void fruit_offload_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_offload_read_state *state = tevent_req_data( + req, struct fruit_offload_read_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq, + state->handle, + state, + &state->flags, + &state->xferlen, + &state->token); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) { + tevent_req_done(req); + return; + } + + status = vfs_offload_token_ctx_init(state->fsp->conn->sconn->client, + &fruit_offload_ctx); + if (tevent_req_nterror(req, status)) { + return; + } + + status = vfs_offload_token_db_store_fsp(fruit_offload_ctx, + state->fsp, + &state->token); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; +} + +static NTSTATUS fruit_offload_read_recv(struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *token) +{ + struct fruit_offload_read_state *state = tevent_req_data( + req, struct fruit_offload_read_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *flags = state->flags; + *xferlen = state->xferlen; + token->length = state->token.length; + token->data = talloc_move(mem_ctx, &state->token.data); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct fruit_offload_write_state { + struct vfs_handle_struct *handle; + off_t copied; + struct files_struct *src_fsp; + struct files_struct *dst_fsp; + bool is_copyfile; +}; + +static void fruit_offload_write_done(struct tevent_req *subreq); +static struct tevent_req *fruit_offload_write_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req, *subreq; + struct fruit_offload_write_state *state; + NTSTATUS status; + struct fruit_config_data *config; + off_t src_off = transfer_offset; + files_struct *src_fsp = NULL; + off_t to_copy = num; + bool copyfile_enabled = false; + + DEBUG(10,("soff: %ju, doff: %ju, len: %ju\n", + (uintmax_t)src_off, (uintmax_t)dest_off, (uintmax_t)num)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NULL); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_offload_write_state); + if (req == NULL) { + return NULL; + } + state->handle = handle; + state->dst_fsp = dest_fsp; + + switch (fsctl) { + case FSCTL_SRV_COPYCHUNK: + case FSCTL_SRV_COPYCHUNK_WRITE: + copyfile_enabled = config->copyfile_enabled; + break; + default: + break; + } + + /* + * Check if this a OS X copyfile style copychunk request with + * a requested chunk count of 0 that was translated to a + * offload_write_send VFS call overloading the parameters src_off + * = dest_off = num = 0. + */ + if (copyfile_enabled && num == 0 && src_off == 0 && dest_off == 0) { + status = vfs_offload_token_db_fetch_fsp( + fruit_offload_ctx, token, &src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + state->src_fsp = src_fsp; + + status = vfs_stat_fsp(src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + to_copy = src_fsp->fsp_name->st.st_ex_size; + state->is_copyfile = true; + } + + subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, + mem_ctx, + ev, + fsctl, + token, + transfer_offset, + dest_fsp, + dest_off, + to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, fruit_offload_write_done, req); + return req; +} + +static void fruit_offload_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_offload_write_state *state = tevent_req_data( + req, struct fruit_offload_write_state); + NTSTATUS status; + unsigned int num_streams = 0; + struct stream_struct *streams = NULL; + unsigned int i; + struct smb_filename *src_fname_tmp = NULL; + struct smb_filename *dst_fname_tmp = NULL; + + status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle, + subreq, + &state->copied); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!state->is_copyfile) { + tevent_req_done(req); + return; + } + + /* + * Now copy all remaining streams. We know the share supports + * streams, because we're in vfs_fruit. We don't do this async + * because streams are few and small. + */ + status = vfs_fstreaminfo(state->src_fsp, + req, &num_streams, &streams); + if (tevent_req_nterror(req, status)) { + return; + } + + if (num_streams == 1) { + /* There is always one stream, ::$DATA. */ + tevent_req_done(req); + return; + } + + for (i = 0; i < num_streams; i++) { + DEBUG(10, ("%s: stream: '%s'/%zu\n", + __func__, streams[i].name, (size_t)streams[i].size)); + + src_fname_tmp = synthetic_smb_fname( + req, + state->src_fsp->fsp_name->base_name, + streams[i].name, + NULL, + state->src_fsp->fsp_name->twrp, + state->src_fsp->fsp_name->flags); + if (tevent_req_nomem(src_fname_tmp, req)) { + return; + } + + if (is_ntfs_default_stream_smb_fname(src_fname_tmp)) { + TALLOC_FREE(src_fname_tmp); + continue; + } + + dst_fname_tmp = synthetic_smb_fname( + req, + state->dst_fsp->fsp_name->base_name, + streams[i].name, + NULL, + state->dst_fsp->fsp_name->twrp, + state->dst_fsp->fsp_name->flags); + if (tevent_req_nomem(dst_fname_tmp, req)) { + TALLOC_FREE(src_fname_tmp); + return; + } + + status = copy_file(req, + state->handle->conn, + src_fname_tmp, + dst_fname_tmp, + FILE_CREATE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("%s: copy %s to %s failed: %s\n", __func__, + smb_fname_str_dbg(src_fname_tmp), + smb_fname_str_dbg(dst_fname_tmp), + nt_errstr(status))); + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + tevent_req_nterror(req, status); + return; + } + + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + } + + TALLOC_FREE(streams); + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + tevent_req_done(req); +} + +static NTSTATUS fruit_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + struct fruit_offload_write_state *state = tevent_req_data( + req, struct fruit_offload_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(1, ("server side copy chunk failed: %s\n", + nt_errstr(status))); + *copied = 0; + tevent_req_received(req); + return status; + } + + *copied = state->copied; + tevent_req_received(req); + + return NT_STATUS_OK; +} + +static char *fruit_get_bandsize_line(char **lines, int numlines) +{ + static regex_t re; + static bool re_initialized = false; + int i; + int ret; + + if (!re_initialized) { + ret = regcomp(&re, "^[[:blank:]]*band-size$", 0); + if (ret != 0) { + return NULL; + } + re_initialized = true; + } + + for (i = 0; i < numlines; i++) { + regmatch_t matches[1]; + + ret = regexec(&re, lines[i], 1, matches, 0); + if (ret == 0) { + /* + * Check if the match was on the last line, sa we want + * the subsequent line. + */ + if (i + 1 == numlines) { + return NULL; + } + return lines[i + 1]; + } + if (ret != REG_NOMATCH) { + return NULL; + } + } + + return NULL; +} + +static bool fruit_get_bandsize_from_line(char *line, size_t *_band_size) +{ + static regex_t re; + static bool re_initialized = false; + regmatch_t matches[2]; + uint64_t band_size; + int ret; + bool ok; + + if (!re_initialized) { + ret = regcomp(&re, + "^[[:blank:]]*" + "\\([[:digit:]]*\\)$", + 0); + if (ret != 0) { + return false; + } + re_initialized = true; + } + + ret = regexec(&re, line, 2, matches, 0); + if (ret != 0) { + DBG_ERR("regex failed [%s]\n", line); + return false; + } + + line[matches[1].rm_eo] = '\0'; + + ok = conv_str_u64(&line[matches[1].rm_so], &band_size); + if (!ok) { + return false; + } + *_band_size = (size_t)band_size; + return true; +} + +/* + * This reads and parses an Info.plist from a TM sparsebundle looking for the + * "band-size" key and value. + */ +static bool fruit_get_bandsize(vfs_handle_struct *handle, + const char *dir, + size_t *band_size) +{ +#define INFO_PLIST_MAX_SIZE 64*1024 + char *plist = NULL; + struct smb_filename *smb_fname = NULL; + files_struct *fsp = NULL; + uint8_t *file_data = NULL; + char **lines = NULL; + char *band_size_line = NULL; + size_t plist_file_size; + ssize_t nread; + int numlines; + int ret; + bool ok = false; + NTSTATUS status; + + plist = talloc_asprintf(talloc_tos(), + "%s/%s/Info.plist", + handle->conn->connectpath, + dir); + if (plist == NULL) { + ok = false; + goto out; + } + + smb_fname = synthetic_smb_fname(talloc_tos(), + plist, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + ok = false; + goto out; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret != 0) { + DBG_INFO("Ignoring Sparsebundle without Info.plist [%s]\n", dir); + ok = true; + goto out; + } + + plist_file_size = smb_fname->st.st_ex_size; + + if (plist_file_size > INFO_PLIST_MAX_SIZE) { + DBG_INFO("%s is too large, ignoring\n", plist); + ok = true; + goto out; + } + + status = SMB_VFS_NEXT_CREATE_FILE( + handle, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + FILE_GENERIC_READ, /* access_mask */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */ + FILE_OPEN, /* create_disposition */ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* psbuf */ + NULL, NULL); /* create context */ + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("Opening [%s] failed [%s]\n", + smb_fname_str_dbg(smb_fname), nt_errstr(status)); + ok = false; + goto out; + } + + file_data = talloc_zero_array(talloc_tos(), + uint8_t, + plist_file_size + 1); + if (file_data == NULL) { + ok = false; + goto out; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fsp, file_data, plist_file_size, 0); + if (nread != plist_file_size) { + DBG_ERR("Short read on [%s]: %zu/%zd\n", + fsp_str_dbg(fsp), nread, plist_file_size); + ok = false; + goto out; + + } + + status = close_file_free(NULL, &fsp, NORMAL_CLOSE); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("close_file failed: %s\n", nt_errstr(status)); + ok = false; + goto out; + } + + lines = file_lines_parse((char *)file_data, + plist_file_size, + &numlines, + talloc_tos()); + if (lines == NULL) { + ok = false; + goto out; + } + + band_size_line = fruit_get_bandsize_line(lines, numlines); + if (band_size_line == NULL) { + DBG_ERR("Didn't find band-size key in [%s]\n", + smb_fname_str_dbg(smb_fname)); + ok = false; + goto out; + } + + ok = fruit_get_bandsize_from_line(band_size_line, band_size); + if (!ok) { + DBG_ERR("fruit_get_bandsize_from_line failed\n"); + goto out; + } + + DBG_DEBUG("Parsed band-size [%zu] for [%s]\n", *band_size, plist); + +out: + if (fsp != NULL) { + status = close_file_free(NULL, &fsp, NORMAL_CLOSE); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("close_file failed: %s\n", nt_errstr(status)); + } + } + TALLOC_FREE(plist); + TALLOC_FREE(smb_fname); + TALLOC_FREE(file_data); + TALLOC_FREE(lines); + return ok; +} + +struct fruit_disk_free_state { + off_t total_size; +}; + +static bool fruit_get_num_bands(vfs_handle_struct *handle, + const char *bundle, + size_t *_nbands) +{ + char *path = NULL; + struct smb_filename *bands_dir = NULL; + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + size_t nbands; + NTSTATUS status; + + path = talloc_asprintf(talloc_tos(), + "%s/%s/bands", + handle->conn->connectpath, + bundle); + if (path == NULL) { + return false; + } + + bands_dir = synthetic_smb_fname(talloc_tos(), + path, + NULL, + NULL, + 0, + 0); + TALLOC_FREE(path); + if (bands_dir == NULL) { + return false; + } + + status = OpenDir(talloc_tos(), + handle->conn, + bands_dir, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(bands_dir); + errno = map_errno_from_nt_status(status); + return false; + } + + nbands = 0; + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + if (ISDOT(dname) || ISDOTDOT(dname)) { + continue; + } + nbands++; + } + TALLOC_FREE(dir_hnd); + + DBG_DEBUG("%zu bands in [%s]\n", nbands, smb_fname_str_dbg(bands_dir)); + + TALLOC_FREE(bands_dir); + + *_nbands = nbands; + return true; +} + +static bool fruit_tmsize_do_dirent(vfs_handle_struct *handle, + struct fruit_disk_free_state *state, + const char *name) +{ + bool ok; + char *p = NULL; + size_t sparsebundle_strlen = strlen("sparsebundle"); + size_t bandsize = 0; + size_t nbands; + off_t tm_size; + + p = strstr(name, "sparsebundle"); + if (p == NULL) { + return true; + } + + if (p[sparsebundle_strlen] != '\0') { + return true; + } + + DBG_DEBUG("Processing sparsebundle [%s]\n", name); + + ok = fruit_get_bandsize(handle, name, &bandsize); + if (!ok) { + /* + * Beware of race conditions: this may be an uninitialized + * Info.plist that a client is just creating. We don't want let + * this to trigger complete failure. + */ + DBG_ERR("Processing sparsebundle [%s] failed\n", name); + return true; + } + + ok = fruit_get_num_bands(handle, name, &nbands); + if (!ok) { + /* + * Beware of race conditions: this may be a backup sparsebundle + * in an early stage lacking a bands subdirectory. We don't want + * let this to trigger complete failure. + */ + DBG_ERR("Processing sparsebundle [%s] failed\n", name); + return true; + } + + /* + * Arithmetic on 32-bit systems may cause overflow, depending on + * size_t precision. First we check its unlikely, then we + * force the precision into target off_t, then we check that + * the total did not overflow either. + */ + if (bandsize > SIZE_MAX/nbands) { + DBG_ERR("tmsize potential overflow: bandsize [%zu] nbands [%zu]\n", + bandsize, nbands); + return false; + } + tm_size = (off_t)bandsize * (off_t)nbands; + + if (state->total_size + tm_size < state->total_size) { + DBG_ERR("tm total size overflow: bandsize [%zu] nbands [%zu]\n", + bandsize, nbands); + return false; + } + + state->total_size += tm_size; + + DBG_DEBUG("[%s] tm_size [%jd] total_size [%jd]\n", + name, (intmax_t)tm_size, (intmax_t)state->total_size); + + return true; +} + +/** + * Calculate used size of a TimeMachine volume + * + * This assumes that the volume is used only for TimeMachine. + * + * - readdir(basedir of share), then + * - for every element that matches regex "^\(.*\)\.sparsebundle$" : + * - parse "\1.sparsebundle/Info.plist" and read the band-size XML key + * - count band files in "\1.sparsebundle/bands/" + * - calculate used size of all bands: band_count * band_size + **/ +static uint64_t fruit_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *_bsize, + uint64_t *_dfree, + uint64_t *_dsize) +{ + struct fruit_config_data *config = NULL; + struct fruit_disk_free_state state = {0}; + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + uint64_t dfree; + uint64_t dsize; + bool ok; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return UINT64_MAX); + + if (!config->time_machine || + config->time_machine_max_size == 0) + { + return SMB_VFS_NEXT_DISK_FREE(handle, + smb_fname, + _bsize, + _dfree, + _dsize); + } + + status = OpenDir(talloc_tos(), + handle->conn, + smb_fname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return UINT64_MAX; + } + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + ok = fruit_tmsize_do_dirent(handle, &state, dname); + if (!ok) { + TALLOC_FREE(talloced); + TALLOC_FREE(dir_hnd); + return UINT64_MAX; + } + TALLOC_FREE(talloced); + } + + TALLOC_FREE(dir_hnd); + + dsize = config->time_machine_max_size / 512; + dfree = dsize - (state.total_size / 512); + if (dfree > dsize) { + dfree = 0; + } + + *_bsize = 512; + *_dsize = dsize; + *_dfree = dfree; + return dfree / 2; +} + +static uint64_t fruit_fs_file_id(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *psbuf) +{ + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return 0); + + if (global_fruit_config.nego_aapl && + config->aapl_zero_file_id) + { + return 0; + } + + return SMB_VFS_NEXT_FS_FILE_ID(handle, psbuf); +} + +static struct vfs_fn_pointers vfs_fruit_fns = { + .connect_fn = fruit_connect, + .disk_free_fn = fruit_disk_free, + + /* File operations */ + .fchmod_fn = fruit_fchmod, + .unlinkat_fn = fruit_unlinkat, + .renameat_fn = fruit_renameat, + .openat_fn = fruit_openat, + .close_fn = fruit_close, + .pread_fn = fruit_pread, + .pwrite_fn = fruit_pwrite, + .pread_send_fn = fruit_pread_send, + .pread_recv_fn = fruit_pread_recv, + .pwrite_send_fn = fruit_pwrite_send, + .pwrite_recv_fn = fruit_pwrite_recv, + .fsync_send_fn = fruit_fsync_send, + .fsync_recv_fn = fruit_fsync_recv, + .stat_fn = fruit_stat, + .lstat_fn = fruit_lstat, + .fstat_fn = fruit_fstat, + .fstreaminfo_fn = fruit_fstreaminfo, + .fntimes_fn = fruit_fntimes, + .ftruncate_fn = fruit_ftruncate, + .fallocate_fn = fruit_fallocate, + .create_file_fn = fruit_create_file, + .freaddir_attr_fn = fruit_freaddir_attr, + .offload_read_send_fn = fruit_offload_read_send, + .offload_read_recv_fn = fruit_offload_read_recv, + .offload_write_send_fn = fruit_offload_write_send, + .offload_write_recv_fn = fruit_offload_write_recv, + .fs_file_id_fn = fruit_fs_file_id, + + /* NT ACL operations */ + .fget_nt_acl_fn = fruit_fget_nt_acl, + .fset_nt_acl_fn = fruit_fset_nt_acl, +}; + +static_decl_vfs; +NTSTATUS vfs_fruit_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fruit", + &vfs_fruit_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + vfs_fruit_debug_level = debug_add_class("fruit"); + if (vfs_fruit_debug_level == -1) { + vfs_fruit_debug_level = DBGC_VFS; + DEBUG(0, ("%s: Couldn't register custom debugging class!\n", + "vfs_fruit_init")); + } else { + DEBUG(10, ("%s: Debug class number of '%s': %d\n", + "vfs_fruit_init","fruit",vfs_fruit_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_full_audit.c b/source3/modules/vfs_full_audit.c new file mode 100644 index 0000000..9fd8a75 --- /dev/null +++ b/source3/modules/vfs_full_audit.c @@ -0,0 +1,3035 @@ +/* + * Auditing VFS module for samba. Log selected file operations to syslog + * facility. + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) John H Terpstra, 2003 + * Copyright (C) Stefan (metze) Metzmacher, 2003 + * Copyright (C) Volker Lendecke, 2004 + * + * 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 . + */ + +/* + * This module implements parseable logging for all Samba VFS operations. + * + * You use it as follows: + * + * [tmp] + * path = /tmp + * vfs objects = full_audit + * full_audit:prefix = %u|%I + * full_audit:success = open opendir create_file + * full_audit:failure = all + * + * vfs op can be "all" which means log all operations. + * vfs op can be "none" which means no logging. + * + * This leads to syslog entries of the form: + * smbd_audit: nobody|192.168.234.1|opendir|ok|/tmp + * smbd_audit: nobody|192.168.234.1|create_file|fail (No such file or directory)|0x1|file|open|/ts/doesNotExist + * smbd_audit: nobody|192.168.234.1|open|ok|w|/tmp/file.txt + * smbd_audit: nobody|192.168.234.1|create_file|ok|0x3|file|open|/tmp/file.txt + * + * where "nobody" is the connected username and "192.168.234.1" is the + * client's IP address. + * + * Options: + * + * prefix: A macro expansion template prepended to the syslog entry. + * + * success: A list of VFS operations for which a successful completion should + * be logged. Defaults to no logging at all. The special operation "all" logs + * - you guessed it - everything. + * + * failure: A list of VFS operations for which failure to complete should be + * logged. Defaults to logging everything. + */ + + +#include "includes.h" +#include "system/filesys.h" +#include "system/syslog.h" +#include "smbd/smbd.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "auth.h" +#include "ntioctl.h" +#include "lib/param/loadparm.h" +#include "lib/util/bitmap.h" +#include "lib/util/tevent_unix.h" +#include "libcli/security/sddl.h" +#include "passdb/machine_sid.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/string_wrappers.h" +#include "source3/lib/substitute.h" + +static int vfs_full_audit_debug_level = DBGC_VFS; + +struct vfs_full_audit_private_data { + struct bitmap *success_ops; + struct bitmap *failure_ops; + int syslog_facility; + int syslog_priority; + bool log_secdesc; + bool do_syslog; +}; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_full_audit_debug_level + +typedef enum _vfs_op_type { + SMB_VFS_OP_NOOP = -1, + + /* Disk operations */ + + SMB_VFS_OP_CONNECT = 0, + SMB_VFS_OP_DISCONNECT, + SMB_VFS_OP_DISK_FREE, + SMB_VFS_OP_GET_QUOTA, + SMB_VFS_OP_SET_QUOTA, + SMB_VFS_OP_GET_SHADOW_COPY_DATA, + SMB_VFS_OP_STATVFS, + SMB_VFS_OP_FS_CAPABILITIES, + SMB_VFS_OP_GET_DFS_REFERRALS, + SMB_VFS_OP_CREATE_DFS_PATHAT, + SMB_VFS_OP_READ_DFS_PATHAT, + + /* Directory operations */ + + SMB_VFS_OP_FDOPENDIR, + SMB_VFS_OP_READDIR, + SMB_VFS_OP_REWINDDIR, + SMB_VFS_OP_MKDIRAT, + SMB_VFS_OP_CLOSEDIR, + + /* File operations */ + + SMB_VFS_OP_OPEN, + SMB_VFS_OP_OPENAT, + SMB_VFS_OP_CREATE_FILE, + SMB_VFS_OP_CLOSE, + SMB_VFS_OP_READ, + SMB_VFS_OP_PREAD, + SMB_VFS_OP_PREAD_SEND, + SMB_VFS_OP_PREAD_RECV, + SMB_VFS_OP_WRITE, + SMB_VFS_OP_PWRITE, + SMB_VFS_OP_PWRITE_SEND, + SMB_VFS_OP_PWRITE_RECV, + SMB_VFS_OP_LSEEK, + SMB_VFS_OP_SENDFILE, + SMB_VFS_OP_RECVFILE, + SMB_VFS_OP_RENAMEAT, + SMB_VFS_OP_FSYNC_SEND, + SMB_VFS_OP_FSYNC_RECV, + SMB_VFS_OP_STAT, + SMB_VFS_OP_FSTAT, + SMB_VFS_OP_LSTAT, + SMB_VFS_OP_FSTATAT, + SMB_VFS_OP_GET_ALLOC_SIZE, + SMB_VFS_OP_UNLINKAT, + SMB_VFS_OP_FCHMOD, + SMB_VFS_OP_FCHOWN, + SMB_VFS_OP_LCHOWN, + SMB_VFS_OP_CHDIR, + SMB_VFS_OP_GETWD, + SMB_VFS_OP_NTIMES, + SMB_VFS_OP_FNTIMES, + SMB_VFS_OP_FTRUNCATE, + SMB_VFS_OP_FALLOCATE, + SMB_VFS_OP_LOCK, + SMB_VFS_OP_FILESYSTEM_SHAREMODE, + SMB_VFS_OP_FCNTL, + SMB_VFS_OP_LINUX_SETLEASE, + SMB_VFS_OP_GETLOCK, + SMB_VFS_OP_SYMLINKAT, + SMB_VFS_OP_READLINKAT, + SMB_VFS_OP_LINKAT, + SMB_VFS_OP_MKNODAT, + SMB_VFS_OP_REALPATH, + SMB_VFS_OP_FCHFLAGS, + SMB_VFS_OP_FILE_ID_CREATE, + SMB_VFS_OP_FS_FILE_ID, + SMB_VFS_OP_FSTREAMINFO, + SMB_VFS_OP_GET_REAL_FILENAME, + SMB_VFS_OP_GET_REAL_FILENAME_AT, + SMB_VFS_OP_CONNECTPATH, + SMB_VFS_OP_BRL_LOCK_WINDOWS, + SMB_VFS_OP_BRL_UNLOCK_WINDOWS, + SMB_VFS_OP_STRICT_LOCK_CHECK, + SMB_VFS_OP_TRANSLATE_NAME, + SMB_VFS_OP_PARENT_PATHNAME, + SMB_VFS_OP_FSCTL, + SMB_VFS_OP_OFFLOAD_READ_SEND, + SMB_VFS_OP_OFFLOAD_READ_RECV, + SMB_VFS_OP_OFFLOAD_WRITE_SEND, + SMB_VFS_OP_OFFLOAD_WRITE_RECV, + SMB_VFS_OP_FGET_COMPRESSION, + SMB_VFS_OP_SET_COMPRESSION, + SMB_VFS_OP_SNAP_CHECK_PATH, + SMB_VFS_OP_SNAP_CREATE, + SMB_VFS_OP_SNAP_DELETE, + + /* DOS attribute operations. */ + SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, + SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV, + SMB_VFS_OP_FGET_DOS_ATTRIBUTES, + SMB_VFS_OP_FSET_DOS_ATTRIBUTES, + + /* NT ACL operations. */ + + SMB_VFS_OP_FGET_NT_ACL, + SMB_VFS_OP_FSET_NT_ACL, + SMB_VFS_OP_AUDIT_FILE, + + /* POSIX ACL operations. */ + + SMB_VFS_OP_SYS_ACL_GET_FD, + SMB_VFS_OP_SYS_ACL_BLOB_GET_FD, + SMB_VFS_OP_SYS_ACL_SET_FD, + SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD, + + /* EA operations. */ + SMB_VFS_OP_GETXATTRAT_SEND, + SMB_VFS_OP_GETXATTRAT_RECV, + SMB_VFS_OP_FGETXATTR, + SMB_VFS_OP_FLISTXATTR, + SMB_VFS_OP_REMOVEXATTR, + SMB_VFS_OP_FREMOVEXATTR, + SMB_VFS_OP_FSETXATTR, + + /* aio operations */ + SMB_VFS_OP_AIO_FORCE, + + /* offline operations */ + SMB_VFS_OP_IS_OFFLINE, + SMB_VFS_OP_SET_OFFLINE, + + /* Durable handle operations. */ + SMB_VFS_OP_DURABLE_COOKIE, + SMB_VFS_OP_DURABLE_DISCONNECT, + SMB_VFS_OP_DURABLE_RECONNECT, + + SMB_VFS_OP_FREADDIR_ATTR, + + /* This should always be last enum value */ + + SMB_VFS_OP_LAST +} vfs_op_type; + +/* The following array *must* be in the same order as defined in vfs_op_type */ + +static struct { + vfs_op_type type; + const char *name; +} vfs_op_names[] = { + { SMB_VFS_OP_CONNECT, "connect" }, + { SMB_VFS_OP_DISCONNECT, "disconnect" }, + { SMB_VFS_OP_DISK_FREE, "disk_free" }, + { SMB_VFS_OP_GET_QUOTA, "get_quota" }, + { SMB_VFS_OP_SET_QUOTA, "set_quota" }, + { SMB_VFS_OP_GET_SHADOW_COPY_DATA, "get_shadow_copy_data" }, + { SMB_VFS_OP_STATVFS, "statvfs" }, + { SMB_VFS_OP_FS_CAPABILITIES, "fs_capabilities" }, + { SMB_VFS_OP_GET_DFS_REFERRALS, "get_dfs_referrals" }, + { SMB_VFS_OP_CREATE_DFS_PATHAT, "create_dfs_pathat" }, + { SMB_VFS_OP_READ_DFS_PATHAT, "read_dfs_pathat" }, + { SMB_VFS_OP_FDOPENDIR, "fdopendir" }, + { SMB_VFS_OP_READDIR, "readdir" }, + { SMB_VFS_OP_REWINDDIR, "rewinddir" }, + { SMB_VFS_OP_MKDIRAT, "mkdirat" }, + { SMB_VFS_OP_CLOSEDIR, "closedir" }, + { SMB_VFS_OP_OPEN, "open" }, + { SMB_VFS_OP_OPENAT, "openat" }, + { SMB_VFS_OP_CREATE_FILE, "create_file" }, + { SMB_VFS_OP_CLOSE, "close" }, + { SMB_VFS_OP_READ, "read" }, + { SMB_VFS_OP_PREAD, "pread" }, + { SMB_VFS_OP_PREAD_SEND, "pread_send" }, + { SMB_VFS_OP_PREAD_RECV, "pread_recv" }, + { SMB_VFS_OP_WRITE, "write" }, + { SMB_VFS_OP_PWRITE, "pwrite" }, + { SMB_VFS_OP_PWRITE_SEND, "pwrite_send" }, + { SMB_VFS_OP_PWRITE_RECV, "pwrite_recv" }, + { SMB_VFS_OP_LSEEK, "lseek" }, + { SMB_VFS_OP_SENDFILE, "sendfile" }, + { SMB_VFS_OP_RECVFILE, "recvfile" }, + { SMB_VFS_OP_RENAMEAT, "renameat" }, + { SMB_VFS_OP_FSYNC_SEND, "fsync_send" }, + { SMB_VFS_OP_FSYNC_RECV, "fsync_recv" }, + { SMB_VFS_OP_STAT, "stat" }, + { SMB_VFS_OP_FSTAT, "fstat" }, + { SMB_VFS_OP_LSTAT, "lstat" }, + { SMB_VFS_OP_FSTATAT, "fstatat" }, + { SMB_VFS_OP_GET_ALLOC_SIZE, "get_alloc_size" }, + { SMB_VFS_OP_UNLINKAT, "unlinkat" }, + { SMB_VFS_OP_FCHMOD, "fchmod" }, + { SMB_VFS_OP_FCHOWN, "fchown" }, + { SMB_VFS_OP_LCHOWN, "lchown" }, + { SMB_VFS_OP_CHDIR, "chdir" }, + { SMB_VFS_OP_GETWD, "getwd" }, + { SMB_VFS_OP_NTIMES, "ntimes" }, + { SMB_VFS_OP_FNTIMES, "fntimes" }, + { SMB_VFS_OP_FTRUNCATE, "ftruncate" }, + { SMB_VFS_OP_FALLOCATE,"fallocate" }, + { SMB_VFS_OP_LOCK, "lock" }, + { SMB_VFS_OP_FILESYSTEM_SHAREMODE, "filesystem_sharemode" }, + { SMB_VFS_OP_FCNTL, "fcntl" }, + { SMB_VFS_OP_LINUX_SETLEASE, "linux_setlease" }, + { SMB_VFS_OP_GETLOCK, "getlock" }, + { SMB_VFS_OP_SYMLINKAT, "symlinkat" }, + { SMB_VFS_OP_READLINKAT,"readlinkat" }, + { SMB_VFS_OP_LINKAT, "linkat" }, + { SMB_VFS_OP_MKNODAT, "mknodat" }, + { SMB_VFS_OP_REALPATH, "realpath" }, + { SMB_VFS_OP_FCHFLAGS, "fchflags" }, + { SMB_VFS_OP_FILE_ID_CREATE, "file_id_create" }, + { SMB_VFS_OP_FS_FILE_ID, "fs_file_id" }, + { SMB_VFS_OP_FSTREAMINFO, "fstreaminfo" }, + { SMB_VFS_OP_GET_REAL_FILENAME, "get_real_filename" }, + { SMB_VFS_OP_GET_REAL_FILENAME_AT, "get_real_filename_at" }, + { SMB_VFS_OP_CONNECTPATH, "connectpath" }, + { SMB_VFS_OP_BRL_LOCK_WINDOWS, "brl_lock_windows" }, + { SMB_VFS_OP_BRL_UNLOCK_WINDOWS, "brl_unlock_windows" }, + { SMB_VFS_OP_STRICT_LOCK_CHECK, "strict_lock_check" }, + { SMB_VFS_OP_TRANSLATE_NAME, "translate_name" }, + { SMB_VFS_OP_PARENT_PATHNAME, "parent_pathname" }, + { SMB_VFS_OP_FSCTL, "fsctl" }, + { SMB_VFS_OP_OFFLOAD_READ_SEND, "offload_read_send" }, + { SMB_VFS_OP_OFFLOAD_READ_RECV, "offload_read_recv" }, + { SMB_VFS_OP_OFFLOAD_WRITE_SEND, "offload_write_send" }, + { SMB_VFS_OP_OFFLOAD_WRITE_RECV, "offload_write_recv" }, + { SMB_VFS_OP_FGET_COMPRESSION, "fget_compression" }, + { SMB_VFS_OP_SET_COMPRESSION, "set_compression" }, + { SMB_VFS_OP_SNAP_CHECK_PATH, "snap_check_path" }, + { SMB_VFS_OP_SNAP_CREATE, "snap_create" }, + { SMB_VFS_OP_SNAP_DELETE, "snap_delete" }, + { SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, "get_dos_attributes_send" }, + { SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV, "get_dos_attributes_recv" }, + { SMB_VFS_OP_FGET_DOS_ATTRIBUTES, "fget_dos_attributes" }, + { SMB_VFS_OP_FSET_DOS_ATTRIBUTES, "fset_dos_attributes" }, + { SMB_VFS_OP_FGET_NT_ACL, "fget_nt_acl" }, + { SMB_VFS_OP_FSET_NT_ACL, "fset_nt_acl" }, + { SMB_VFS_OP_AUDIT_FILE, "audit_file" }, + { SMB_VFS_OP_SYS_ACL_GET_FD, "sys_acl_get_fd" }, + { SMB_VFS_OP_SYS_ACL_BLOB_GET_FD, "sys_acl_blob_get_fd" }, + { SMB_VFS_OP_SYS_ACL_SET_FD, "sys_acl_set_fd" }, + { SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD, "sys_acl_delete_def_fd" }, + { SMB_VFS_OP_GETXATTRAT_SEND, "getxattrat_send" }, + { SMB_VFS_OP_GETXATTRAT_RECV, "getxattrat_recv" }, + { SMB_VFS_OP_FGETXATTR, "fgetxattr" }, + { SMB_VFS_OP_FLISTXATTR, "flistxattr" }, + { SMB_VFS_OP_REMOVEXATTR, "removexattr" }, + { SMB_VFS_OP_FREMOVEXATTR, "fremovexattr" }, + { SMB_VFS_OP_FSETXATTR, "fsetxattr" }, + { SMB_VFS_OP_AIO_FORCE, "aio_force" }, + { SMB_VFS_OP_IS_OFFLINE, "is_offline" }, + { SMB_VFS_OP_SET_OFFLINE, "set_offline" }, + { SMB_VFS_OP_DURABLE_COOKIE, "durable_cookie" }, + { SMB_VFS_OP_DURABLE_DISCONNECT, "durable_disconnect" }, + { SMB_VFS_OP_DURABLE_RECONNECT, "durable_reconnect" }, + { SMB_VFS_OP_FREADDIR_ATTR, "freaddir_attr" }, + { SMB_VFS_OP_LAST, NULL } +}; + +static int audit_syslog_facility(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_facilities[] = { +#ifdef LOG_AUTH + { LOG_AUTH, "AUTH" }, +#endif +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "AUTHPRIV" }, +#endif +#ifdef LOG_AUDIT + { LOG_AUDIT, "AUDIT" }, +#endif +#ifdef LOG_CONSOLE + { LOG_CONSOLE, "CONSOLE" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "CRON" }, +#endif +#ifdef LOG_DAEMON + { LOG_DAEMON, "DAEMON" }, +#endif +#ifdef LOG_FTP + { LOG_FTP, "FTP" }, +#endif +#ifdef LOG_INSTALL + { LOG_INSTALL, "INSTALL" }, +#endif +#ifdef LOG_KERN + { LOG_KERN, "KERN" }, +#endif +#ifdef LOG_LAUNCHD + { LOG_LAUNCHD, "LAUNCHD" }, +#endif +#ifdef LOG_LFMT + { LOG_LFMT, "LFMT" }, +#endif +#ifdef LOG_LPR + { LOG_LPR, "LPR" }, +#endif +#ifdef LOG_MAIL + { LOG_MAIL, "MAIL" }, +#endif +#ifdef LOG_MEGASAFE + { LOG_MEGASAFE, "MEGASAFE" }, +#endif +#ifdef LOG_NETINFO + { LOG_NETINFO, "NETINFO" }, +#endif +#ifdef LOG_NEWS + { LOG_NEWS, "NEWS" }, +#endif +#ifdef LOG_NFACILITIES + { LOG_NFACILITIES, "NFACILITIES" }, +#endif +#ifdef LOG_NTP + { LOG_NTP, "NTP" }, +#endif +#ifdef LOG_RAS + { LOG_RAS, "RAS" }, +#endif +#ifdef LOG_REMOTEAUTH + { LOG_REMOTEAUTH, "REMOTEAUTH" }, +#endif +#ifdef LOG_SECURITY + { LOG_SECURITY, "SECURITY" }, +#endif +#ifdef LOG_SYSLOG + { LOG_SYSLOG, "SYSLOG" }, +#endif +#ifdef LOG_USER + { LOG_USER, "USER" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "UUCP" }, +#endif + { LOG_LOCAL0, "LOCAL0" }, + { LOG_LOCAL1, "LOCAL1" }, + { LOG_LOCAL2, "LOCAL2" }, + { LOG_LOCAL3, "LOCAL3" }, + { LOG_LOCAL4, "LOCAL4" }, + { LOG_LOCAL5, "LOCAL5" }, + { LOG_LOCAL6, "LOCAL6" }, + { LOG_LOCAL7, "LOCAL7" }, + { -1, NULL } + }; + + int facility; + + facility = lp_parm_enum(SNUM(handle->conn), "full_audit", "facility", enum_log_facilities, LOG_USER); + + return facility; +} + +static int audit_syslog_priority(vfs_handle_struct *handle) +{ + static const struct enum_list enum_log_priorities[] = { + { LOG_EMERG, "EMERG" }, + { LOG_ALERT, "ALERT" }, + { LOG_CRIT, "CRIT" }, + { LOG_ERR, "ERR" }, + { LOG_WARNING, "WARNING" }, + { LOG_NOTICE, "NOTICE" }, + { LOG_INFO, "INFO" }, + { LOG_DEBUG, "DEBUG" }, + { -1, NULL } + }; + + int priority; + + priority = lp_parm_enum(SNUM(handle->conn), "full_audit", "priority", + enum_log_priorities, LOG_NOTICE); + if (priority == -1) { + priority = LOG_WARNING; + } + + return priority; +} + +static char *audit_prefix(TALLOC_CTX *ctx, connection_struct *conn) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *prefix = NULL; + char *result; + + prefix = talloc_strdup(ctx, + lp_parm_const_string(SNUM(conn), "full_audit", + "prefix", "%u|%I")); + if (!prefix) { + return NULL; + } + result = talloc_sub_full(ctx, + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name, + conn->connectpath, + conn->session_info->unix_token->gid, + conn->session_info->unix_info->sanitized_username, + conn->session_info->info->domain_name, + prefix); + TALLOC_FREE(prefix); + return result; +} + +static bool log_success(struct vfs_full_audit_private_data *pd, vfs_op_type op) +{ + if (pd->success_ops == NULL) { + return True; + } + + return bitmap_query(pd->success_ops, op); +} + +static bool log_failure(struct vfs_full_audit_private_data *pd, vfs_op_type op) +{ + if (pd->failure_ops == NULL) + return True; + + return bitmap_query(pd->failure_ops, op); +} + +static struct bitmap *init_bitmap(TALLOC_CTX *mem_ctx, const char **ops) +{ + struct bitmap *bm; + + if (ops == NULL) { + DBG_ERR("init_bitmap, ops list is empty (logic error)\n"); + return NULL; + } + + bm = bitmap_talloc(mem_ctx, SMB_VFS_OP_LAST); + if (bm == NULL) { + DBG_ERR("Could not alloc bitmap\n"); + return NULL; + } + + for (; *ops != NULL; ops += 1) { + int i; + bool neg = false; + const char *op; + + if (strequal(*ops, "all")) { + for (i=0; i= SMB_VFS_OP_LAST) + return "INVALID VFS OP"; + return vfs_op_names[op].name; +} + +static TALLOC_CTX *tmp_do_log_ctx; +/* + * Get us a temporary talloc context usable just for DEBUG arguments + */ +static TALLOC_CTX *do_log_ctx(void) +{ + if (tmp_do_log_ctx == NULL) { + tmp_do_log_ctx = talloc_named_const(NULL, 0, "do_log_ctx"); + } + return tmp_do_log_ctx; +} + +static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); + +static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle, + const char *format, ...) +{ + struct vfs_full_audit_private_data *pd; + fstring err_msg; + char *audit_pre = NULL; + va_list ap; + char *op_msg = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, pd, + struct vfs_full_audit_private_data, + return;); + + if (success && (!log_success(pd, op))) + goto out; + + if (!success && (!log_failure(pd, op))) + goto out; + + if (success) + fstrcpy(err_msg, "ok"); + else + fstr_sprintf(err_msg, "fail (%s)", strerror(errno)); + + va_start(ap, format); + op_msg = talloc_vasprintf(talloc_tos(), format, ap); + va_end(ap); + + if (!op_msg) { + goto out; + } + + audit_pre = audit_prefix(talloc_tos(), handle->conn); + + if (pd->do_syslog) { + int priority; + + /* + * Specify the facility to interoperate with other syslog + * callers (smbd for example). + */ + priority = pd->syslog_priority | pd->syslog_facility; + + syslog(priority, "%s|%s|%s|%s\n", + audit_pre ? audit_pre : "", + audit_opname(op), err_msg, op_msg); + } else { + DEBUG(1, ("%s|%s|%s|%s\n", + audit_pre ? audit_pre : "", + audit_opname(op), err_msg, op_msg)); + } + out: + TALLOC_FREE(audit_pre); + TALLOC_FREE(op_msg); + TALLOC_FREE(tmp_do_log_ctx); +} + +/** + * Return a string using the do_log_ctx() + */ +static const char *smb_fname_str_do_log(struct connection_struct *conn, + const struct smb_filename *smb_fname) +{ + char *fname = NULL; + NTSTATUS status; + + if (smb_fname == NULL) { + return ""; + } + + if (smb_fname->base_name[0] != '/') { + char *abs_name = NULL; + struct smb_filename *fname_copy = cp_smb_filename( + do_log_ctx(), + smb_fname); + if (fname_copy == NULL) { + return ""; + } + + if (!ISDOT(smb_fname->base_name)) { + abs_name = talloc_asprintf(do_log_ctx(), + "%s/%s", + conn->cwd_fsp->fsp_name->base_name, + smb_fname->base_name); + } else { + abs_name = talloc_strdup(do_log_ctx(), + conn->cwd_fsp->fsp_name->base_name); + } + if (abs_name == NULL) { + return ""; + } + fname_copy->base_name = abs_name; + smb_fname = fname_copy; + } + + status = get_full_smb_filename(do_log_ctx(), smb_fname, &fname); + if (!NT_STATUS_IS_OK(status)) { + return ""; + } + return fname; +} + +/** + * Return an fsp debug string using the do_log_ctx() + */ +static const char *fsp_str_do_log(const struct files_struct *fsp) +{ + return smb_fname_str_do_log(fsp->conn, fsp->fsp_name); +} + +/* Implementation of vfs_ops. Pass everything on to the default + operation but log event first. */ + +static int smb_full_audit_connect(vfs_handle_struct *handle, + const char *svc, const char *user) +{ + int result; + const char *none[] = { "none" }; + struct vfs_full_audit_private_data *pd = NULL; + + result = SMB_VFS_NEXT_CONNECT(handle, svc, user); + if (result < 0) { + return result; + } + + pd = talloc_zero(handle, struct vfs_full_audit_private_data); + if (!pd) { + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + pd->syslog_facility = audit_syslog_facility(handle); + if (pd->syslog_facility == -1) { + DEBUG(1, ("%s: Unknown facility %s\n", __func__, + lp_parm_const_string(SNUM(handle->conn), + "full_audit", "facility", + "USER"))); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + pd->syslog_priority = audit_syslog_priority(handle); + + pd->log_secdesc = lp_parm_bool(SNUM(handle->conn), + "full_audit", "log_secdesc", false); + + pd->do_syslog = lp_parm_bool(SNUM(handle->conn), + "full_audit", "syslog", true); + +#ifdef WITH_SYSLOG + if (pd->do_syslog) { + openlog("smbd_audit", 0, pd->syslog_facility); + } +#endif + + pd->success_ops = init_bitmap( + pd, lp_parm_string_list(SNUM(handle->conn), "full_audit", + "success", none)); + if (pd->success_ops == NULL) { + DBG_ERR("Invalid success operations list. Failing connect\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + pd->failure_ops = init_bitmap( + pd, lp_parm_string_list(SNUM(handle->conn), "full_audit", + "failure", none)); + if (pd->failure_ops == NULL) { + DBG_ERR("Invalid failure operations list. Failing connect\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + /* Store the private data. */ + SMB_VFS_HANDLE_SET_DATA(handle, pd, NULL, + struct vfs_full_audit_private_data, return -1); + + do_log(SMB_VFS_OP_CONNECT, True, handle, + "%s", svc); + + return 0; +} + +static void smb_full_audit_disconnect(vfs_handle_struct *handle) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + SMB_VFS_NEXT_DISCONNECT(handle); + + do_log(SMB_VFS_OP_DISCONNECT, True, handle, + "%s", lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))); + + /* The bitmaps will be disconnected when the private + data is deleted. */ +} + +static uint64_t smb_full_audit_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + uint64_t result; + + result = SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree, dsize); + + /* Don't have a reasonable notion of failure here */ + + do_log(SMB_VFS_OP_DISK_FREE, + True, + handle, + "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static int smb_full_audit_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ + int result; + + result = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt); + + do_log(SMB_VFS_OP_GET_QUOTA, + (result >= 0), + handle, + "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static int smb_full_audit_set_quota(struct vfs_handle_struct *handle, + enum SMB_QUOTA_TYPE qtype, unid_t id, + SMB_DISK_QUOTA *qt) +{ + int result; + + result = SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, qt); + + do_log(SMB_VFS_OP_SET_QUOTA, (result >= 0), handle, ""); + + return result; +} + +static int smb_full_audit_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *shadow_copy_data, + bool labels) +{ + int result; + + result = SMB_VFS_NEXT_GET_SHADOW_COPY_DATA(handle, fsp, shadow_copy_data, labels); + + do_log(SMB_VFS_OP_GET_SHADOW_COPY_DATA, (result >= 0), handle, ""); + + return result; +} + +static int smb_full_audit_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + int result; + + result = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf); + + do_log(SMB_VFS_OP_STATVFS, (result >= 0), handle, ""); + + return result; +} + +static uint32_t smb_full_audit_fs_capabilities(struct vfs_handle_struct *handle, enum timestamp_set_resolution *p_ts_res) +{ + int result; + + result = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res); + + do_log(SMB_VFS_OP_FS_CAPABILITIES, true, handle, ""); + + return result; +} + +static NTSTATUS smb_full_audit_get_dfs_referrals( + struct vfs_handle_struct *handle, + struct dfs_GetDFSReferral *r) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r); + + do_log(SMB_VFS_OP_GET_DFS_REFERRALS, NT_STATUS_IS_OK(status), + handle, ""); + + return status; +} + +static NTSTATUS smb_full_audit_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + NTSTATUS status; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + smb_fname, + reflist, + referral_count); + + do_log(SMB_VFS_OP_CREATE_DFS_PATHAT, + NT_STATUS_IS_OK(status), + handle, + "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + return status; +} + +static NTSTATUS smb_full_audit_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + struct smb_filename *full_fname = NULL; + NTSTATUS status; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + dirfsp, + smb_fname, + ppreflist, + preferral_count); + + do_log(SMB_VFS_OP_READ_DFS_PATHAT, + NT_STATUS_IS_OK(status), + handle, + "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + return status; +} + +static NTSTATUS smb_full_audit_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, service_path, + base_volume); + do_log(SMB_VFS_OP_SNAP_CHECK_PATH, NT_STATUS_IS_OK(status), + handle, ""); + + return status; +} + +static NTSTATUS smb_full_audit_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **base_path, + char **snap_path) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, tstamp, + rw, base_path, snap_path); + do_log(SMB_VFS_OP_SNAP_CREATE, NT_STATUS_IS_OK(status), handle, ""); + + return status; +} + +static NTSTATUS smb_full_audit_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, base_path, + snap_path); + do_log(SMB_VFS_OP_SNAP_DELETE, NT_STATUS_IS_OK(status), handle, ""); + + return status; +} + +static DIR *smb_full_audit_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, const char *mask, uint32_t attr) +{ + DIR *result; + + result = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr); + + do_log(SMB_VFS_OP_FDOPENDIR, (result != NULL), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static struct dirent *smb_full_audit_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + struct dirent *result; + + result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp); + + /* This operation has no reasonable error condition + * (End of dir is also failure), so always succeed. + */ + do_log(SMB_VFS_OP_READDIR, True, handle, ""); + + return result; +} + +static void smb_full_audit_rewinddir(vfs_handle_struct *handle, + DIR *dirp) +{ + SMB_VFS_NEXT_REWINDDIR(handle, dirp); + + do_log(SMB_VFS_OP_REWINDDIR, True, handle, ""); +} + +static int smb_full_audit_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + + do_log(SMB_VFS_OP_MKDIRAT, + (result >= 0), + handle, + "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + + return result; +} + +static int smb_full_audit_closedir(vfs_handle_struct *handle, + DIR *dirp) +{ + int result; + + result = SMB_VFS_NEXT_CLOSEDIR(handle, dirp); + + do_log(SMB_VFS_OP_CLOSEDIR, (result >= 0), handle, ""); + + return result; +} + +static int smb_full_audit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + int result; + + result = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + + do_log(SMB_VFS_OP_OPENAT, (result >= 0), handle, "%s|%s", + ((how->flags & O_WRONLY) || (how->flags & O_RDWR))?"w":"r", + fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result_fsp, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS result; + const char* str_create_disposition; + + switch (create_disposition) { + case FILE_SUPERSEDE: + str_create_disposition = "supersede"; + break; + case FILE_OVERWRITE_IF: + str_create_disposition = "overwrite_if"; + break; + case FILE_OPEN: + str_create_disposition = "open"; + break; + case FILE_OVERWRITE: + str_create_disposition = "overwrite"; + break; + case FILE_CREATE: + str_create_disposition = "create"; + break; + case FILE_OPEN_IF: + str_create_disposition = "open_if"; + break; + default: + str_create_disposition = "unknown"; + } + + result = SMB_VFS_NEXT_CREATE_FILE( + handle, /* handle */ + req, /* req */ + dirfsp, /* dirfsp */ + smb_fname, /* fname */ + access_mask, /* access_mask */ + share_access, /* share_access */ + create_disposition, /* create_disposition*/ + create_options, /* create_options */ + file_attributes, /* file_attributes */ + oplock_request, /* oplock_request */ + lease, /* lease */ + allocation_size, /* allocation_size */ + private_flags, + sd, /* sd */ + ea_list, /* ea_list */ + result_fsp, /* result */ + pinfo, /* pinfo */ + in_context_blobs, out_context_blobs); /* create context */ + + do_log(SMB_VFS_OP_CREATE_FILE, (NT_STATUS_IS_OK(result)), handle, + "0x%x|%s|%s|%s", access_mask, + create_options & FILE_DIRECTORY_FILE ? "dir" : "file", + str_create_disposition, + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static int smb_full_audit_close(vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + + result = SMB_VFS_NEXT_CLOSE(handle, fsp); + + do_log(SMB_VFS_OP_CLOSE, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static ssize_t smb_full_audit_pread(vfs_handle_struct *handle, files_struct *fsp, + void *data, size_t n, off_t offset) +{ + ssize_t result; + + result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + + do_log(SMB_VFS_OP_PREAD, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +struct smb_full_audit_pread_state { + vfs_handle_struct *handle; + files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_full_audit_pread_done(struct tevent_req *subreq); + +static struct tevent_req *smb_full_audit_pread_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct smb_full_audit_pread_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_full_audit_pread_state); + if (req == NULL) { + do_log(SMB_VFS_OP_PREAD_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return NULL; + } + state->handle = handle; + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + do_log(SMB_VFS_OP_PREAD_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_full_audit_pread_done, req); + + do_log(SMB_VFS_OP_PREAD_SEND, true, handle, "%s", fsp_str_do_log(fsp)); + return req; +} + +static void smb_full_audit_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_full_audit_pread_state *state = tevent_req_data( + req, struct smb_full_audit_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t smb_full_audit_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_full_audit_pread_state *state = tevent_req_data( + req, struct smb_full_audit_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + do_log(SMB_VFS_OP_PREAD_RECV, false, state->handle, "%s", + fsp_str_do_log(state->fsp)); + return -1; + } + + do_log(SMB_VFS_OP_PREAD_RECV, (state->ret >= 0), state->handle, "%s", + fsp_str_do_log(state->fsp)); + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static ssize_t smb_full_audit_pwrite(vfs_handle_struct *handle, files_struct *fsp, + const void *data, size_t n, + off_t offset) +{ + ssize_t result; + + result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + + do_log(SMB_VFS_OP_PWRITE, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +struct smb_full_audit_pwrite_state { + vfs_handle_struct *handle; + files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_full_audit_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *smb_full_audit_pwrite_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct smb_full_audit_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_full_audit_pwrite_state); + if (req == NULL) { + do_log(SMB_VFS_OP_PWRITE_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return NULL; + } + state->handle = handle; + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + do_log(SMB_VFS_OP_PWRITE_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_full_audit_pwrite_done, req); + + do_log(SMB_VFS_OP_PWRITE_SEND, true, handle, "%s", + fsp_str_do_log(fsp)); + return req; +} + +static void smb_full_audit_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_full_audit_pwrite_state *state = tevent_req_data( + req, struct smb_full_audit_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t smb_full_audit_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_full_audit_pwrite_state *state = tevent_req_data( + req, struct smb_full_audit_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + do_log(SMB_VFS_OP_PWRITE_RECV, false, state->handle, "%s", + fsp_str_do_log(state->fsp)); + return -1; + } + + do_log(SMB_VFS_OP_PWRITE_RECV, (state->ret >= 0), state->handle, "%s", + fsp_str_do_log(state->fsp)); + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static off_t smb_full_audit_lseek(vfs_handle_struct *handle, files_struct *fsp, + off_t offset, int whence) +{ + ssize_t result; + + result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence); + + do_log(SMB_VFS_OP_LSEEK, (result != (ssize_t)-1), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static ssize_t smb_full_audit_sendfile(vfs_handle_struct *handle, int tofd, + files_struct *fromfsp, + const DATA_BLOB *hdr, off_t offset, + size_t n) +{ + ssize_t result; + + result = SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, hdr, offset, n); + + do_log(SMB_VFS_OP_SENDFILE, (result >= 0), handle, + "%s", fsp_str_do_log(fromfsp)); + + return result; +} + +static ssize_t smb_full_audit_recvfile(vfs_handle_struct *handle, int fromfd, + files_struct *tofsp, + off_t offset, + size_t n) +{ + ssize_t result; + + result = SMB_VFS_NEXT_RECVFILE(handle, fromfd, tofsp, offset, n); + + do_log(SMB_VFS_OP_RECVFILE, (result >= 0), handle, + "%s", fsp_str_do_log(tofsp)); + + return result; +} + +static int smb_full_audit_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int result; + int saved_errno; + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + return -1; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + + if (result == -1) { + saved_errno = errno; + } + do_log(SMB_VFS_OP_RENAMEAT, (result >= 0), handle, "%s|%s", + smb_fname_str_do_log(handle->conn, full_fname_src), + smb_fname_str_do_log(handle->conn, full_fname_dst)); + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + + if (result == -1) { + errno = saved_errno; + } + return result; +} + +struct smb_full_audit_fsync_state { + vfs_handle_struct *handle; + files_struct *fsp; + int ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_full_audit_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *smb_full_audit_fsync_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp) +{ + struct tevent_req *req, *subreq; + struct smb_full_audit_fsync_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_full_audit_fsync_state); + if (req == NULL) { + do_log(SMB_VFS_OP_FSYNC_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return NULL; + } + state->handle = handle; + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(subreq, req)) { + do_log(SMB_VFS_OP_FSYNC_SEND, false, handle, "%s", + fsp_str_do_log(fsp)); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_full_audit_fsync_done, req); + + do_log(SMB_VFS_OP_FSYNC_SEND, true, handle, "%s", fsp_str_do_log(fsp)); + return req; +} + +static void smb_full_audit_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_full_audit_fsync_state *state = tevent_req_data( + req, struct smb_full_audit_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static int smb_full_audit_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_full_audit_fsync_state *state = tevent_req_data( + req, struct smb_full_audit_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + do_log(SMB_VFS_OP_FSYNC_RECV, false, state->handle, "%s", + fsp_str_do_log(state->fsp)); + return -1; + } + + do_log(SMB_VFS_OP_FSYNC_RECV, (state->ret >= 0), state->handle, "%s", + fsp_str_do_log(state->fsp)); + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int smb_full_audit_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result; + + result = SMB_VFS_NEXT_STAT(handle, smb_fname); + + do_log(SMB_VFS_OP_STAT, (result >= 0), handle, "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static int smb_full_audit_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int result; + + result = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + + do_log(SMB_VFS_OP_FSTAT, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int result; + + result = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + + do_log(SMB_VFS_OP_LSTAT, (result >= 0), handle, "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static int smb_full_audit_fstatat( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + int result; + + result = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags); + + do_log(SMB_VFS_OP_FSTATAT, + (result >= 0), + handle, + "%s/%s", + fsp_str_do_log(dirfsp), + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} +static uint64_t smb_full_audit_get_alloc_size(vfs_handle_struct *handle, + files_struct *fsp, const SMB_STRUCT_STAT *sbuf) +{ + uint64_t result; + + result = SMB_VFS_NEXT_GET_ALLOC_SIZE(handle, fsp, sbuf); + + do_log(SMB_VFS_OP_GET_ALLOC_SIZE, (result != (uint64_t)-1), handle, + "%llu", (unsigned long long)result); + + return result; +} + +static int smb_full_audit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + + do_log(SMB_VFS_OP_UNLINKAT, (result >= 0), handle, "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + return result; +} + +static int smb_full_audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, + mode_t mode) +{ + int result; + + result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + + do_log(SMB_VFS_OP_FCHMOD, (result >= 0), handle, + "%s|%o", fsp_str_do_log(fsp), mode); + + return result; +} + +static int smb_full_audit_fchown(vfs_handle_struct *handle, files_struct *fsp, + uid_t uid, gid_t gid) +{ + int result; + + result = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid); + + do_log(SMB_VFS_OP_FCHOWN, (result >= 0), handle, "%s|%ld|%ld", + fsp_str_do_log(fsp), (long int)uid, (long int)gid); + + return result; +} + +static int smb_full_audit_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int result; + + result = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid); + + do_log(SMB_VFS_OP_LCHOWN, (result >= 0), handle, "%s|%ld|%ld", + smb_fname->base_name, (long int)uid, (long int)gid); + + return result; +} + +static int smb_full_audit_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int result; + + result = SMB_VFS_NEXT_CHDIR(handle, smb_fname); + + do_log(SMB_VFS_OP_CHDIR, + (result >= 0), + handle, + "chdir|%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static struct smb_filename *smb_full_audit_getwd(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + struct smb_filename *result; + + result = SMB_VFS_NEXT_GETWD(handle, ctx); + + do_log(SMB_VFS_OP_GETWD, (result != NULL), handle, "%s", + result == NULL? "" : result->base_name); + + return result; +} + +static int smb_full_audit_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int result; + time_t create_time = convert_timespec_to_time_t(ft->create_time); + time_t atime = convert_timespec_to_time_t(ft->atime); + time_t mtime = convert_timespec_to_time_t(ft->mtime); + time_t ctime = convert_timespec_to_time_t(ft->ctime); + const char *create_time_str = ""; + const char *atime_str = ""; + const char *mtime_str = ""; + const char *ctime_str = ""; + TALLOC_CTX *frame = talloc_stackframe(); + + if (frame == NULL) { + errno = ENOMEM; + return -1; + } + + result = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + + if (create_time > 0) { + create_time_str = timestring(frame, create_time); + } + if (atime > 0) { + atime_str = timestring(frame, atime); + } + if (mtime > 0) { + mtime_str = timestring(frame, mtime); + } + if (ctime > 0) { + ctime_str = timestring(frame, ctime); + } + + do_log(SMB_VFS_OP_FNTIMES, + (result >= 0), + handle, + "%s|%s|%s|%s|%s", + fsp_str_do_log(fsp), + create_time_str, + atime_str, + mtime_str, + ctime_str); + + TALLOC_FREE(frame); + + return result; +} + +static int smb_full_audit_ftruncate(vfs_handle_struct *handle, files_struct *fsp, + off_t len) +{ + int result; + + result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len); + + do_log(SMB_VFS_OP_FTRUNCATE, (result >= 0), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_fallocate(vfs_handle_struct *handle, files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + int result; + + result = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + + do_log(SMB_VFS_OP_FALLOCATE, (result >= 0), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static bool smb_full_audit_lock(vfs_handle_struct *handle, files_struct *fsp, + int op, off_t offset, off_t count, int type) +{ + bool result; + + result = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type); + + do_log(SMB_VFS_OP_LOCK, result, handle, "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_filesystem_sharemode(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + int result; + + result = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle, + fsp, + share_access, + access_mask); + + do_log(SMB_VFS_OP_FILESYSTEM_SHAREMODE, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_fcntl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + int cmd, va_list cmd_arg) +{ + void *arg; + va_list dup_cmd_arg; + int result; + + va_copy(dup_cmd_arg, cmd_arg); + arg = va_arg(dup_cmd_arg, void *); + result = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg); + va_end(dup_cmd_arg); + + do_log(SMB_VFS_OP_FCNTL, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_linux_setlease(vfs_handle_struct *handle, files_struct *fsp, + int leasetype) +{ + int result; + + result = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype); + + do_log(SMB_VFS_OP_LINUX_SETLEASE, (result >= 0), handle, "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static bool smb_full_audit_getlock(vfs_handle_struct *handle, files_struct *fsp, + off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid) +{ + bool result; + + result = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, ppid); + + do_log(SMB_VFS_OP_GETLOCK, result, handle, "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); + + do_log(SMB_VFS_OP_SYMLINKAT, + (result >= 0), + handle, + "%s|%s", + link_contents->base_name, + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + + return result; +} + +static int smb_full_audit_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + + do_log(SMB_VFS_OP_READLINKAT, + (result >= 0), + handle, + "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + + return result; +} + +static int smb_full_audit_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + struct smb_filename *old_full_fname = NULL; + struct smb_filename *new_full_fname = NULL; + int result; + + old_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (old_full_fname == NULL) { + return -1; + } + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + TALLOC_FREE(old_full_fname); + return -1; + } + result = SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); + + do_log(SMB_VFS_OP_LINKAT, + (result >= 0), + handle, + "%s|%s", + smb_fname_str_do_log(handle->conn, old_full_fname), + smb_fname_str_do_log(handle->conn, new_full_fname)); + + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + + return result; +} + +static int smb_full_audit_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + struct smb_filename *full_fname = NULL; + int result; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + result = SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); + + do_log(SMB_VFS_OP_MKNODAT, + (result >= 0), + handle, + "%s", + smb_fname_str_do_log(handle->conn, full_fname)); + + TALLOC_FREE(full_fname); + + return result; +} + +static struct smb_filename *smb_full_audit_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + struct smb_filename *result_fname = NULL; + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + + do_log(SMB_VFS_OP_REALPATH, + (result_fname != NULL), + handle, + "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result_fname; +} + +static int smb_full_audit_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + int result; + + result = SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); + + do_log(SMB_VFS_OP_FCHFLAGS, + (result != 0), + handle, + "%s", + smb_fname_str_do_log(handle->conn, fsp->fsp_name)); + + return result; +} + +static struct file_id smb_full_audit_file_id_create(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id_zero = { 0 }; + struct file_id result; + struct file_id_buf idbuf; + + result = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf); + + do_log(SMB_VFS_OP_FILE_ID_CREATE, + !file_id_equal(&id_zero, &result), + handle, + "%s", + file_id_str_buf(result, &idbuf)); + + return result; +} + +static uint64_t smb_full_audit_fs_file_id(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + uint64_t result; + + result = SMB_VFS_NEXT_FS_FILE_ID(handle, sbuf); + + do_log(SMB_VFS_OP_FS_FILE_ID, + result != 0, + handle, "%" PRIu64, result); + + return result; +} + +static NTSTATUS smb_full_audit_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx, + pnum_streams, pstreams); + + do_log(SMB_VFS_OP_FSTREAMINFO, + NT_STATUS_IS_OK(result), + handle, + "%s", + smb_fname_str_do_log(handle->conn, fsp->fsp_name)); + + return result; +} + +static NTSTATUS smb_full_audit_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + + do_log(SMB_VFS_OP_GET_REAL_FILENAME_AT, + NT_STATUS_IS_OK(result), + handle, + "%s/%s->%s", + fsp_str_dbg(dirfsp), + name, + NT_STATUS_IS_OK(result) ? *found_name : ""); + + return result; +} + +static const char *smb_full_audit_connectpath( + vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + const char *result; + + result = SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname); + + do_log(SMB_VFS_OP_CONNECTPATH, + result != NULL, + handle, + "%s", + smb_fname_str_do_log(handle->conn, smb_fname)); + + return result; +} + +static NTSTATUS smb_full_audit_brl_lock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + struct lock_struct *plock) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock); + + do_log(SMB_VFS_OP_BRL_LOCK_WINDOWS, NT_STATUS_IS_OK(result), handle, + "%s:%llu-%llu. type=%d.", + fsp_str_do_log(brl_fsp(br_lck)), + (unsigned long long)plock->start, + (unsigned long long)plock->size, + plock->lock_type); + + return result; +} + +static bool smb_full_audit_brl_unlock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + const struct lock_struct *plock) +{ + bool result; + + result = SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock); + + do_log(SMB_VFS_OP_BRL_UNLOCK_WINDOWS, (result == 0), handle, + "%s:%llu-%llu:%d", fsp_str_do_log(brl_fsp(br_lck)), + (unsigned long long)plock->start, + (unsigned long long)plock->size, + plock->lock_type); + + return result; +} + +static bool smb_full_audit_strict_lock_check(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct lock_struct *plock) +{ + bool result; + + result = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock); + + do_log(SMB_VFS_OP_STRICT_LOCK_CHECK, result, handle, + "%s:%llu-%llu:%d", fsp_str_do_log(fsp), + (unsigned long long)plock->start, + (unsigned long long)plock->size, + plock->lock_type); + + return result; +} + +static NTSTATUS smb_full_audit_translate_name(struct vfs_handle_struct *handle, + const char *name, + enum vfs_translate_direction direction, + TALLOC_CTX *mem_ctx, + char **mapped_name) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_TRANSLATE_NAME(handle, name, direction, mem_ctx, + mapped_name); + + do_log(SMB_VFS_OP_TRANSLATE_NAME, NT_STATUS_IS_OK(result), handle, ""); + + return result; +} + +static NTSTATUS smb_full_audit_parent_pathname(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const struct smb_filename *smb_fname_in, + struct smb_filename **parent_dir_out, + struct smb_filename **atname_out) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_PARENT_PATHNAME(handle, + mem_ctx, + smb_fname_in, + parent_dir_out, + atname_out); + do_log(SMB_VFS_OP_CONNECTPATH, + NT_STATUS_IS_OK(result), + handle, + "%s", + smb_fname_str_do_log(handle->conn, smb_fname_in)); + + return result; +} + +static NTSTATUS smb_full_audit_fsctl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *ctx, + uint32_t function, + uint16_t req_flags, + const uint8_t *_in_data, + uint32_t in_len, + uint8_t **_out_data, + uint32_t max_out_len, + uint32_t *out_len) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_FSCTL(handle, + fsp, + ctx, + function, + req_flags, + _in_data, + in_len, + _out_data, + max_out_len, + out_len); + + do_log(SMB_VFS_OP_FSCTL, NT_STATUS_IS_OK(result), handle, ""); + + return result; +} + +static struct tevent_req *smb_full_audit_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + + req = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp, + fsctl, ttl, offset, to_copy); + + do_log(SMB_VFS_OP_OFFLOAD_READ_SEND, req, handle, ""); + + return req; +} + +static NTSTATUS smb_full_audit_offload_read_recv( + struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *_token_blob) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(req, handle, mem_ctx, + flags, xferlen, _token_blob); + + do_log(SMB_VFS_OP_OFFLOAD_READ_RECV, NT_STATUS_IS_OK(status), handle, ""); + + return status; +} + +static struct tevent_req *smb_full_audit_offload_write_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req; + + req = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, mem_ctx, ev, + fsctl, token, transfer_offset, + dest_fsp, dest_off, num); + + do_log(SMB_VFS_OP_OFFLOAD_WRITE_SEND, req, handle, ""); + + return req; +} + +static NTSTATUS smb_full_audit_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(handle, req, copied); + + do_log(SMB_VFS_OP_OFFLOAD_WRITE_RECV, NT_STATUS_IS_OK(result), handle, ""); + + return result; +} + +static NTSTATUS smb_full_audit_fget_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_FGET_COMPRESSION(handle, mem_ctx, fsp, + _compression_fmt); + + do_log(SMB_VFS_OP_FGET_COMPRESSION, NT_STATUS_IS_OK(result), handle, + "%s", + fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_set_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp, + compression_fmt); + + do_log(SMB_VFS_OP_SET_COMPRESSION, NT_STATUS_IS_OK(result), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **pattr_data) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_FREADDIR_ATTR(handle, fsp, mem_ctx, pattr_data); + + do_log(SMB_VFS_OP_FREADDIR_ATTR, + NT_STATUS_IS_OK(status), + handle, + "%s", + fsp_str_do_log(fsp)); + + return status; +} + +struct smb_full_audit_get_dos_attributes_state { + struct vfs_aio_state aio_state; + vfs_handle_struct *handle; + files_struct *dir_fsp; + const struct smb_filename *smb_fname; + uint32_t dosmode; +}; + +static void smb_full_audit_get_dos_attributes_done(struct tevent_req *subreq); + +static struct tevent_req *smb_full_audit_get_dos_attributes_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + struct smb_filename *smb_fname) +{ + struct tevent_req *req = NULL; + struct smb_full_audit_get_dos_attributes_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb_full_audit_get_dos_attributes_state); + if (req == NULL) { + do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, + false, + handle, + "%s/%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name); + return NULL; + } + *state = (struct smb_full_audit_get_dos_attributes_state) { + .handle = handle, + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + }; + + subreq = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_SEND(mem_ctx, + ev, + handle, + dir_fsp, + smb_fname); + if (tevent_req_nomem(subreq, req)) { + do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, + false, + handle, + "%s/%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + smb_full_audit_get_dos_attributes_done, + req); + + do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, + true, + handle, + "%s/%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name); + + return req; +} + +static void smb_full_audit_get_dos_attributes_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb_full_audit_get_dos_attributes_state *state = + tevent_req_data(req, + struct smb_full_audit_get_dos_attributes_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_RECV(subreq, + &state->aio_state, + &state->dosmode); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; +} + +static NTSTATUS smb_full_audit_get_dos_attributes_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + uint32_t *dosmode) +{ + struct smb_full_audit_get_dos_attributes_state *state = + tevent_req_data(req, + struct smb_full_audit_get_dos_attributes_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV, + false, + state->handle, + "%s/%s", + fsp_str_do_log(state->dir_fsp), + state->smb_fname->base_name); + tevent_req_received(req); + return status; + } + + do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV, + true, + state->handle, + "%s/%s", + fsp_str_do_log(state->dir_fsp), + state->smb_fname->base_name); + + *aio_state = state->aio_state; + *dosmode = state->dosmode; + tevent_req_received(req); + return NT_STATUS_OK; +} + +static NTSTATUS smb_full_audit_fget_dos_attributes( + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, + fsp, + dosmode); + + do_log(SMB_VFS_OP_FGET_DOS_ATTRIBUTES, + NT_STATUS_IS_OK(status), + handle, + "%s", + fsp_str_do_log(fsp)); + + return status; +} + +static NTSTATUS smb_full_audit_fset_dos_attributes( + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + NTSTATUS status; + + status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, + fsp, + dosmode); + + do_log(SMB_VFS_OP_FSET_DOS_ATTRIBUTES, + NT_STATUS_IS_OK(status), + handle, + "%s", + fsp_str_do_log(fsp)); + + return status; +} + +static NTSTATUS smb_full_audit_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + + do_log(SMB_VFS_OP_FGET_NT_ACL, NT_STATUS_IS_OK(result), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + struct vfs_full_audit_private_data *pd; + NTSTATUS result; + char *sd = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, pd, + struct vfs_full_audit_private_data, + return NT_STATUS_INTERNAL_ERROR); + + if (pd->log_secdesc) { + sd = sddl_encode(talloc_tos(), psd, get_global_sam_sid()); + } + + result = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + + do_log(SMB_VFS_OP_FSET_NT_ACL, NT_STATUS_IS_OK(result), handle, + "%s [%s]", fsp_str_do_log(fsp), sd ? sd : ""); + + TALLOC_FREE(sd); + + return result; +} + +static NTSTATUS smb_full_audit_audit_file(struct vfs_handle_struct *handle, + struct smb_filename *file, + struct security_acl *sacl, + uint32_t access_requested, + uint32_t access_denied) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_AUDIT_FILE(handle, + file, + sacl, + access_requested, + access_denied); + + do_log(SMB_VFS_OP_AUDIT_FILE, NT_STATUS_IS_OK(result), handle, + "%s", + smb_fname_str_do_log(handle->conn, file)); + + return result; +} + +static SMB_ACL_T smb_full_audit_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result; + + result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, + fsp, + type, + mem_ctx); + + do_log(SMB_VFS_OP_SYS_ACL_GET_FD, (result != NULL), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_sys_acl_blob_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **blob_description, + DATA_BLOB *blob) +{ + int result; + + result = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, blob_description, blob); + + do_log(SMB_VFS_OP_SYS_ACL_BLOB_GET_FD, (result >= 0), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_sys_acl_set_fd(vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + int result; + + result = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); + + do_log(SMB_VFS_OP_SYS_ACL_SET_FD, (result >= 0), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_sys_acl_delete_def_fd(vfs_handle_struct *handle, + struct files_struct *fsp) +{ + int result; + + result = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp); + + do_log(SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD, + (result >= 0), + handle, + "%s", + fsp_str_do_log(fsp)); + + return result; +} + +struct smb_full_audit_getxattrat_state { + struct vfs_aio_state aio_state; + vfs_handle_struct *handle; + files_struct *dir_fsp; + const struct smb_filename *smb_fname; + const char *xattr_name; + ssize_t xattr_size; + uint8_t *xattr_value; +}; + +static void smb_full_audit_getxattrat_done(struct tevent_req *subreq); + +static struct tevent_req *smb_full_audit_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb_full_audit_getxattrat_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb_full_audit_getxattrat_state); + if (req == NULL) { + do_log(SMB_VFS_OP_GETXATTRAT_SEND, + false, + handle, + "%s/%s|%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name, + xattr_name); + return NULL; + } + *state = (struct smb_full_audit_getxattrat_state) { + .handle = handle, + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + .xattr_name = xattr_name, + }; + + subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state, + ev, + handle, + dir_fsp, + smb_fname, + xattr_name, + alloc_hint); + if (tevent_req_nomem(subreq, req)) { + do_log(SMB_VFS_OP_GETXATTRAT_SEND, + false, + handle, + "%s/%s|%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name, + xattr_name); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_full_audit_getxattrat_done, req); + + do_log(SMB_VFS_OP_GETXATTRAT_SEND, + true, + handle, + "%s/%s|%s", + fsp_str_do_log(dir_fsp), + smb_fname->base_name, + xattr_name); + + return req; +} + +static void smb_full_audit_getxattrat_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_full_audit_getxattrat_state *state = tevent_req_data( + req, struct smb_full_audit_getxattrat_state); + + state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq, + &state->aio_state, + state, + &state->xattr_value); + TALLOC_FREE(subreq); + if (state->xattr_size == -1) { + tevent_req_error(req, state->aio_state.error); + return; + } + + tevent_req_done(req); +} + +static ssize_t smb_full_audit_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct smb_full_audit_getxattrat_state *state = tevent_req_data( + req, struct smb_full_audit_getxattrat_state); + ssize_t xattr_size; + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + do_log(SMB_VFS_OP_GETXATTRAT_RECV, + false, + state->handle, + "%s/%s|%s", + fsp_str_do_log(state->dir_fsp), + state->smb_fname->base_name, + state->xattr_name); + tevent_req_received(req); + return -1; + } + + do_log(SMB_VFS_OP_GETXATTRAT_RECV, + true, + state->handle, + "%s/%s|%s", + fsp_str_do_log(state->dir_fsp), + state->smb_fname->base_name, + state->xattr_name); + + *aio_state = state->aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +static ssize_t smb_full_audit_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, void *value, size_t size) +{ + ssize_t result; + + result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); + + do_log(SMB_VFS_OP_FGETXATTR, (result >= 0), handle, + "%s|%s", fsp_str_do_log(fsp), name); + + return result; +} + +static ssize_t smb_full_audit_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + ssize_t result; + + result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); + + do_log(SMB_VFS_OP_FLISTXATTR, (result >= 0), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static int smb_full_audit_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + int result; + + result = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); + + do_log(SMB_VFS_OP_FREMOVEXATTR, (result >= 0), handle, + "%s|%s", fsp_str_do_log(fsp), name); + + return result; +} + +static int smb_full_audit_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name, + const void *value, size_t size, int flags) +{ + int result; + + result = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); + + do_log(SMB_VFS_OP_FSETXATTR, (result >= 0), handle, + "%s|%s", fsp_str_do_log(fsp), name); + + return result; +} + +static bool smb_full_audit_aio_force(struct vfs_handle_struct *handle, + struct files_struct *fsp) +{ + bool result; + + result = SMB_VFS_NEXT_AIO_FORCE(handle, fsp); + do_log(SMB_VFS_OP_AIO_FORCE, result, handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_durable_cookie(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cookie) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_DURABLE_COOKIE(handle, + fsp, + mem_ctx, + cookie); + + do_log(SMB_VFS_OP_DURABLE_COOKIE, NT_STATUS_IS_OK(result), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_durable_disconnect( + struct vfs_handle_struct *handle, + struct files_struct *fsp, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + DATA_BLOB *new_cookie) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_DURABLE_DISCONNECT(handle, + fsp, + old_cookie, + mem_ctx, + new_cookie); + + do_log(SMB_VFS_OP_DURABLE_DISCONNECT, NT_STATUS_IS_OK(result), handle, + "%s", fsp_str_do_log(fsp)); + + return result; +} + +static NTSTATUS smb_full_audit_durable_reconnect( + struct vfs_handle_struct *handle, + struct smb_request *smb1req, + struct smbXsrv_open *op, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + struct files_struct **fsp, + DATA_BLOB *new_cookie) +{ + NTSTATUS result; + + result = SMB_VFS_NEXT_DURABLE_RECONNECT(handle, + smb1req, + op, + old_cookie, + mem_ctx, + fsp, + new_cookie); + + do_log(SMB_VFS_OP_DURABLE_RECONNECT, + NT_STATUS_IS_OK(result), + handle, + ""); + + return result; +} + +static struct vfs_fn_pointers vfs_full_audit_fns = { + + /* Disk operations */ + + .connect_fn = smb_full_audit_connect, + .disconnect_fn = smb_full_audit_disconnect, + .disk_free_fn = smb_full_audit_disk_free, + .get_quota_fn = smb_full_audit_get_quota, + .set_quota_fn = smb_full_audit_set_quota, + .get_shadow_copy_data_fn = smb_full_audit_get_shadow_copy_data, + .statvfs_fn = smb_full_audit_statvfs, + .fs_capabilities_fn = smb_full_audit_fs_capabilities, + .get_dfs_referrals_fn = smb_full_audit_get_dfs_referrals, + .create_dfs_pathat_fn = smb_full_audit_create_dfs_pathat, + .read_dfs_pathat_fn = smb_full_audit_read_dfs_pathat, + .fdopendir_fn = smb_full_audit_fdopendir, + .readdir_fn = smb_full_audit_readdir, + .rewind_dir_fn = smb_full_audit_rewinddir, + .mkdirat_fn = smb_full_audit_mkdirat, + .closedir_fn = smb_full_audit_closedir, + .openat_fn = smb_full_audit_openat, + .create_file_fn = smb_full_audit_create_file, + .close_fn = smb_full_audit_close, + .pread_fn = smb_full_audit_pread, + .pread_send_fn = smb_full_audit_pread_send, + .pread_recv_fn = smb_full_audit_pread_recv, + .pwrite_fn = smb_full_audit_pwrite, + .pwrite_send_fn = smb_full_audit_pwrite_send, + .pwrite_recv_fn = smb_full_audit_pwrite_recv, + .lseek_fn = smb_full_audit_lseek, + .sendfile_fn = smb_full_audit_sendfile, + .recvfile_fn = smb_full_audit_recvfile, + .renameat_fn = smb_full_audit_renameat, + .fsync_send_fn = smb_full_audit_fsync_send, + .fsync_recv_fn = smb_full_audit_fsync_recv, + .stat_fn = smb_full_audit_stat, + .fstat_fn = smb_full_audit_fstat, + .lstat_fn = smb_full_audit_lstat, + .fstatat_fn = smb_full_audit_fstatat, + .get_alloc_size_fn = smb_full_audit_get_alloc_size, + .unlinkat_fn = smb_full_audit_unlinkat, + .fchmod_fn = smb_full_audit_fchmod, + .fchown_fn = smb_full_audit_fchown, + .lchown_fn = smb_full_audit_lchown, + .chdir_fn = smb_full_audit_chdir, + .getwd_fn = smb_full_audit_getwd, + .fntimes_fn = smb_full_audit_fntimes, + .ftruncate_fn = smb_full_audit_ftruncate, + .fallocate_fn = smb_full_audit_fallocate, + .lock_fn = smb_full_audit_lock, + .filesystem_sharemode_fn = smb_full_audit_filesystem_sharemode, + .fcntl_fn = smb_full_audit_fcntl, + .linux_setlease_fn = smb_full_audit_linux_setlease, + .getlock_fn = smb_full_audit_getlock, + .symlinkat_fn = smb_full_audit_symlinkat, + .readlinkat_fn = smb_full_audit_readlinkat, + .linkat_fn = smb_full_audit_linkat, + .mknodat_fn = smb_full_audit_mknodat, + .realpath_fn = smb_full_audit_realpath, + .fchflags_fn = smb_full_audit_fchflags, + .file_id_create_fn = smb_full_audit_file_id_create, + .fs_file_id_fn = smb_full_audit_fs_file_id, + .offload_read_send_fn = smb_full_audit_offload_read_send, + .offload_read_recv_fn = smb_full_audit_offload_read_recv, + .offload_write_send_fn = smb_full_audit_offload_write_send, + .offload_write_recv_fn = smb_full_audit_offload_write_recv, + .fget_compression_fn = smb_full_audit_fget_compression, + .set_compression_fn = smb_full_audit_set_compression, + .snap_check_path_fn = smb_full_audit_snap_check_path, + .snap_create_fn = smb_full_audit_snap_create, + .snap_delete_fn = smb_full_audit_snap_delete, + .fstreaminfo_fn = smb_full_audit_fstreaminfo, + .get_real_filename_at_fn = smb_full_audit_get_real_filename_at, + .connectpath_fn = smb_full_audit_connectpath, + .brl_lock_windows_fn = smb_full_audit_brl_lock_windows, + .brl_unlock_windows_fn = smb_full_audit_brl_unlock_windows, + .strict_lock_check_fn = smb_full_audit_strict_lock_check, + .translate_name_fn = smb_full_audit_translate_name, + .parent_pathname_fn = smb_full_audit_parent_pathname, + .fsctl_fn = smb_full_audit_fsctl, + .get_dos_attributes_send_fn = smb_full_audit_get_dos_attributes_send, + .get_dos_attributes_recv_fn = smb_full_audit_get_dos_attributes_recv, + .fget_dos_attributes_fn = smb_full_audit_fget_dos_attributes, + .fset_dos_attributes_fn = smb_full_audit_fset_dos_attributes, + .fget_nt_acl_fn = smb_full_audit_fget_nt_acl, + .fset_nt_acl_fn = smb_full_audit_fset_nt_acl, + .audit_file_fn = smb_full_audit_audit_file, + .sys_acl_get_fd_fn = smb_full_audit_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = smb_full_audit_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = smb_full_audit_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = smb_full_audit_sys_acl_delete_def_fd, + .getxattrat_send_fn = smb_full_audit_getxattrat_send, + .getxattrat_recv_fn = smb_full_audit_getxattrat_recv, + .fgetxattr_fn = smb_full_audit_fgetxattr, + .flistxattr_fn = smb_full_audit_flistxattr, + .fremovexattr_fn = smb_full_audit_fremovexattr, + .fsetxattr_fn = smb_full_audit_fsetxattr, + .aio_force_fn = smb_full_audit_aio_force, + .durable_cookie_fn = smb_full_audit_durable_cookie, + .durable_disconnect_fn = smb_full_audit_durable_disconnect, + .durable_reconnect_fn = smb_full_audit_durable_reconnect, + .freaddir_attr_fn = smb_full_audit_freaddir_attr, +}; + +static_decl_vfs; +NTSTATUS vfs_full_audit_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + smb_vfs_assert_all_fns(&vfs_full_audit_fns, "full_audit"); + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "full_audit", + &vfs_full_audit_fns); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_full_audit_debug_level = debug_add_class("full_audit"); + if (vfs_full_audit_debug_level == -1) { + vfs_full_audit_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_full_audit: Couldn't register custom debugging " + "class!\n")); + } else { + DEBUG(10, ("vfs_full_audit: Debug class number of " + "'full_audit': %d\n", vfs_full_audit_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_glusterfs.c b/source3/modules/vfs_glusterfs.c new file mode 100644 index 0000000..235329f --- /dev/null +++ b/source3/modules/vfs_glusterfs.c @@ -0,0 +1,2673 @@ +/* + Unix SMB/CIFS implementation. + + Wrap GlusterFS GFAPI calls in vfs functions. + + Copyright (c) 2013 Anand Avati + + 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 . +*/ + +/** + * @file vfs_glusterfs.c + * @author Anand Avati + * @date May 2013 + * @brief Samba VFS module for glusterfs + * + * @todo + * - sendfile/recvfile support + * + * A Samba VFS module for GlusterFS, based on Gluster's libgfapi. + * This is a "bottom" vfs module (not something to be stacked on top of + * another module), and translates (most) calls to the closest actions + * available in libgfapi. + * + */ + +#include "includes.h" +#include "smbd/smbd.h" +#include +#include +#include "lib/util/dlinklist.h" +#include "lib/util/tevent_unix.h" +#include "smbd/globals.h" +#include "lib/util/sys_rw.h" +#include "smbprofile.h" +#include "modules/posixacl_xattr.h" +#include "lib/pthreadpool/pthreadpool_tevent.h" + +#define DEFAULT_VOLFILE_SERVER "localhost" +#define GLUSTER_NAME_MAX 255 + +/** + * Helper to convert struct stat to struct stat_ex. + */ +static void smb_stat_ex_from_stat(struct stat_ex *dst, const struct stat *src) +{ + ZERO_STRUCTP(dst); + + dst->st_ex_dev = src->st_dev; + dst->st_ex_ino = src->st_ino; + dst->st_ex_mode = src->st_mode; + dst->st_ex_nlink = src->st_nlink; + dst->st_ex_uid = src->st_uid; + dst->st_ex_gid = src->st_gid; + dst->st_ex_rdev = src->st_rdev; + dst->st_ex_size = src->st_size; + dst->st_ex_atime.tv_sec = src->st_atime; + dst->st_ex_mtime.tv_sec = src->st_mtime; + dst->st_ex_ctime.tv_sec = src->st_ctime; + dst->st_ex_btime.tv_sec = src->st_mtime; + dst->st_ex_blksize = src->st_blksize; + dst->st_ex_blocks = src->st_blocks; +#ifdef STAT_HAVE_NSEC + dst->st_ex_atime.tv_nsec = src->st_atime_nsec; + dst->st_ex_mtime.tv_nsec = src->st_mtime_nsec; + dst->st_ex_ctime.tv_nsec = src->st_ctime_nsec; + dst->st_ex_btime.tv_nsec = src->st_mtime_nsec; +#endif +} + +/* pre-opened glfs_t */ + +static struct glfs_preopened { + char *volume; + char *connectpath; + glfs_t *fs; + int ref; + struct glfs_preopened *next, *prev; +} *glfs_preopened; + + +static int glfs_set_preopened(const char *volume, const char *connectpath, glfs_t *fs) +{ + struct glfs_preopened *entry = NULL; + + entry = talloc_zero(NULL, struct glfs_preopened); + if (!entry) { + errno = ENOMEM; + return -1; + } + + entry->volume = talloc_strdup(entry, volume); + if (!entry->volume) { + talloc_free(entry); + errno = ENOMEM; + return -1; + } + + entry->connectpath = talloc_strdup(entry, connectpath); + if (entry->connectpath == NULL) { + talloc_free(entry); + errno = ENOMEM; + return -1; + } + + entry->fs = fs; + entry->ref = 1; + + DLIST_ADD(glfs_preopened, entry); + + return 0; +} + +static glfs_t *glfs_find_preopened(const char *volume, const char *connectpath) +{ + struct glfs_preopened *entry = NULL; + + for (entry = glfs_preopened; entry; entry = entry->next) { + if (strcmp(entry->volume, volume) == 0 && + strcmp(entry->connectpath, connectpath) == 0) + { + entry->ref++; + return entry->fs; + } + } + + return NULL; +} + +static void glfs_clear_preopened(glfs_t *fs) +{ + struct glfs_preopened *entry = NULL; + + for (entry = glfs_preopened; entry; entry = entry->next) { + if (entry->fs == fs) { + if (--entry->ref) + return; + + DLIST_REMOVE(glfs_preopened, entry); + + glfs_fini(entry->fs); + talloc_free(entry); + break; + } + } +} + +static int vfs_gluster_set_volfile_servers(glfs_t *fs, + const char *volfile_servers) +{ + char *server = NULL; + size_t server_count = 0; + size_t server_success = 0; + int ret = -1; + TALLOC_CTX *frame = talloc_stackframe(); + + DBG_INFO("servers list %s\n", volfile_servers); + + while (next_token_talloc(frame, &volfile_servers, &server, " \t")) { + char *transport = NULL; + char *host = NULL; + int port = 0; + + server_count++; + DBG_INFO("server %zu %s\n", server_count, server); + + /* Determine the transport type */ + if (strncmp(server, "unix+", 5) == 0) { + port = 0; + transport = talloc_strdup(frame, "unix"); + if (!transport) { + errno = ENOMEM; + goto out; + } + host = talloc_strdup(frame, server + 5); + if (!host) { + errno = ENOMEM; + goto out; + } + } else { + char *p = NULL; + char *port_index = NULL; + + if (strncmp(server, "tcp+", 4) == 0) { + server += 4; + } + + /* IPv6 is enclosed in [] + * ':' before ']' is part of IPv6 + * ':' after ']' indicates port + */ + p = server; + if (server[0] == '[') { + server++; + p = index(server, ']'); + if (p == NULL) { + /* Malformed IPv6 */ + continue; + } + p[0] = '\0'; + p++; + } + + port_index = index(p, ':'); + + if (port_index == NULL) { + port = 0; + } else { + port = atoi(port_index + 1); + port_index[0] = '\0'; + } + transport = talloc_strdup(frame, "tcp"); + if (!transport) { + errno = ENOMEM; + goto out; + } + host = talloc_strdup(frame, server); + if (!host) { + errno = ENOMEM; + goto out; + } + } + + DBG_INFO("Calling set volfile server with params " + "transport=%s, host=%s, port=%d\n", transport, + host, port); + + ret = glfs_set_volfile_server(fs, transport, host, port); + if (ret < 0) { + DBG_WARNING("Failed to set volfile_server " + "transport=%s, host=%s, port=%d (%s)\n", + transport, host, port, strerror(errno)); + } else { + server_success++; + } + } + +out: + if (server_count == 0) { + ret = -1; + } else if (server_success < server_count) { + DBG_WARNING("Failed to set %zu out of %zu servers parsed\n", + server_count - server_success, server_count); + ret = 0; + } + + TALLOC_FREE(frame); + return ret; +} + +/* Disk Operations */ + +static int check_for_write_behind_translator(TALLOC_CTX *mem_ctx, + glfs_t *fs, + const char *volume) +{ + char *buf = NULL; + char **lines = NULL; + int numlines = 0; + int i; + char *option; + bool write_behind_present = false; + size_t newlen; + int ret; + + ret = glfs_get_volfile(fs, NULL, 0); + if (ret == 0) { + DBG_ERR("%s: Failed to get volfile for " + "volume (%s): No volfile\n", + volume, + strerror(errno)); + return -1; + } + if (ret > 0) { + DBG_ERR("%s: Invalid return %d for glfs_get_volfile for " + "volume (%s): No volfile\n", + volume, + ret, + strerror(errno)); + return -1; + } + + newlen = 0 - ret; + + buf = talloc_zero_array(mem_ctx, char, newlen); + if (buf == NULL) { + return -1; + } + + ret = glfs_get_volfile(fs, buf, newlen); + if (ret != newlen) { + TALLOC_FREE(buf); + DBG_ERR("%s: Failed to get volfile for volume (%s)\n", + volume, strerror(errno)); + return -1; + } + + option = talloc_asprintf(mem_ctx, "volume %s-write-behind", volume); + if (option == NULL) { + TALLOC_FREE(buf); + return -1; + } + + /* + * file_lines_parse() plays horrible tricks with + * the passed-in talloc pointers and the hierarchy + * which makes freeing hard to get right. + * + * As we know mem_ctx is freed by the caller, after + * this point don't free on exit and let the caller + * handle it. This violates good Samba coding practice + * but we know we're not leaking here. + */ + + lines = file_lines_parse(buf, + newlen, + &numlines, + mem_ctx); + if (lines == NULL || numlines <= 0) { + return -1; + } + /* On success, buf is now a talloc child of lines !! */ + + for (i=0; i < numlines; i++) { + if (strequal(lines[i], option)) { + write_behind_present = true; + break; + } + } + + if (write_behind_present) { + DBG_ERR("Write behind translator is enabled for " + "volume (%s), refusing to connect! " + "Please turn off the write behind translator by calling " + "'gluster volume set %s performance.write-behind off' " + "on the commandline. " + "Check the vfs_glusterfs(8) manpage for " + "further details.\n", + volume, volume); + return -1; + } + + return 0; +} + +static int vfs_gluster_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *volfile_servers; + const char *volume; + char *logfile; + int loglevel; + glfs_t *fs = NULL; + TALLOC_CTX *tmp_ctx; + int ret = 0; + bool write_behind_pass_through_set = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = -1; + goto done; + } + logfile = lp_parm_substituted_string(tmp_ctx, + lp_sub, + SNUM(handle->conn), + "glusterfs", + "logfile", + NULL); + + loglevel = lp_parm_int(SNUM(handle->conn), "glusterfs", "loglevel", -1); + + volfile_servers = lp_parm_substituted_string(tmp_ctx, + lp_sub, + SNUM(handle->conn), + "glusterfs", + "volfile_server", + NULL); + if (volfile_servers == NULL) { + volfile_servers = DEFAULT_VOLFILE_SERVER; + } + + volume = lp_parm_const_string(SNUM(handle->conn), "glusterfs", "volume", + NULL); + if (volume == NULL) { + volume = service; + } + + fs = glfs_find_preopened(volume, handle->conn->connectpath); + if (fs) { + goto done; + } + + fs = glfs_new(volume); + if (fs == NULL) { + ret = -1; + goto done; + } + + ret = vfs_gluster_set_volfile_servers(fs, volfile_servers); + if (ret < 0) { + DBG_ERR("Failed to set volfile_servers from list %s\n", + volfile_servers); + goto done; + } + + ret = glfs_set_xlator_option(fs, "*-md-cache", "cache-posix-acl", + "true"); + if (ret < 0) { + DEBUG(0, ("%s: Failed to set xlator options\n", volume)); + goto done; + } + + ret = glfs_set_xlator_option(fs, "*-md-cache", "cache-selinux", + "true"); + if (ret < 0) { + DEBUG(0, ("%s: Failed to set xlator options\n", volume)); + goto done; + } + + ret = glfs_set_xlator_option(fs, "*-snapview-client", + "snapdir-entry-path", + handle->conn->connectpath); + if (ret < 0) { + DEBUG(0, ("%s: Failed to set xlator option:" + " snapdir-entry-path\n", volume)); + goto done; + } + +#ifdef HAVE_GFAPI_VER_7_9 + ret = glfs_set_xlator_option(fs, "*-write-behind", "pass-through", + "true"); + if (ret < 0) { + DBG_ERR("%s: Failed to set xlator option: pass-through\n", + volume); + goto done; + } + write_behind_pass_through_set = true; +#endif + + ret = glfs_set_logging(fs, logfile, loglevel); + if (ret < 0) { + DEBUG(0, ("%s: Failed to set logfile %s loglevel %d\n", + volume, logfile, loglevel)); + goto done; + } + + ret = glfs_init(fs); + if (ret < 0) { + DEBUG(0, ("%s: Failed to initialize volume (%s)\n", + volume, strerror(errno))); + goto done; + } + + if (!write_behind_pass_through_set) { + ret = check_for_write_behind_translator(tmp_ctx, fs, volume); + if (ret < 0) { + goto done; + } + } + + ret = glfs_set_preopened(volume, handle->conn->connectpath, fs); + if (ret < 0) { + DEBUG(0, ("%s: Failed to register volume (%s)\n", + volume, strerror(errno))); + goto done; + } + + /* + * The shadow_copy2 module will fail to export subdirectories + * of a gluster volume unless we specify the mount point, + * because the detection fails if the file system is not + * locally mounted: + * https://bugzilla.samba.org/show_bug.cgi?id=13091 + */ + lp_do_parameter(SNUM(handle->conn), "shadow:mountpoint", "/"); + + /* + * Unless we have an async implementation of getxattrat turn this off. + */ + lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false"); + +done: + if (ret < 0) { + if (fs) + glfs_fini(fs); + } else { + DBG_ERR("%s: Initialized volume from servers %s\n", + volume, volfile_servers); + handle->data = fs; + } + talloc_free(tmp_ctx); + return ret; +} + +static void vfs_gluster_disconnect(struct vfs_handle_struct *handle) +{ + glfs_t *fs = NULL; + + fs = handle->data; + + glfs_clear_preopened(fs); +} + +static uint64_t vfs_gluster_disk_free(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize_p, + uint64_t *dfree_p, + uint64_t *dsize_p) +{ + struct statvfs statvfs = { 0, }; + int ret; + + ret = glfs_statvfs(handle->data, smb_fname->base_name, &statvfs); + if (ret < 0) { + return -1; + } + + if (bsize_p != NULL) { + *bsize_p = (uint64_t)statvfs.f_bsize; /* Block size */ + } + if (dfree_p != NULL) { + *dfree_p = (uint64_t)statvfs.f_bavail; /* Available Block units */ + } + if (dsize_p != NULL) { + *dsize_p = (uint64_t)statvfs.f_blocks; /* Total Block units */ + } + + return (uint64_t)statvfs.f_bavail; +} + +static int vfs_gluster_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ + errno = ENOSYS; + return -1; +} + +static int +vfs_gluster_set_quota(struct vfs_handle_struct *handle, + enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt) +{ + errno = ENOSYS; + return -1; +} + +static int vfs_gluster_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *vfs_statvfs) +{ + struct statvfs statvfs = { 0, }; + int ret; + + ret = glfs_statvfs(handle->data, smb_fname->base_name, &statvfs); + if (ret < 0) { + DEBUG(0, ("glfs_statvfs(%s) failed: %s\n", + smb_fname->base_name, strerror(errno))); + return -1; + } + + ZERO_STRUCTP(vfs_statvfs); + + vfs_statvfs->OptimalTransferSize = statvfs.f_frsize; + vfs_statvfs->BlockSize = statvfs.f_bsize; + vfs_statvfs->TotalBlocks = statvfs.f_blocks; + vfs_statvfs->BlocksAvail = statvfs.f_bfree; + vfs_statvfs->UserBlocksAvail = statvfs.f_bavail; + vfs_statvfs->TotalFileNodes = statvfs.f_files; + vfs_statvfs->FreeFileNodes = statvfs.f_ffree; + vfs_statvfs->FsIdentifier = statvfs.f_fsid; + vfs_statvfs->FsCapabilities = + FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES; + + return ret; +} + +static uint32_t vfs_gluster_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES; + +#ifdef HAVE_GFAPI_VER_6 + caps |= FILE_SUPPORTS_SPARSE_FILES; +#endif + +#ifdef STAT_HAVE_NSEC + *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER; +#endif + + return caps; +} + +static glfs_fd_t *vfs_gluster_fetch_glfd(struct vfs_handle_struct *handle, + const files_struct *fsp) +{ + glfs_fd_t **glfd = (glfs_fd_t **)VFS_FETCH_FSP_EXTENSION(handle, fsp); + if (glfd == NULL) { + DBG_INFO("Failed to fetch fsp extension\n"); + return NULL; + } + if (*glfd == NULL) { + DBG_INFO("Empty glfs_fd_t pointer\n"); + return NULL; + } + + return *glfd; +} + +static DIR *vfs_gluster_fdopendir(struct vfs_handle_struct *handle, + files_struct *fsp, const char *mask, + uint32_t attributes) +{ + glfs_fd_t *glfd = NULL; + + glfd = glfs_opendir(handle->data, fsp->fsp_name->base_name); + if (glfd == NULL) { + return NULL; + } + + return (DIR *)glfd; +} + +static int vfs_gluster_closedir(struct vfs_handle_struct *handle, DIR *dirp) +{ + int ret; + + START_PROFILE(syscall_closedir); + ret = glfs_closedir((void *)dirp); + END_PROFILE(syscall_closedir); + + return ret; +} + +static struct dirent *vfs_gluster_readdir(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + static char direntbuf[512]; + int ret; + struct dirent *dirent = 0; + + START_PROFILE(syscall_readdir); + + ret = glfs_readdir_r((void *)dirp, (void *)direntbuf, &dirent); + + if ((ret < 0) || (dirent == NULL)) { + END_PROFILE(syscall_readdir); + return NULL; + } + + END_PROFILE(syscall_readdir); + return dirent; +} + +static void vfs_gluster_rewinddir(struct vfs_handle_struct *handle, DIR *dirp) +{ + START_PROFILE(syscall_rewinddir); + glfs_seekdir((void *)dirp, 0); + END_PROFILE(syscall_rewinddir); +} + +static int vfs_gluster_mkdirat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_mkdirat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_mkdirat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_mkdirat(pglfd, smb_fname->base_name, mode); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_mkdirat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_mkdirat); + return -1; + } + + ret = glfs_mkdir(handle->data, full_fname->base_name, mode); + + TALLOC_FREE(full_fname); +#endif + + END_PROFILE(syscall_mkdirat); + + return ret; +} + +static int vfs_gluster_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int flags = how->flags; + struct smb_filename *full_fname = NULL; + bool have_opath = false; + bool became_root = false; + glfs_fd_t *glfd = NULL; + glfs_fd_t *pglfd = NULL; + glfs_fd_t **p_tmp; + + START_PROFILE(syscall_openat); + + if (how->resolve != 0) { + END_PROFILE(syscall_openat); + errno = ENOSYS; + return -1; + } + + p_tmp = VFS_ADD_FSP_EXTENSION(handle, fsp, glfs_fd_t *, NULL); + if (p_tmp == NULL) { + END_PROFILE(syscall_openat); + errno = ENOMEM; + return -1; + } + +#ifdef O_PATH + have_opath = true; + if (fsp->fsp_flags.is_pathref) { + flags |= O_PATH; + } +#endif + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_openat); + return -1; + } + + if (fsp->fsp_flags.is_pathref && !have_opath) { + become_root(); + became_root = true; + } + + if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) { +#ifdef HAVE_GFAPI_VER_7_11 + /* + * Fetch Gluster fd for parent directory using dirfsp + * before calling glfs_openat(); + */ + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_openat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + glfd = glfs_openat(pglfd, + smb_fname->base_name, + flags, + how->mode); +#else + /* + * Replace smb_fname with full_path constructed above. + */ + smb_fname = full_fname; +#endif + } + + if (pglfd == NULL) { + /* + * smb_fname can either be a full_path or the same one + * as received from the caller. In the latter case we + * are operating at current working directory. + */ + if (flags & O_CREAT) { + glfd = glfs_creat(handle->data, + smb_fname->base_name, + flags, + how->mode); + } else { + glfd = glfs_open(handle->data, + smb_fname->base_name, + flags); + } + } + + if (became_root) { + unbecome_root(); + } + + TALLOC_FREE(full_fname); + + fsp->fsp_flags.have_proc_fds = false; + + if (glfd == NULL) { + END_PROFILE(syscall_openat); + /* no extension destroy_fn, so no need to save errno */ + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return -1; + } + + *p_tmp = glfd; + + END_PROFILE(syscall_openat); + /* An arbitrary value for error reporting, so you know its us. */ + return 13371337; +} + +static int vfs_gluster_close(struct vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_close); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_close); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + + ret = glfs_close(glfd); + END_PROFILE(syscall_close); + + return ret; +} + +static ssize_t vfs_gluster_pread(struct vfs_handle_struct *handle, + files_struct *fsp, void *data, size_t n, + off_t offset) +{ + ssize_t ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE_BYTES(syscall_pread, n); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE_BYTES(syscall_pread); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + +#ifdef HAVE_GFAPI_VER_7_6 + ret = glfs_pread(glfd, data, n, offset, 0, NULL); +#else + ret = glfs_pread(glfd, data, n, offset, 0); +#endif + END_PROFILE_BYTES(syscall_pread); + + return ret; +} + +struct vfs_gluster_pread_state { + ssize_t ret; + glfs_fd_t *fd; + void *buf; + size_t count; + off_t offset; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_gluster_pread_do(void *private_data); +static void vfs_gluster_pread_done(struct tevent_req *subreq); +static int vfs_gluster_pread_state_destructor(struct vfs_gluster_pread_state *state); + +static struct tevent_req *vfs_gluster_pread_send(struct vfs_handle_struct + *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + files_struct *fsp, + void *data, size_t n, + off_t offset) +{ + struct vfs_gluster_pread_state *state; + struct tevent_req *req, *subreq; + + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_pread_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = glfd; + state->buf = data; + state->count = n; + state->offset = offset; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p, + state->profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, + vfs_gluster_pread_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_gluster_pread_done, req); + + talloc_set_destructor(state, vfs_gluster_pread_state_destructor); + + return req; +} + +static void vfs_gluster_pread_do(void *private_data) +{ + struct vfs_gluster_pread_state *state = talloc_get_type_abort( + private_data, struct vfs_gluster_pread_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + do { +#ifdef HAVE_GFAPI_VER_7_6 + state->ret = glfs_pread(state->fd, state->buf, state->count, + state->offset, 0, NULL); +#else + state->ret = glfs_pread(state->fd, state->buf, state->count, + state->offset, 0); +#endif + } while ((state->ret == -1) && (errno == EINTR)); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_gluster_pread_state_destructor(struct vfs_gluster_pread_state *state) +{ + return -1; +} + +static void vfs_gluster_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_gluster_pread_state *state = tevent_req_data( + req, struct vfs_gluster_pread_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_gluster_pread_do(state); + } + + tevent_req_done(req); +} + +static ssize_t vfs_gluster_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_gluster_pread_state *state = tevent_req_data( + req, struct vfs_gluster_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct vfs_gluster_pwrite_state { + ssize_t ret; + glfs_fd_t *fd; + const void *buf; + size_t count; + off_t offset; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_gluster_pwrite_do(void *private_data); +static void vfs_gluster_pwrite_done(struct tevent_req *subreq); +static int vfs_gluster_pwrite_state_destructor(struct vfs_gluster_pwrite_state *state); + +static struct tevent_req *vfs_gluster_pwrite_send(struct vfs_handle_struct + *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + files_struct *fsp, + const void *data, size_t n, + off_t offset) +{ + struct tevent_req *req, *subreq; + struct vfs_gluster_pwrite_state *state; + + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_pwrite_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = glfd; + state->buf = data; + state->count = n; + state->offset = offset; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p, + state->profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, + vfs_gluster_pwrite_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_gluster_pwrite_done, req); + + talloc_set_destructor(state, vfs_gluster_pwrite_state_destructor); + + return req; +} + +static void vfs_gluster_pwrite_do(void *private_data) +{ + struct vfs_gluster_pwrite_state *state = talloc_get_type_abort( + private_data, struct vfs_gluster_pwrite_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + do { +#ifdef HAVE_GFAPI_VER_7_6 + state->ret = glfs_pwrite(state->fd, state->buf, state->count, + state->offset, 0, NULL, NULL); +#else + state->ret = glfs_pwrite(state->fd, state->buf, state->count, + state->offset, 0); +#endif + } while ((state->ret == -1) && (errno == EINTR)); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_gluster_pwrite_state_destructor(struct vfs_gluster_pwrite_state *state) +{ + return -1; +} + +static void vfs_gluster_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_gluster_pwrite_state *state = tevent_req_data( + req, struct vfs_gluster_pwrite_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_gluster_pwrite_do(state); + } + + tevent_req_done(req); +} + +static ssize_t vfs_gluster_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_gluster_pwrite_state *state = tevent_req_data( + req, struct vfs_gluster_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + + return state->ret; +} + +static ssize_t vfs_gluster_pwrite(struct vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + ssize_t ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE_BYTES(syscall_pwrite, n); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE_BYTES(syscall_pwrite); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + +#ifdef HAVE_GFAPI_VER_7_6 + ret = glfs_pwrite(glfd, data, n, offset, 0, NULL, NULL); +#else + ret = glfs_pwrite(glfd, data, n, offset, 0); +#endif + END_PROFILE_BYTES(syscall_pwrite); + + return ret; +} + +static off_t vfs_gluster_lseek(struct vfs_handle_struct *handle, + files_struct *fsp, off_t offset, int whence) +{ + off_t ret = 0; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_lseek); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_lseek); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_lseek(glfd, offset, whence); + END_PROFILE(syscall_lseek); + + return ret; +} + +static ssize_t vfs_gluster_sendfile(struct vfs_handle_struct *handle, int tofd, + files_struct *fromfsp, + const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + errno = ENOTSUP; + return -1; +} + +static ssize_t vfs_gluster_recvfile(struct vfs_handle_struct *handle, + int fromfd, files_struct *tofsp, + off_t offset, size_t n) +{ + errno = ENOTSUP; + return -1; +} + +static int vfs_gluster_renameat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *src_pglfd = NULL; + glfs_fd_t *dst_pglfd = NULL; + + START_PROFILE(syscall_renameat); + + src_pglfd = vfs_gluster_fetch_glfd(handle, srcfsp); + if (src_pglfd == NULL) { + END_PROFILE(syscall_renameat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + dst_pglfd = vfs_gluster_fetch_glfd(handle, dstfsp); + if (dst_pglfd == NULL) { + END_PROFILE(syscall_renameat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_renameat(src_pglfd, smb_fname_src->base_name, + dst_pglfd, smb_fname_dst->base_name); +#else + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + + START_PROFILE(syscall_renameat); + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + END_PROFILE(syscall_renameat); + errno = ENOMEM; + return -1; + } + + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + END_PROFILE(syscall_renameat); + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return -1; + } + ret = glfs_rename(handle->data, + full_fname_src->base_name, + full_fname_dst->base_name); + + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); +#endif + + END_PROFILE(syscall_renameat); + + return ret; +} + +struct vfs_gluster_fsync_state { + ssize_t ret; + glfs_fd_t *fd; + + struct vfs_aio_state vfs_aio_state; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); +}; + +static void vfs_gluster_fsync_do(void *private_data); +static void vfs_gluster_fsync_done(struct tevent_req *subreq); +static int vfs_gluster_fsync_state_destructor(struct vfs_gluster_fsync_state *state); + +static struct tevent_req *vfs_gluster_fsync_send(struct vfs_handle_struct + *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + files_struct *fsp) +{ + struct tevent_req *req, *subreq; + struct vfs_gluster_fsync_state *state; + + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_fsync_state); + if (req == NULL) { + return NULL; + } + + state->ret = -1; + state->fd = glfd; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p, + state->profile_bytes, 0); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); + + subreq = pthreadpool_tevent_job_send( + state, ev, handle->conn->sconn->pool, vfs_gluster_fsync_do, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_gluster_fsync_done, req); + + talloc_set_destructor(state, vfs_gluster_fsync_state_destructor); + + return req; +} + +static void vfs_gluster_fsync_do(void *private_data) +{ + struct vfs_gluster_fsync_state *state = talloc_get_type_abort( + private_data, struct vfs_gluster_fsync_state); + struct timespec start_time; + struct timespec end_time; + + SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes); + + PROFILE_TIMESTAMP(&start_time); + + do { +#ifdef HAVE_GFAPI_VER_7_6 + state->ret = glfs_fsync(state->fd, NULL, NULL); +#else + state->ret = glfs_fsync(state->fd); +#endif + } while ((state->ret == -1) && (errno == EINTR)); + + if (state->ret == -1) { + state->vfs_aio_state.error = errno; + } + + PROFILE_TIMESTAMP(&end_time); + + state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time); + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes); +} + +static int vfs_gluster_fsync_state_destructor(struct vfs_gluster_fsync_state *state) +{ + return -1; +} + +static void vfs_gluster_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_gluster_fsync_state *state = tevent_req_data( + req, struct vfs_gluster_fsync_state); + int ret; + + ret = pthreadpool_tevent_job_recv(subreq); + TALLOC_FREE(subreq); + SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes); + talloc_set_destructor(state, NULL); + if (ret != 0) { + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + /* + * If we get EAGAIN from pthreadpool_tevent_job_recv() this + * means the lower level pthreadpool failed to create a new + * thread. Fallback to sync processing in that case to allow + * some progress for the client. + */ + vfs_gluster_fsync_do(state); + } + + tevent_req_done(req); +} + +static int vfs_gluster_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_gluster_fsync_state *state = tevent_req_data( + req, struct vfs_gluster_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int vfs_gluster_stat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct stat st; + int ret; + + START_PROFILE(syscall_stat); + ret = glfs_stat(handle->data, smb_fname->base_name, &st); + if (ret == 0) { + smb_stat_ex_from_stat(&smb_fname->st, &st); + } + if (ret < 0 && errno != ENOENT) { + DEBUG(0, ("glfs_stat(%s) failed: %s\n", + smb_fname->base_name, strerror(errno))); + } + END_PROFILE(syscall_stat); + + return ret; +} + +static int vfs_gluster_fstat(struct vfs_handle_struct *handle, + files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + struct stat st; + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_fstat); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fstat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_fstat(glfd, &st); + if (ret == 0) { + smb_stat_ex_from_stat(sbuf, &st); + } + if (ret < 0) { + DEBUG(0, ("glfs_fstat(%d) failed: %s\n", + fsp_get_io_fd(fsp), strerror(errno))); + } + END_PROFILE(syscall_fstat); + + return ret; +} + +static int vfs_gluster_fstatat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + struct stat st; + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_fstatat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_fstatat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_fstatat(pglfd, smb_fname->base_name, &st, flags); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_fstatat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_fstatat); + return -1; + } + + ret = glfs_stat(handle->data, full_fname->base_name, &st); + + TALLOC_FREE(full_fname->base_name); +#endif + + if (ret == 0) { + smb_stat_ex_from_stat(sbuf, &st); + } + + END_PROFILE(syscall_fstatat); + + return ret; +} + +static int vfs_gluster_lstat(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct stat st; + int ret; + + START_PROFILE(syscall_lstat); + ret = glfs_lstat(handle->data, smb_fname->base_name, &st); + if (ret == 0) { + smb_stat_ex_from_stat(&smb_fname->st, &st); + } + if (ret < 0 && errno != ENOENT) { + DEBUG(0, ("glfs_lstat(%s) failed: %s\n", + smb_fname->base_name, strerror(errno))); + } + END_PROFILE(syscall_lstat); + + return ret; +} + +static uint64_t vfs_gluster_get_alloc_size(struct vfs_handle_struct *handle, + files_struct *fsp, + const SMB_STRUCT_STAT *sbuf) +{ + uint64_t ret; + + START_PROFILE(syscall_get_alloc_size); + ret = sbuf->st_ex_blocks * 512; + END_PROFILE(syscall_get_alloc_size); + + return ret; +} + +static int vfs_gluster_unlinkat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_unlinkat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_unlinkat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_unlinkat(pglfd, smb_fname->base_name, flags); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_unlinkat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_unlinkat); + return -1; + } + + if (flags & AT_REMOVEDIR) { + ret = glfs_rmdir(handle->data, full_fname->base_name); + } else { + ret = glfs_unlink(handle->data, full_fname->base_name); + } + + TALLOC_FREE(full_fname); +#endif + + END_PROFILE(syscall_unlinkat); + + return ret; +} + +static int vfs_gluster_fchmod(struct vfs_handle_struct *handle, + files_struct *fsp, mode_t mode) +{ + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_fchmod); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fchmod); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to remove xattrs. + */ + ret = glfs_fchmod(glfd, mode); + } else { + /* + * This is no longer a handle based call. + */ + ret = glfs_chmod(handle->data, fsp->fsp_name->base_name, mode); + } + END_PROFILE(syscall_fchmod); + + return ret; +} + +static int vfs_gluster_fchown(struct vfs_handle_struct *handle, + files_struct *fsp, uid_t uid, gid_t gid) +{ + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_fchown); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fchown); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_fchown(glfd, uid, gid); + END_PROFILE(syscall_fchown); + + return ret; +} + +static int vfs_gluster_lchown(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int ret; + + START_PROFILE(syscall_lchown); + ret = glfs_lchown(handle->data, smb_fname->base_name, uid, gid); + END_PROFILE(syscall_lchown); + + return ret; +} + +static int vfs_gluster_chdir(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int ret; + + START_PROFILE(syscall_chdir); + ret = glfs_chdir(handle->data, smb_fname->base_name); + END_PROFILE(syscall_chdir); + + return ret; +} + +static struct smb_filename *vfs_gluster_getwd(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + char cwd[PATH_MAX] = { '\0' }; + char *ret; + struct smb_filename *smb_fname = NULL; + + START_PROFILE(syscall_getwd); + + ret = glfs_getcwd(handle->data, cwd, PATH_MAX - 1); + END_PROFILE(syscall_getwd); + + if (ret == NULL) { + return NULL; + } + smb_fname = synthetic_smb_fname(ctx, + ret, + NULL, + NULL, + 0, + 0); + return smb_fname; +} + +static int vfs_gluster_fntimes(struct vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int ret = -1; + struct timespec times[2]; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_fntimes); + + if (is_omit_timespec(&ft->atime)) { + times[0].tv_sec = fsp->fsp_name->st.st_ex_atime.tv_sec; + times[0].tv_nsec = fsp->fsp_name->st.st_ex_atime.tv_nsec; + } else { + times[0].tv_sec = ft->atime.tv_sec; + times[0].tv_nsec = ft->atime.tv_nsec; + } + + if (is_omit_timespec(&ft->mtime)) { + times[1].tv_sec = fsp->fsp_name->st.st_ex_mtime.tv_sec; + times[1].tv_nsec = fsp->fsp_name->st.st_ex_mtime.tv_nsec; + } else { + times[1].tv_sec = ft->mtime.tv_sec; + times[1].tv_nsec = ft->mtime.tv_nsec; + } + + if ((timespec_compare(×[0], + &fsp->fsp_name->st.st_ex_atime) == 0) && + (timespec_compare(×[1], + &fsp->fsp_name->st.st_ex_mtime) == 0)) { + END_PROFILE(syscall_fntimes); + return 0; + } + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fntimes); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + if (!fsp->fsp_flags.is_pathref) { + ret = glfs_futimens(glfd, times); + } else { + ret = glfs_utimens(handle->data, + fsp->fsp_name->base_name, + times); + } + END_PROFILE(syscall_fntimes); + + return ret; +} + +static int vfs_gluster_ftruncate(struct vfs_handle_struct *handle, + files_struct *fsp, off_t offset) +{ + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_ftruncate); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_ftruncate); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + +#ifdef HAVE_GFAPI_VER_7_6 + ret = glfs_ftruncate(glfd, offset, NULL, NULL); +#else + ret = glfs_ftruncate(glfd, offset); +#endif + END_PROFILE(syscall_ftruncate); + + return ret; +} + +static int vfs_gluster_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, off_t len) +{ + int ret; +#ifdef HAVE_GFAPI_VER_6 + glfs_fd_t *glfd = NULL; + int keep_size, punch_hole; + + START_PROFILE(syscall_fallocate); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fallocate); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + keep_size = mode & VFS_FALLOCATE_FL_KEEP_SIZE; + punch_hole = mode & VFS_FALLOCATE_FL_PUNCH_HOLE; + + mode &= ~(VFS_FALLOCATE_FL_KEEP_SIZE|VFS_FALLOCATE_FL_PUNCH_HOLE); + if (mode != 0) { + END_PROFILE(syscall_fallocate); + errno = ENOTSUP; + return -1; + } + + if (punch_hole) { + ret = glfs_discard(glfd, offset, len); + if (ret != 0) { + DBG_DEBUG("glfs_discard failed: %s\n", + strerror(errno)); + } + } + + ret = glfs_fallocate(glfd, keep_size, offset, len); + END_PROFILE(syscall_fallocate); +#else + errno = ENOTSUP; + ret = -1; +#endif + return ret; +} + +static struct smb_filename *vfs_gluster_realpath(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + char *result = NULL; + struct smb_filename *result_fname = NULL; + char *resolved_path = NULL; + + START_PROFILE(syscall_realpath); + + resolved_path = SMB_MALLOC_ARRAY(char, PATH_MAX+1); + if (resolved_path == NULL) { + END_PROFILE(syscall_realpath); + errno = ENOMEM; + return NULL; + } + + result = glfs_realpath(handle->data, + smb_fname->base_name, + resolved_path); + if (result != NULL) { + result_fname = synthetic_smb_fname(ctx, + result, + NULL, + NULL, + 0, + 0); + } + + SAFE_FREE(resolved_path); + END_PROFILE(syscall_realpath); + + return result_fname; +} + +static bool vfs_gluster_lock(struct vfs_handle_struct *handle, + files_struct *fsp, int op, off_t offset, + off_t count, int type) +{ + struct flock flock = { 0, }; + int ret; + glfs_fd_t *glfd = NULL; + bool ok = false; + + START_PROFILE(syscall_fcntl_lock); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + ok = false; + goto out; + } + + flock.l_type = type; + flock.l_whence = SEEK_SET; + flock.l_start = offset; + flock.l_len = count; + flock.l_pid = 0; + + ret = glfs_posix_lock(glfd, op, &flock); + + if (op == F_GETLK) { + /* lock query, true if someone else has locked */ + if ((ret != -1) && + (flock.l_type != F_UNLCK) && + (flock.l_pid != 0) && (flock.l_pid != getpid())) { + ok = true; + goto out; + } + /* not me */ + ok = false; + goto out; + } + + if (ret == -1) { + ok = false; + goto out; + } + + ok = true; +out: + END_PROFILE(syscall_fcntl_lock); + + return ok; +} + +static int vfs_gluster_filesystem_sharemode(struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + errno = ENOSYS; + return -1; +} + +static int vfs_gluster_fcntl(vfs_handle_struct *handle, + files_struct *fsp, int cmd, va_list cmd_arg) +{ + /* + * SMB_VFS_FCNTL() is currently only called by vfs_set_blocking() to + * clear O_NONBLOCK, etc for LOCK_MAND and FIFOs. Ignore it. + */ + if (cmd == F_GETFL) { + return 0; + } else if (cmd == F_SETFL) { + va_list dup_cmd_arg; + int opt; + + va_copy(dup_cmd_arg, cmd_arg); + opt = va_arg(dup_cmd_arg, int); + va_end(dup_cmd_arg); + if (opt == 0) { + return 0; + } + DBG_ERR("unexpected fcntl SETFL(%d)\n", opt); + goto err_out; + } + DBG_ERR("unexpected fcntl: %d\n", cmd); +err_out: + errno = EINVAL; + return -1; +} + +static int vfs_gluster_linux_setlease(struct vfs_handle_struct *handle, + files_struct *fsp, int leasetype) +{ + errno = ENOSYS; + return -1; +} + +static bool vfs_gluster_getlock(struct vfs_handle_struct *handle, + files_struct *fsp, off_t *poffset, + off_t *pcount, int *ptype, pid_t *ppid) +{ + struct flock flock = { 0, }; + int ret; + glfs_fd_t *glfd = NULL; + + START_PROFILE(syscall_fcntl_getlock); + + glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + END_PROFILE(syscall_fcntl_getlock); + DBG_ERR("Failed to fetch gluster fd\n"); + return false; + } + + flock.l_type = *ptype; + flock.l_whence = SEEK_SET; + flock.l_start = *poffset; + flock.l_len = *pcount; + flock.l_pid = 0; + + ret = glfs_posix_lock(glfd, F_GETLK, &flock); + + if (ret == -1) { + END_PROFILE(syscall_fcntl_getlock); + return false; + } + + *ptype = flock.l_type; + *poffset = flock.l_start; + *pcount = flock.l_len; + *ppid = flock.l_pid; + END_PROFILE(syscall_fcntl_getlock); + + return true; +} + +static int vfs_gluster_symlinkat(struct vfs_handle_struct *handle, + const struct smb_filename *link_target, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_symlinkat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_symlinkat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_symlinkat(link_target->base_name, + pglfd, + new_smb_fname->base_name); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_symlinkat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_symlinkat); + return -1; + } + + ret = glfs_symlink(handle->data, + link_target->base_name, + full_fname->base_name); + + TALLOC_FREE(full_fname); +#endif + + END_PROFILE(syscall_symlinkat); + + return ret; +} + +static int vfs_gluster_readlinkat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_readlinkat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_readlinkat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_readlinkat(pglfd, smb_fname->base_name, buf, bufsiz); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_readlinkat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_readlinkat); + return -1; + } + + ret = glfs_readlink(handle->data, full_fname->base_name, buf, bufsiz); + + TALLOC_FREE(full_fname); +#endif + + END_PROFILE(syscall_readlinkat); + + return ret; +} + +static int vfs_gluster_linkat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *src_pglfd = NULL; + glfs_fd_t *dst_pglfd = NULL; + + START_PROFILE(syscall_linkat); + + src_pglfd = vfs_gluster_fetch_glfd(handle, srcfsp); + if (src_pglfd == NULL) { + END_PROFILE(syscall_linkat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + dst_pglfd = vfs_gluster_fetch_glfd(handle, dstfsp); + if (dst_pglfd == NULL) { + END_PROFILE(syscall_linkat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_linkat(src_pglfd, + old_smb_fname->base_name, + dst_pglfd, + new_smb_fname->base_name, + flags); +#else + struct smb_filename *full_fname_old = NULL; + struct smb_filename *full_fname_new = NULL; + + START_PROFILE(syscall_linkat); + + full_fname_old = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (full_fname_old == NULL) { + END_PROFILE(syscall_linkat); + return -1; + } + + full_fname_new = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (full_fname_new == NULL) { + END_PROFILE(syscall_linkat); + TALLOC_FREE(full_fname_old); + return -1; + } + + ret = glfs_link(handle->data, + full_fname_old->base_name, + full_fname_new->base_name); + + TALLOC_FREE(full_fname_old); + TALLOC_FREE(full_fname_new); +#endif + + END_PROFILE(syscall_linkat); + + return ret; +} + +static int vfs_gluster_mknodat(struct vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + int ret; + +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; + + START_PROFILE(syscall_mknodat); + + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + END_PROFILE(syscall_mknodat); + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + ret = glfs_mknodat(pglfd, smb_fname->base_name, mode, dev); +#else + struct smb_filename *full_fname = NULL; + + START_PROFILE(syscall_mknodat); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + END_PROFILE(syscall_mknodat); + return -1; + } + + ret = glfs_mknod(handle->data, full_fname->base_name, mode, dev); + + TALLOC_FREE(full_fname); +#endif + + END_PROFILE(syscall_mknodat); + + return ret; +} + +static int vfs_gluster_fchflags(struct vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + errno = ENOSYS; + return -1; +} + +static NTSTATUS vfs_gluster_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + int ret; + char key_buf[GLUSTER_NAME_MAX + 64]; + char val_buf[GLUSTER_NAME_MAX + 1]; + + if (strlen(name) >= GLUSTER_NAME_MAX) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + + snprintf(key_buf, GLUSTER_NAME_MAX + 64, + "glusterfs.get_real_filename:%s", name); + + ret = glfs_getxattr(handle->data, + dirfsp->fsp_name->base_name, + key_buf, + val_buf, + GLUSTER_NAME_MAX + 1); + if (ret == -1) { + if (errno == ENOATTR) { + errno = ENOENT; + } + return map_nt_error_from_unix(errno); + } + + *found_name = talloc_strdup(mem_ctx, val_buf); + if (found_name[0] == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static const char *vfs_gluster_connectpath( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + return handle->conn->connectpath; +} + +/* EA Operations */ + +static ssize_t vfs_gluster_fgetxattr(struct vfs_handle_struct *handle, + files_struct *fsp, const char *name, + void *value, size_t size) +{ + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to retrieve xattr value. + */ + return glfs_fgetxattr(glfd, name, value, size); + } + + /* + * This is no longer a handle based call. + */ + return glfs_getxattr(handle->data, + fsp->fsp_name->base_name, + name, + value, + size); +} + +static ssize_t vfs_gluster_flistxattr(struct vfs_handle_struct *handle, + files_struct *fsp, char *list, + size_t size) +{ + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to list xattrs. + */ + return glfs_flistxattr(glfd, list, size); + } else { + /* + * This is no longer a handle based call. + */ + return glfs_listxattr(handle->data, + fsp->fsp_name->base_name, + list, + size); + } +} + +static int vfs_gluster_fremovexattr(struct vfs_handle_struct *handle, + files_struct *fsp, const char *name) +{ + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to remove xattrs. + */ + return glfs_fremovexattr(glfd, name); + } else { + /* + * This is no longer a handle based call. + */ + return glfs_removexattr(handle->data, + fsp->fsp_name->base_name, + name); + } +} + +static int vfs_gluster_fsetxattr(struct vfs_handle_struct *handle, + files_struct *fsp, const char *name, + const void *value, size_t size, int flags) +{ + glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp); + if (glfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return -1; + } + + if (!fsp->fsp_flags.is_pathref) { + /* + * We can use an io_fd to set xattrs. + */ + return glfs_fsetxattr(glfd, name, value, size, flags); + } else { + /* + * This is no longer a handle based call. + */ + return glfs_setxattr(handle->data, + fsp->fsp_name->base_name, + name, + value, + size, + flags); + } +} + +/* AIO Operations */ + +static bool vfs_gluster_aio_force(struct vfs_handle_struct *handle, + files_struct *fsp) +{ + return false; +} + +static NTSTATUS vfs_gluster_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NO_MEMORY; + int ret; + char *msdfs_link = NULL; +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; +#else + struct smb_filename *full_fname = NULL; +#endif + + /* Form the msdfs_link contents */ + msdfs_link = msdfs_link_string(frame, + reflist, + referral_count); + if (msdfs_link == NULL) { + goto out; + } + +#ifdef HAVE_GFAPI_VER_7_11 + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto out; + } + + ret = glfs_symlinkat(msdfs_link, pglfd, smb_fname->base_name); +#else + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + goto out; + } + + ret = glfs_symlink(handle->data, msdfs_link, full_fname->base_name); + + TALLOC_FREE(full_fname); +#endif + if (ret == 0) { + status = NT_STATUS_OK; + } else { + status = map_nt_error_from_unix(errno); + } + + out: + + TALLOC_FREE(frame); + return status; +} + +/* + * Read and return the contents of a DFS redirect given a + * pathname. A caller can pass in NULL for ppreflist and + * preferral_count but still determine if this was a + * DFS redirect point by getting NT_STATUS_OK back + * without incurring the overhead of reading and parsing + * the referral contents. + */ + +static NTSTATUS vfs_gluster_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + NTSTATUS status = NT_STATUS_NO_MEMORY; + size_t bufsize; + char *link_target = NULL; + int referral_len; + bool ok; +#if defined(HAVE_BROKEN_READLINK) + char link_target_buf[PATH_MAX]; +#else + char link_target_buf[7]; +#endif + struct stat st; + struct smb_filename *full_fname = NULL; + int ret; +#ifdef HAVE_GFAPI_VER_7_11 + glfs_fd_t *pglfd = NULL; +#endif + + if (is_named_stream(smb_fname)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* + * We're only checking if this is a DFS + * redirect. We don't need to return data. + */ + bufsize = sizeof(link_target_buf); + link_target = link_target_buf; + } else { + bufsize = PATH_MAX; + link_target = talloc_array(mem_ctx, char, bufsize); + if (!link_target) { + goto err; + } + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err; + } + + ret = glfs_lstat(handle->data, full_fname->base_name, &st); + if (ret < 0) { + status = map_nt_error_from_unix(errno); + goto err; + } + +#ifdef HAVE_GFAPI_VER_7_11 + pglfd = vfs_gluster_fetch_glfd(handle, dirfsp); + if (pglfd == NULL) { + DBG_ERR("Failed to fetch gluster fd\n"); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + referral_len = glfs_readlinkat(pglfd, + smb_fname->base_name, + link_target, + bufsize - 1); +#else + referral_len = glfs_readlink(handle->data, + full_fname->base_name, + link_target, + bufsize - 1); +#endif + if (referral_len < 0) { + if (errno == EINVAL) { + DBG_INFO("%s is not a link.\n", full_fname->base_name); + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + } else { + status = map_nt_error_from_unix(errno); + DBG_ERR("Error reading " + "msdfs link %s: %s\n", + full_fname->base_name, + strerror(errno)); + } + goto err; + } + link_target[referral_len] = '\0'; + + DBG_INFO("%s -> %s\n", + full_fname->base_name, + link_target); + + if (!strnequal(link_target, "msdfs:", 6)) { + status = NT_STATUS_OBJECT_TYPE_MISMATCH; + goto err; + } + + if (ppreflist == NULL && preferral_count == NULL) { + /* Early return for checking if this is a DFS link. */ + TALLOC_FREE(full_fname); + smb_stat_ex_from_stat(&smb_fname->st, &st); + return NT_STATUS_OK; + } + + ok = parse_msdfs_symlink(mem_ctx, + lp_msdfs_shuffle_referrals(SNUM(handle->conn)), + link_target, + ppreflist, + preferral_count); + + if (ok) { + smb_stat_ex_from_stat(&smb_fname->st, &st); + status = NT_STATUS_OK; + } else { + status = NT_STATUS_NO_MEMORY; + } + + err: + + if (link_target != link_target_buf) { + TALLOC_FREE(link_target); + } + TALLOC_FREE(full_fname); + return status; +} + +static struct vfs_fn_pointers glusterfs_fns = { + + /* Disk Operations */ + + .connect_fn = vfs_gluster_connect, + .disconnect_fn = vfs_gluster_disconnect, + .disk_free_fn = vfs_gluster_disk_free, + .get_quota_fn = vfs_gluster_get_quota, + .set_quota_fn = vfs_gluster_set_quota, + .statvfs_fn = vfs_gluster_statvfs, + .fs_capabilities_fn = vfs_gluster_fs_capabilities, + + .get_dfs_referrals_fn = NULL, + + /* Directory Operations */ + + .fdopendir_fn = vfs_gluster_fdopendir, + .readdir_fn = vfs_gluster_readdir, + .rewind_dir_fn = vfs_gluster_rewinddir, + .mkdirat_fn = vfs_gluster_mkdirat, + .closedir_fn = vfs_gluster_closedir, + + /* File Operations */ + + .openat_fn = vfs_gluster_openat, + .create_file_fn = NULL, + .close_fn = vfs_gluster_close, + .pread_fn = vfs_gluster_pread, + .pread_send_fn = vfs_gluster_pread_send, + .pread_recv_fn = vfs_gluster_pread_recv, + .pwrite_fn = vfs_gluster_pwrite, + .pwrite_send_fn = vfs_gluster_pwrite_send, + .pwrite_recv_fn = vfs_gluster_pwrite_recv, + .lseek_fn = vfs_gluster_lseek, + .sendfile_fn = vfs_gluster_sendfile, + .recvfile_fn = vfs_gluster_recvfile, + .renameat_fn = vfs_gluster_renameat, + .fsync_send_fn = vfs_gluster_fsync_send, + .fsync_recv_fn = vfs_gluster_fsync_recv, + + .stat_fn = vfs_gluster_stat, + .fstat_fn = vfs_gluster_fstat, + .fstatat_fn = vfs_gluster_fstatat, + .lstat_fn = vfs_gluster_lstat, + .get_alloc_size_fn = vfs_gluster_get_alloc_size, + .unlinkat_fn = vfs_gluster_unlinkat, + + .fchmod_fn = vfs_gluster_fchmod, + .fchown_fn = vfs_gluster_fchown, + .lchown_fn = vfs_gluster_lchown, + .chdir_fn = vfs_gluster_chdir, + .getwd_fn = vfs_gluster_getwd, + .fntimes_fn = vfs_gluster_fntimes, + .ftruncate_fn = vfs_gluster_ftruncate, + .fallocate_fn = vfs_gluster_fallocate, + .lock_fn = vfs_gluster_lock, + .filesystem_sharemode_fn = vfs_gluster_filesystem_sharemode, + .fcntl_fn = vfs_gluster_fcntl, + .linux_setlease_fn = vfs_gluster_linux_setlease, + .getlock_fn = vfs_gluster_getlock, + .symlinkat_fn = vfs_gluster_symlinkat, + .readlinkat_fn = vfs_gluster_readlinkat, + .linkat_fn = vfs_gluster_linkat, + .mknodat_fn = vfs_gluster_mknodat, + .realpath_fn = vfs_gluster_realpath, + .fchflags_fn = vfs_gluster_fchflags, + .file_id_create_fn = NULL, + .fstreaminfo_fn = NULL, + .get_real_filename_at_fn = vfs_gluster_get_real_filename_at, + .connectpath_fn = vfs_gluster_connectpath, + .create_dfs_pathat_fn = vfs_gluster_create_dfs_pathat, + .read_dfs_pathat_fn = vfs_gluster_read_dfs_pathat, + + .brl_lock_windows_fn = NULL, + .brl_unlock_windows_fn = NULL, + .strict_lock_check_fn = NULL, + .translate_name_fn = NULL, + .fsctl_fn = NULL, + + /* NT ACL Operations */ + .fget_nt_acl_fn = NULL, + .fset_nt_acl_fn = NULL, + .audit_file_fn = NULL, + + /* Posix ACL Operations */ + .sys_acl_get_fd_fn = posixacl_xattr_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = posixacl_xattr_acl_set_fd, + .sys_acl_delete_def_fd_fn = posixacl_xattr_acl_delete_def_fd, + + /* EA Operations */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = vfs_gluster_fgetxattr, + .flistxattr_fn = vfs_gluster_flistxattr, + .fremovexattr_fn = vfs_gluster_fremovexattr, + .fsetxattr_fn = vfs_gluster_fsetxattr, + + /* AIO Operations */ + .aio_force_fn = vfs_gluster_aio_force, + + /* Durable handle Operations */ + .durable_cookie_fn = NULL, + .durable_disconnect_fn = NULL, + .durable_reconnect_fn = NULL, +}; + +static_decl_vfs; +NTSTATUS vfs_glusterfs_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "glusterfs", &glusterfs_fns); +} diff --git a/source3/modules/vfs_glusterfs_fuse.c b/source3/modules/vfs_glusterfs_fuse.c new file mode 100644 index 0000000..88c740c --- /dev/null +++ b/source3/modules/vfs_glusterfs_fuse.c @@ -0,0 +1,273 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (c) 2019 Guenther Deschner + + 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 "smbd/smbd.h" +#include "system/filesys.h" + +#define GLUSTER_NAME_MAX 255 + +static NTSTATUS vfs_gluster_fuse_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **_found_name) +{ + int ret, dirfd; + char key_buf[GLUSTER_NAME_MAX + 64]; + char val_buf[GLUSTER_NAME_MAX + 1]; + char *found_name = NULL; + + if (strlen(name) >= GLUSTER_NAME_MAX) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + + snprintf(key_buf, GLUSTER_NAME_MAX + 64, + "glusterfs.get_real_filename:%s", name); + + dirfd = openat(fsp_get_pathref_fd(dirfsp), ".", O_RDONLY); + if (dirfd == -1) { + NTSTATUS status = map_nt_error_from_unix(errno); + DBG_DEBUG("Could not open '.' in %s: %s\n", + fsp_str_dbg(dirfsp), + strerror(errno)); + return status; + } + + ret = fgetxattr(dirfd, key_buf, val_buf, GLUSTER_NAME_MAX + 1); + close(dirfd); + if (ret == -1) { + if (errno == ENOATTR) { + errno = ENOENT; + } + return map_nt_error_from_unix(errno); + } + + found_name = talloc_strdup(mem_ctx, val_buf); + if (found_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + *_found_name = found_name; + return NT_STATUS_OK; +} + +struct device_mapping_entry { + SMB_DEV_T device; /* the local device, for reference */ + uint64_t mapped_device; /* the mapped device */ +}; + +struct vfs_glusterfs_fuse_handle_data { + unsigned num_mapped_devices; + struct device_mapping_entry *mapped_devices; +}; + +/* a 64 bit hash, based on the one in tdb, copied from vfs_fileied */ +static uint64_t vfs_glusterfs_fuse_uint64_hash(const uint8_t *s, size_t len) +{ + uint64_t value; /* Used to compute the hash value. */ + uint32_t i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + for (value = 0x238F13AFLL * len, i=0; i < len; i++) + value = (value + (((uint64_t)s[i]) << (i*5 % 24))); + + return (1103515243LL * value + 12345LL); +} + +static void vfs_glusterfs_fuse_load_devices( + struct vfs_glusterfs_fuse_handle_data *data) +{ + FILE *f; + struct mntent *m; + + data->num_mapped_devices = 0; + TALLOC_FREE(data->mapped_devices); + + f = setmntent("/etc/mtab", "r"); + if (!f) { + return; + } + + while ((m = getmntent(f))) { + struct stat st; + char *p; + uint64_t mapped_device; + + if (stat(m->mnt_dir, &st) != 0) { + /* TODO: log? */ + continue; + } + + /* strip the host part off of the fsname */ + p = strrchr(m->mnt_fsname, ':'); + if (p == NULL) { + p = m->mnt_fsname; + } else { + /* TODO: consider the case of '' ? */ + p++; + } + + mapped_device = vfs_glusterfs_fuse_uint64_hash( + (const uint8_t *)p, + strlen(p)); + + data->mapped_devices = talloc_realloc(data, + data->mapped_devices, + struct device_mapping_entry, + data->num_mapped_devices + 1); + if (data->mapped_devices == NULL) { + goto nomem; + } + + data->mapped_devices[data->num_mapped_devices].device = + st.st_dev; + data->mapped_devices[data->num_mapped_devices].mapped_device = + mapped_device; + + data->num_mapped_devices++; + } + + endmntent(f); + return; + +nomem: + data->num_mapped_devices = 0; + TALLOC_FREE(data->mapped_devices); + + endmntent(f); + return; +} + +static int vfs_glusterfs_fuse_map_device_cached( + struct vfs_glusterfs_fuse_handle_data *data, + SMB_DEV_T device, + uint64_t *mapped_device) +{ + unsigned i; + + for (i = 0; i < data->num_mapped_devices; i++) { + if (data->mapped_devices[i].device == device) { + *mapped_device = data->mapped_devices[i].mapped_device; + return 0; + } + } + + return -1; +} + +static int vfs_glusterfs_fuse_map_device( + struct vfs_glusterfs_fuse_handle_data *data, + SMB_DEV_T device, + uint64_t *mapped_device) +{ + int ret; + + ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device); + if (ret == 0) { + return 0; + } + + vfs_glusterfs_fuse_load_devices(data); + + ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device); + + return ret; +} + +static struct file_id vfs_glusterfs_fuse_file_id_create( + struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct vfs_glusterfs_fuse_handle_data *data; + struct file_id id; + uint64_t mapped_device; + int ret; + + ZERO_STRUCT(id); + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf); + + SMB_VFS_HANDLE_GET_DATA(handle, data, + struct vfs_glusterfs_fuse_handle_data, + return id); + + ret = vfs_glusterfs_fuse_map_device(data, sbuf->st_ex_dev, + &mapped_device); + if (ret == 0) { + id.devid = mapped_device; + } else { + DBG_WARNING("Failed to map device [%jx], falling back to " + "standard file_id [%jx]\n", + (uintmax_t)sbuf->st_ex_dev, + (uintmax_t)id.devid); + } + + DBG_DEBUG("Returning dev [%jx] inode [%jx]\n", + (uintmax_t)id.devid, (uintmax_t)id.inode); + + return id; +} + +static int vfs_glusterfs_fuse_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct vfs_glusterfs_fuse_handle_data *data; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + data = talloc_zero(handle->conn, struct vfs_glusterfs_fuse_handle_data); + if (data == NULL) { + DBG_ERR("talloc_zero() failed.\n"); + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + + /* + * Fill the cache in the tree connect, so that the first file/dir access + * has chances of being fast... + */ + vfs_glusterfs_fuse_load_devices(data); + + SMB_VFS_HANDLE_SET_DATA(handle, data, NULL, + struct vfs_glusterfs_fuse_handle_data, + return -1); + + DBG_DEBUG("vfs_glusterfs_fuse_connect(): connected to service[%s]\n", + service); + + return 0; +} + +struct vfs_fn_pointers glusterfs_fuse_fns = { + + .connect_fn = vfs_glusterfs_fuse_connect, + .get_real_filename_at_fn = vfs_gluster_fuse_get_real_filename_at, + .file_id_create_fn = vfs_glusterfs_fuse_file_id_create, +}; + +static_decl_vfs; +NTSTATUS vfs_glusterfs_fuse_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "glusterfs_fuse", &glusterfs_fuse_fns); +} diff --git a/source3/modules/vfs_gpfs.c b/source3/modules/vfs_gpfs.c new file mode 100644 index 0000000..a8b4e38 --- /dev/null +++ b/source3/modules/vfs_gpfs.c @@ -0,0 +1,2546 @@ +/* + * Unix SMB/CIFS implementation. + * Samba VFS module for GPFS filesystem + * Copyright (C) Christian Ambach 2006 + * Copyright (C) Christof Schmitt 2015 + * Major code contributions by Chetan Shringarpure + * and Gomati Mohanan + * + * 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 "smbd/smbd.h" +#include "include/smbprofile.h" +#include "modules/non_posix_acls.h" +#include "libcli/security/security.h" +#include "nfs4_acls.h" +#include "system/filesys.h" +#include "auth.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/gpfswrap.h" + +#include +#include +#include "lib/crypto/gnutls_helpers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#ifndef GPFS_GETACL_NATIVE +#define GPFS_GETACL_NATIVE 0x00000004 +#endif + +struct gpfs_config_data { + struct smbacl4_vfs_params nfs4_params; + bool sharemodes; + bool leases; + bool hsm; + bool syncio; + bool winattr; + bool ftruncate; + bool getrealfilename; + bool dfreequota; + bool acl; + bool settimes; + bool recalls; + struct { + bool gpfs_fstat_x; + } pathref_ok; +}; + +struct gpfs_fsp_extension { + bool offline; +}; + +static inline unsigned int gpfs_acl_flags(gpfs_acl_t *gacl) +{ + if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) { + return gacl->v4Level1.acl_flags; + } + return 0; +} + +static inline gpfs_ace_v4_t *gpfs_ace_ptr(gpfs_acl_t *gacl, unsigned int i) +{ + if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) { + return &gacl->v4Level1.ace_v4[i]; + } + return &gacl->ace_v4[i]; +} + +static unsigned int vfs_gpfs_access_mask_to_allow(uint32_t access_mask) +{ + unsigned int allow = GPFS_SHARE_NONE; + + if (access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) { + allow |= GPFS_SHARE_WRITE; + } + if (access_mask & (FILE_READ_DATA|FILE_EXECUTE)) { + allow |= GPFS_SHARE_READ; + } + + return allow; +} + +static unsigned int vfs_gpfs_share_access_to_deny(uint32_t share_access) +{ + unsigned int deny = GPFS_DENY_NONE; + + if (!(share_access & FILE_SHARE_WRITE)) { + deny |= GPFS_DENY_WRITE; + } + if (!(share_access & FILE_SHARE_READ)) { + deny |= GPFS_DENY_READ; + } + + /* + * GPFS_DENY_DELETE can only be set together with either + * GPFS_DENY_WRITE or GPFS_DENY_READ. + */ + if ((deny & (GPFS_DENY_WRITE|GPFS_DENY_READ)) && + !(share_access & FILE_SHARE_DELETE)) { + deny |= GPFS_DENY_DELETE; + } + + return deny; +} + +static int set_gpfs_sharemode(files_struct *fsp, uint32_t access_mask, + uint32_t share_access) +{ + unsigned int allow = GPFS_SHARE_NONE; + unsigned int deny = GPFS_DENY_NONE; + int result; + + if (access_mask == 0) { + DBG_DEBUG("Clearing file system share mode.\n"); + } else { + allow = vfs_gpfs_access_mask_to_allow(access_mask); + deny = vfs_gpfs_share_access_to_deny(share_access); + } + DBG_DEBUG("access_mask=0x%x, allow=0x%x, share_access=0x%x, " + "deny=0x%x\n", access_mask, allow, share_access, deny); + + result = gpfswrap_set_share(fsp_get_io_fd(fsp), allow, deny); + if (result == 0) { + return 0; + } + + if (errno == EACCES) { + DBG_NOTICE("GPFS share mode denied for %s/%s.\n", + fsp->conn->connectpath, + fsp->fsp_name->base_name); + } else if (errno == EPERM) { + DBG_ERR("Samba requested GPFS sharemode for %s/%s, but the " + "GPFS file system is not configured accordingly. " + "Configure file system with mmchfs -D nfs4 or " + "set gpfs:sharemodes=no in Samba.\n", + fsp->conn->connectpath, + fsp->fsp_name->base_name); + } else { + DBG_ERR("gpfs_set_share failed: %s\n", strerror(errno)); + } + + return result; +} + +static int vfs_gpfs_filesystem_sharemode(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + + struct gpfs_config_data *config; + int ret = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if(!config->sharemodes) { + return 0; + } + + /* + * A named stream fsp will have the basefile open in the fsp + * fd, so lacking a distinct fd for the stream we have to skip + * set_gpfs_sharemode for stream. + */ + if (fsp_is_alternate_stream(fsp)) { + DBG_NOTICE("Not requesting GPFS sharemode on stream: %s/%s\n", + fsp->conn->connectpath, + fsp_str_dbg(fsp)); + return 0; + } + + ret = set_gpfs_sharemode(fsp, access_mask, share_access); + + return ret; +} + +static int vfs_gpfs_close(vfs_handle_struct *handle, files_struct *fsp) +{ + + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (config->sharemodes && + (fsp->fsp_flags.kernel_share_modes_taken)) + { + /* + * Always clear GPFS sharemode in case the actual + * close gets deferred due to outstanding POSIX locks + * (see fd_close_posix) + */ + int ret = gpfswrap_set_share(fsp_get_io_fd(fsp), 0, 0); + if (ret != 0) { + DBG_ERR("Clearing GPFS sharemode on close failed for " + " %s/%s: %s\n", + fsp->conn->connectpath, + fsp->fsp_name->base_name, + strerror(errno)); + } + } + + return SMB_VFS_NEXT_CLOSE(handle, fsp); +} + +#ifdef HAVE_KERNEL_OPLOCKS_LINUX +static int lease_type_to_gpfs(int leasetype) +{ + if (leasetype == F_RDLCK) { + return GPFS_LEASE_READ; + } + + if (leasetype == F_WRLCK) { + return GPFS_LEASE_WRITE; + } + + return GPFS_LEASE_NONE; +} + +static int vfs_gpfs_setlease(vfs_handle_struct *handle, + files_struct *fsp, + int leasetype) +{ + struct gpfs_config_data *config; + int ret=0; + + START_PROFILE(syscall_linux_setlease); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + ret = linux_set_lease_sighandler(fsp_get_io_fd(fsp)); + if (ret == -1) { + goto failure; + } + + if (config->leases) { + int gpfs_lease_type = lease_type_to_gpfs(leasetype); + int saved_errno = 0; + + /* + * Ensure the lease owner is root to allow + * correct delivery of lease-break signals. + */ + become_root(); + ret = gpfswrap_set_lease(fsp_get_io_fd(fsp), gpfs_lease_type); + if (ret < 0) { + saved_errno = errno; + } + unbecome_root(); + + if (saved_errno != 0) { + errno = saved_errno; + } + } + +failure: + END_PROFILE(syscall_linux_setlease); + + return ret; +} + +#else /* HAVE_KERNEL_OPLOCKS_LINUX */ + +static int vfs_gpfs_setlease(vfs_handle_struct *handle, + files_struct *fsp, + int leasetype) +{ + return ENOSYS; +} +#endif /* HAVE_KERNEL_OPLOCKS_LINUX */ + +static NTSTATUS vfs_gpfs_get_real_filename_at(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + int result; + char *full_path = NULL; + char *to_free = NULL; + char real_pathname[PATH_MAX+1], tmpbuf[PATH_MAX]; + size_t full_path_len; + int buflen; + bool mangled; + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + if (!config->getrealfilename) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + + mangled = mangle_is_mangled(name, handle->conn->params); + if (mangled) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + + full_path_len = full_path_tos(dirfsp->fsp_name->base_name, name, + tmpbuf, sizeof(tmpbuf), + &full_path, &to_free); + if (full_path_len == -1) { + return NT_STATUS_NO_MEMORY; + } + + buflen = sizeof(real_pathname) - 1; + + result = gpfswrap_get_realfilename_path(full_path, real_pathname, + &buflen); + + TALLOC_FREE(to_free); + + if ((result == -1) && (errno == ENOSYS)) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + + if (result == -1) { + DEBUG(10, ("smbd_gpfs_get_realfilename_path returned %s\n", + strerror(errno))); + return map_nt_error_from_unix(errno); + } + + /* + * GPFS does not necessarily null-terminate the returned path + * but instead returns the buffer length in buflen. + */ + + if (buflen < sizeof(real_pathname)) { + real_pathname[buflen] = '\0'; + } else { + real_pathname[sizeof(real_pathname)-1] = '\0'; + } + + DBG_DEBUG("%s/%s -> %s\n", + fsp_str_dbg(dirfsp), + name, + real_pathname); + + name = strrchr_m(real_pathname, '/'); + if (name == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + *found_name = talloc_strdup(mem_ctx, name+1); + if (*found_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static void sd2gpfs_control(uint16_t control, struct gpfs_acl *gacl) +{ + unsigned int gpfs_aclflags = 0; + control &= SEC_DESC_DACL_PROTECTED | SEC_DESC_SACL_PROTECTED | + SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_SACL_AUTO_INHERITED | + SEC_DESC_DACL_DEFAULTED | SEC_DESC_SACL_DEFAULTED | + SEC_DESC_DACL_PRESENT | SEC_DESC_SACL_PRESENT; + gpfs_aclflags = control << 8; + if (!(control & SEC_DESC_DACL_PRESENT)) + gpfs_aclflags |= ACL4_FLAG_NULL_DACL; + if (!(control & SEC_DESC_SACL_PRESENT)) + gpfs_aclflags |= ACL4_FLAG_NULL_SACL; + gacl->acl_level = GPFS_ACL_LEVEL_V4FLAGS; + gacl->v4Level1.acl_flags = gpfs_aclflags; +} + +static uint16_t gpfs2sd_control(unsigned int gpfs_aclflags) +{ + uint16_t control = gpfs_aclflags >> 8; + control &= SEC_DESC_DACL_PROTECTED | SEC_DESC_SACL_PROTECTED | + SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_SACL_AUTO_INHERITED | + SEC_DESC_DACL_DEFAULTED | SEC_DESC_SACL_DEFAULTED | + SEC_DESC_DACL_PRESENT | SEC_DESC_SACL_PRESENT; + control |= SEC_DESC_SELF_RELATIVE; + return control; +} + +static void gpfs_dumpacl(int level, struct gpfs_acl *gacl) +{ + gpfs_aclCount_t i; + if (gacl==NULL) + { + DEBUG(0, ("gpfs acl is NULL\n")); + return; + } + + DEBUG(level, ("len: %d, level: %d, version: %d, nace: %d, " + "control: %x\n", + gacl->acl_len, gacl->acl_level, gacl->acl_version, + gacl->acl_nace, gpfs_acl_flags(gacl))); + + for(i=0; iacl_nace; i++) + { + struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, i); + DEBUG(level, ("\tace[%d]: type:%d, flags:0x%x, mask:0x%x, " + "iflags:0x%x, who:%u\n", + i, gace->aceType, gace->aceFlags, gace->aceMask, + gace->aceIFlags, gace->aceWho)); + } +} + +static int gpfs_getacl_with_capability(struct files_struct *fsp, + int flags, + void *buf) +{ + int ret, saved_errno; + + set_effective_capability(DAC_OVERRIDE_CAPABILITY); + + ret = gpfswrap_fgetacl(fsp_get_pathref_fd(fsp), flags, buf); + saved_errno = errno; + + drop_effective_capability(DAC_OVERRIDE_CAPABILITY); + + errno = saved_errno; + return ret; +} + +/* + * get the ACL from GPFS, allocated on the specified mem_ctx + * internally retries when initial buffer was too small + * + * caller needs to cast result to either + * raw = yes: struct gpfs_opaque_acl + * raw = no: struct gpfs_acl + * + */ +static void *vfs_gpfs_getacl(TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + const bool raw, + const gpfs_aclType_t type) +{ + const char *fname = fsp->fsp_name->base_name; + void *aclbuf; + size_t size = 512; + int ret, flags; + unsigned int *len; + size_t struct_size; + bool use_capability = false; + +again: + + aclbuf = talloc_zero_size(mem_ctx, size); + if (aclbuf == NULL) { + errno = ENOMEM; + return NULL; + } + + if (raw) { + struct gpfs_opaque_acl *buf = (struct gpfs_opaque_acl *) aclbuf; + buf->acl_type = type; + flags = GPFS_GETACL_NATIVE; + len = (unsigned int *) &(buf->acl_buffer_len); + struct_size = sizeof(struct gpfs_opaque_acl); + } else { + struct gpfs_acl *buf = (struct gpfs_acl *) aclbuf; + buf->acl_type = type; + buf->acl_level = GPFS_ACL_LEVEL_V4FLAGS; + flags = GPFS_GETACL_STRUCT; + len = &(buf->acl_len); + /* reserve space for control flags in gpfs 3.5 and beyond */ + struct_size = sizeof(struct gpfs_acl) + sizeof(unsigned int); + } + + /* set the length of the buffer as input value */ + *len = size; + + if (use_capability) { + ret = gpfs_getacl_with_capability(fsp, flags, aclbuf); + } else { + ret = gpfswrap_fgetacl(fsp_get_pathref_fd(fsp), flags, aclbuf); + if ((ret != 0) && (errno == EACCES)) { + DBG_DEBUG("Retry with DAC capability for %s\n", fname); + use_capability = true; + ret = gpfs_getacl_with_capability(fsp, flags, aclbuf); + } + } + + if ((ret != 0) && (errno == ENOSPC)) { + /* + * get the size needed to accommodate the complete buffer + * + * the value returned only applies to the ACL blob in the + * struct so make sure to also have headroom for the first + * struct members by adding room for the complete struct + * (might be a few bytes too much then) + */ + size = *len + struct_size; + talloc_free(aclbuf); + DEBUG(10, ("Increasing ACL buffer size to %zu\n", size)); + goto again; + } + + if (ret != 0) { + DEBUG(5, ("smbd_gpfs_getacl failed with %s\n", + strerror(errno))); + talloc_free(aclbuf); + return NULL; + } + + return aclbuf; +} + +/* Tries to get nfs4 acls and returns SMB ACL allocated. + * On failure returns 1 if it got non-NFSv4 ACL to prompt + * retry with POSIX ACL checks. + * On failure returns -1 if there is system (GPFS) error, check errno. + * Returns 0 on success + */ +static int gpfs_get_nfs4_acl(TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + struct SMB4ACL_T **ppacl) +{ + const char *fname = fsp->fsp_name->base_name; + gpfs_aclCount_t i; + struct gpfs_acl *gacl = NULL; + DEBUG(10, ("gpfs_get_nfs4_acl invoked for %s\n", fname)); + + /* Get the ACL */ + gacl = (struct gpfs_acl*) vfs_gpfs_getacl(talloc_tos(), fsp, + false, 0); + if (gacl == NULL) { + DEBUG(9, ("gpfs_getacl failed for %s with %s\n", + fname, strerror(errno))); + if (errno == ENODATA) { + /* + * GPFS returns ENODATA for snapshot + * directories. Retry with POSIX ACLs check. + */ + return 1; + } + + return -1; + } + + if (gacl->acl_type != GPFS_ACL_TYPE_NFS4) { + DEBUG(10, ("Got non-nfsv4 acl\n")); + /* Retry with POSIX ACLs check */ + talloc_free(gacl); + return 1; + } + + *ppacl = smb_create_smb4acl(mem_ctx); + + if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) { + uint16_t control = gpfs2sd_control(gpfs_acl_flags(gacl)); + smbacl4_set_controlflags(*ppacl, control); + } + + DEBUG(10, ("len: %d, level: %d, version: %d, nace: %d, control: %x\n", + gacl->acl_len, gacl->acl_level, gacl->acl_version, + gacl->acl_nace, gpfs_acl_flags(gacl))); + + for (i=0; iacl_nace; i++) { + struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, i); + SMB_ACE4PROP_T smbace = { 0 }; + DEBUG(10, ("type: %d, iflags: %x, flags: %x, mask: %x, " + "who: %d\n", gace->aceType, gace->aceIFlags, + gace->aceFlags, gace->aceMask, gace->aceWho)); + + if (gace->aceIFlags & ACE4_IFLAG_SPECIAL_ID) { + smbace.flags |= SMB_ACE4_ID_SPECIAL; + switch (gace->aceWho) { + case ACE4_SPECIAL_OWNER: + smbace.who.special_id = SMB_ACE4_WHO_OWNER; + break; + case ACE4_SPECIAL_GROUP: + smbace.who.special_id = SMB_ACE4_WHO_GROUP; + break; + case ACE4_SPECIAL_EVERYONE: + smbace.who.special_id = SMB_ACE4_WHO_EVERYONE; + break; + default: + DEBUG(8, ("invalid special gpfs id %d " + "ignored\n", gace->aceWho)); + continue; /* don't add it */ + } + } else { + if (gace->aceFlags & ACE4_FLAG_GROUP_ID) + smbace.who.gid = gace->aceWho; + else + smbace.who.uid = gace->aceWho; + } + + /* remove redundant deny entries */ + if (i > 0 && gace->aceType == SMB_ACE4_ACCESS_DENIED_ACE_TYPE) { + struct gpfs_ace_v4 *prev = gpfs_ace_ptr(gacl, i - 1); + if (prev->aceType == SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE && + prev->aceFlags == gace->aceFlags && + prev->aceIFlags == gace->aceIFlags && + (gace->aceMask & prev->aceMask) == 0 && + gace->aceWho == prev->aceWho) { + /* it's redundant - skip it */ + continue; + } + } + + smbace.aceType = gace->aceType; + smbace.aceFlags = gace->aceFlags; + smbace.aceMask = gace->aceMask; + smb_add_ace4(*ppacl, &smbace); + } + + talloc_free(gacl); + + return 0; +} + +static NTSTATUS gpfsacl_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + struct SMB4ACL_T *pacl = NULL; + int result; + struct gpfs_config_data *config; + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + + *ppdesc = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + if (!config->acl) { + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + TALLOC_FREE(frame); + return status; + } + + result = gpfs_get_nfs4_acl(frame, fsp, &pacl); + + if (result == 0) { + status = smb_fget_nt_acl_nfs4(fsp, &config->nfs4_params, + security_info, + mem_ctx, ppdesc, pacl); + TALLOC_FREE(frame); + return status; + } + + if (result > 0) { + DEBUG(10, ("retrying with posix acl...\n")); + status = posix_fget_nt_acl(fsp, security_info, + mem_ctx, ppdesc); + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + + /* GPFS ACL was not read, something wrong happened, error code is set in errno */ + return map_nt_error_from_unix(errno); +} + +static bool vfs_gpfs_nfs4_ace_to_gpfs_ace(SMB_ACE4PROP_T *nfs4_ace, + struct gpfs_ace_v4 *gace, + uid_t owner_uid) +{ + gace->aceType = nfs4_ace->aceType; + gace->aceFlags = nfs4_ace->aceFlags; + gace->aceMask = nfs4_ace->aceMask; + + if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) { + switch(nfs4_ace->who.special_id) { + case SMB_ACE4_WHO_EVERYONE: + gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID; + gace->aceWho = ACE4_SPECIAL_EVERYONE; + break; + case SMB_ACE4_WHO_OWNER: + /* + * With GPFS it is not possible to deny ACL or + * attribute access to the owner. Setting an + * ACL with such an entry is not possible. + * Denying ACL or attribute access for the + * owner through a named ACL entry can be + * stored in an ACL, it is just not effective. + * + * Map this case to a named entry to allow at + * least setting this ACL, which will be + * enforced by the smbd permission check. Do + * not do this for an inheriting OWNER entry, + * as this represents a CREATOR OWNER ACE. The + * remaining limitation is that CREATOR OWNER + * cannot deny ACL or attribute access. + */ + if (!nfs_ace_is_inherit(nfs4_ace) && + nfs4_ace->aceType == + SMB_ACE4_ACCESS_DENIED_ACE_TYPE && + nfs4_ace->aceMask & (SMB_ACE4_READ_ATTRIBUTES| + SMB_ACE4_WRITE_ATTRIBUTES| + SMB_ACE4_READ_ACL| + SMB_ACE4_WRITE_ACL)) { + gace->aceIFlags = 0; + gace->aceWho = owner_uid; + } else { + gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID; + gace->aceWho = ACE4_SPECIAL_OWNER; + } + break; + case SMB_ACE4_WHO_GROUP: + gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID; + gace->aceWho = ACE4_SPECIAL_GROUP; + break; + default: + DBG_WARNING("Unsupported special_id %d\n", + nfs4_ace->who.special_id); + return false; + } + + return true; + } + + gace->aceIFlags = 0; + gace->aceWho = (nfs4_ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) ? + nfs4_ace->who.gid : nfs4_ace->who.uid; + + return true; +} + +static struct gpfs_acl *vfs_gpfs_smbacl2gpfsacl(TALLOC_CTX *mem_ctx, + files_struct *fsp, + struct SMB4ACL_T *smbacl, + bool controlflags) +{ + struct gpfs_acl *gacl; + gpfs_aclLen_t gacl_len; + struct SMB4ACE_T *smbace; + + gacl_len = offsetof(gpfs_acl_t, ace_v4) + sizeof(unsigned int) + + smb_get_naces(smbacl) * sizeof(gpfs_ace_v4_t); + + gacl = (struct gpfs_acl *)TALLOC_SIZE(mem_ctx, gacl_len); + if (gacl == NULL) { + DEBUG(0, ("talloc failed\n")); + errno = ENOMEM; + return NULL; + } + + gacl->acl_level = GPFS_ACL_LEVEL_BASE; + gacl->acl_version = GPFS_ACL_VERSION_NFS4; + gacl->acl_type = GPFS_ACL_TYPE_NFS4; + gacl->acl_nace = 0; /* change later... */ + + if (controlflags) { + gacl->acl_level = GPFS_ACL_LEVEL_V4FLAGS; + sd2gpfs_control(smbacl4_get_controlflags(smbacl), gacl); + } + + for (smbace=smb_first_ace4(smbacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) { + struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, gacl->acl_nace); + SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace); + bool add_ace; + + add_ace = vfs_gpfs_nfs4_ace_to_gpfs_ace(aceprop, gace, + fsp->fsp_name->st.st_ex_uid); + if (!add_ace) { + continue; + } + + gacl->acl_nace++; + } + gacl->acl_len = (char *)gpfs_ace_ptr(gacl, gacl->acl_nace) + - (char *)gacl; + return gacl; +} + +static bool gpfsacl_process_smbacl(vfs_handle_struct *handle, + files_struct *fsp, + struct SMB4ACL_T *smbacl) +{ + int ret; + struct gpfs_acl *gacl; + TALLOC_CTX *mem_ctx = talloc_tos(); + + gacl = vfs_gpfs_smbacl2gpfsacl(mem_ctx, fsp, smbacl, true); + if (gacl == NULL) { /* out of memory */ + return False; + } + ret = gpfswrap_putacl(fsp->fsp_name->base_name, + GPFS_PUTACL_STRUCT | GPFS_ACL_SAMBA, gacl); + + if ((ret != 0) && (errno == EINVAL)) { + DEBUG(10, ("Retry without nfs41 control flags\n")); + talloc_free(gacl); + gacl = vfs_gpfs_smbacl2gpfsacl(mem_ctx, fsp, smbacl, false); + if (gacl == NULL) { /* out of memory */ + return False; + } + ret = gpfswrap_putacl(fsp->fsp_name->base_name, + GPFS_PUTACL_STRUCT | GPFS_ACL_SAMBA, + gacl); + } + + if (ret != 0) { + DEBUG(8, ("gpfs_putacl failed with %s\n", strerror(errno))); + gpfs_dumpacl(8, gacl); + return False; + } + + DEBUG(10, ("gpfs_putacl succeeded\n")); + return True; +} + +static NTSTATUS gpfsacl_set_nt_acl_internal(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) +{ + struct gpfs_acl *acl; + NTSTATUS result = NT_STATUS_ACCESS_DENIED; + + acl = (struct gpfs_acl*) vfs_gpfs_getacl(talloc_tos(), + fsp, + false, 0); + if (acl == NULL) { + return map_nt_error_from_unix(errno); + } + + if (acl->acl_version == GPFS_ACL_VERSION_NFS4) { + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + result = smb_set_nt_acl_nfs4(handle, + fsp, &config->nfs4_params, security_info_sent, psd, + gpfsacl_process_smbacl); + } else { /* assume POSIX ACL - by default... */ + result = set_nt_acl(fsp, security_info_sent, psd); + } + + talloc_free(acl); + return result; +} + +static NTSTATUS gpfsacl_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) +{ + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + if (!config->acl) { + return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + } + + return gpfsacl_set_nt_acl_internal(handle, fsp, security_info_sent, psd); +} + +static SMB_ACL_T gpfs2smb_acl(const struct gpfs_acl *pacl, TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result; + gpfs_aclCount_t i; + + result = sys_acl_init(mem_ctx); + if (result == NULL) { + errno = ENOMEM; + return NULL; + } + + result->count = pacl->acl_nace; + result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, + result->count); + if (result->acl == NULL) { + TALLOC_FREE(result); + errno = ENOMEM; + return NULL; + } + + for (i=0; iacl_nace; i++) { + struct smb_acl_entry *ace = &result->acl[i]; + const struct gpfs_ace_v1 *g_ace = &pacl->ace_v1[i]; + + DEBUG(10, ("Converting type %d id %lu perm %x\n", + (int)g_ace->ace_type, (unsigned long)g_ace->ace_who, + (int)g_ace->ace_perm)); + + switch (g_ace->ace_type) { + case GPFS_ACL_USER: + ace->a_type = SMB_ACL_USER; + ace->info.user.uid = (uid_t)g_ace->ace_who; + break; + case GPFS_ACL_USER_OBJ: + ace->a_type = SMB_ACL_USER_OBJ; + break; + case GPFS_ACL_GROUP: + ace->a_type = SMB_ACL_GROUP; + ace->info.group.gid = (gid_t)g_ace->ace_who; + break; + case GPFS_ACL_GROUP_OBJ: + ace->a_type = SMB_ACL_GROUP_OBJ; + break; + case GPFS_ACL_OTHER: + ace->a_type = SMB_ACL_OTHER; + break; + case GPFS_ACL_MASK: + ace->a_type = SMB_ACL_MASK; + break; + default: + DEBUG(10, ("Got invalid ace_type: %d\n", + g_ace->ace_type)); + TALLOC_FREE(result); + errno = EINVAL; + return NULL; + } + + ace->a_perm = 0; + ace->a_perm |= (g_ace->ace_perm & ACL_PERM_READ) ? + SMB_ACL_READ : 0; + ace->a_perm |= (g_ace->ace_perm & ACL_PERM_WRITE) ? + SMB_ACL_WRITE : 0; + ace->a_perm |= (g_ace->ace_perm & ACL_PERM_EXECUTE) ? + SMB_ACL_EXECUTE : 0; + + DEBUGADD(10, ("Converted to %d perm %x\n", + ace->a_type, ace->a_perm)); + } + + return result; +} + +static SMB_ACL_T gpfsacl_get_posix_acl(struct files_struct *fsp, + gpfs_aclType_t type, + TALLOC_CTX *mem_ctx) +{ + struct gpfs_acl *pacl; + SMB_ACL_T result = NULL; + + pacl = vfs_gpfs_getacl(talloc_tos(), fsp, false, type); + + if (pacl == NULL) { + DBG_DEBUG("vfs_gpfs_getacl failed for %s with %s\n", + fsp_str_dbg(fsp), strerror(errno)); + if (errno == 0) { + errno = EINVAL; + } + goto done; + } + + if (pacl->acl_version != GPFS_ACL_VERSION_POSIX) { + DEBUG(10, ("Got acl version %d, expected %d\n", + pacl->acl_version, GPFS_ACL_VERSION_POSIX)); + errno = EINVAL; + goto done; + } + + DEBUG(10, ("len: %d, level: %d, version: %d, nace: %d\n", + pacl->acl_len, pacl->acl_level, pacl->acl_version, + pacl->acl_nace)); + + result = gpfs2smb_acl(pacl, mem_ctx); + if (result != NULL) { + errno = 0; + } + + done: + + if (pacl != NULL) { + talloc_free(pacl); + } + if (errno != 0) { + TALLOC_FREE(result); + } + return result; +} + +static SMB_ACL_T gpfsacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + gpfs_aclType_t gpfs_type; + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NULL); + + if (!config->acl) { + return SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx); + } + + switch(type) { + case SMB_ACL_TYPE_ACCESS: + gpfs_type = GPFS_ACL_TYPE_ACCESS; + break; + case SMB_ACL_TYPE_DEFAULT: + gpfs_type = GPFS_ACL_TYPE_DEFAULT; + break; + default: + DEBUG(0, ("Got invalid type: %d\n", type)); + smb_panic("exiting"); + } + return gpfsacl_get_posix_acl(fsp, gpfs_type, mem_ctx); +} + +static int gpfsacl_sys_acl_blob_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **blob_description, + DATA_BLOB *blob) +{ + struct gpfs_config_data *config; + struct gpfs_opaque_acl *acl = NULL; + DATA_BLOB aclblob; + int result; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (!config->acl) { + return SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, + blob_description, blob); + } + + errno = 0; + acl = (struct gpfs_opaque_acl *) vfs_gpfs_getacl(mem_ctx, + fsp, + true, + GPFS_ACL_TYPE_NFS4); + + if (errno) { + DEBUG(5, ("vfs_gpfs_getacl finished with errno %d: %s\n", + errno, strerror(errno))); + + /* EINVAL means POSIX ACL, bail out on other cases */ + if (errno != EINVAL) { + return -1; + } + } + + if (acl != NULL) { + /* + * file has NFSv4 ACL + * + * we only need the actual ACL blob here + * acl_version will always be NFS4 because we asked + * for NFS4 + * acl_type is only used for POSIX ACLs + */ + aclblob.data = (uint8_t*) acl->acl_var_data; + aclblob.length = acl->acl_buffer_len; + + *blob_description = talloc_strdup(mem_ctx, "gpfs_nfs4_acl"); + if (!*blob_description) { + talloc_free(acl); + errno = ENOMEM; + return -1; + } + + result = non_posix_sys_acl_blob_get_fd_helper(handle, fsp, + aclblob, mem_ctx, + blob); + + talloc_free(acl); + return result; + } + + /* fall back to POSIX ACL */ + return posix_sys_acl_blob_get_fd(handle, fsp, mem_ctx, + blob_description, blob); +} + +static struct gpfs_acl *smb2gpfs_acl(const SMB_ACL_T pacl, + SMB_ACL_TYPE_T type) +{ + gpfs_aclLen_t len; + struct gpfs_acl *result; + int i; + + DEBUG(10, ("smb2gpfs_acl: Got ACL with %d entries\n", pacl->count)); + + len = offsetof(gpfs_acl_t, ace_v1) + (pacl->count) * + sizeof(gpfs_ace_v1_t); + + result = (struct gpfs_acl *)SMB_MALLOC(len); + if (result == NULL) { + errno = ENOMEM; + return result; + } + + result->acl_len = len; + result->acl_level = 0; + result->acl_version = GPFS_ACL_VERSION_POSIX; + result->acl_type = (type == SMB_ACL_TYPE_DEFAULT) ? + GPFS_ACL_TYPE_DEFAULT : GPFS_ACL_TYPE_ACCESS; + result->acl_nace = pacl->count; + + for (i=0; icount; i++) { + const struct smb_acl_entry *ace = &pacl->acl[i]; + struct gpfs_ace_v1 *g_ace = &result->ace_v1[i]; + + DEBUG(10, ("Converting type %d perm %x\n", + (int)ace->a_type, (int)ace->a_perm)); + + g_ace->ace_perm = 0; + + switch(ace->a_type) { + case SMB_ACL_USER: + g_ace->ace_type = GPFS_ACL_USER; + g_ace->ace_who = (gpfs_uid_t)ace->info.user.uid; + break; + case SMB_ACL_USER_OBJ: + g_ace->ace_type = GPFS_ACL_USER_OBJ; + g_ace->ace_perm |= ACL_PERM_CONTROL; + g_ace->ace_who = 0; + break; + case SMB_ACL_GROUP: + g_ace->ace_type = GPFS_ACL_GROUP; + g_ace->ace_who = (gpfs_uid_t)ace->info.group.gid; + break; + case SMB_ACL_GROUP_OBJ: + g_ace->ace_type = GPFS_ACL_GROUP_OBJ; + g_ace->ace_who = 0; + break; + case SMB_ACL_MASK: + g_ace->ace_type = GPFS_ACL_MASK; + g_ace->ace_perm = 0x8f; + g_ace->ace_who = 0; + break; + case SMB_ACL_OTHER: + g_ace->ace_type = GPFS_ACL_OTHER; + g_ace->ace_who = 0; + break; + default: + DEBUG(10, ("Got invalid ace_type: %d\n", ace->a_type)); + errno = EINVAL; + SAFE_FREE(result); + return NULL; + } + + g_ace->ace_perm |= (ace->a_perm & SMB_ACL_READ) ? + ACL_PERM_READ : 0; + g_ace->ace_perm |= (ace->a_perm & SMB_ACL_WRITE) ? + ACL_PERM_WRITE : 0; + g_ace->ace_perm |= (ace->a_perm & SMB_ACL_EXECUTE) ? + ACL_PERM_EXECUTE : 0; + + DEBUGADD(10, ("Converted to %d id %d perm %x\n", + g_ace->ace_type, g_ace->ace_who, g_ace->ace_perm)); + } + + return result; +} + +static int gpfsacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + struct gpfs_config_data *config; + struct gpfs_acl *gpfs_acl = NULL; + int result; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (!config->acl) { + return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); + } + + gpfs_acl = smb2gpfs_acl(theacl, type); + if (gpfs_acl == NULL) { + return -1; + } + + /* + * This is no longer a handle based call. + */ + result = gpfswrap_putacl(fsp->fsp_name->base_name, + GPFS_PUTACL_STRUCT|GPFS_ACL_SAMBA, + gpfs_acl); + SAFE_FREE(gpfs_acl); + return result; +} + +static int gpfsacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (!config->acl) { + return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp); + } + + errno = ENOTSUP; + return -1; +} + + +/* + * Assumed: mode bits are shiftable and standard + * Output: the new aceMask field for an smb nfs4 ace + */ +static uint32_t gpfsacl_mask_filter(uint32_t aceType, uint32_t aceMask, uint32_t rwx) +{ + const uint32_t posix_nfs4map[3] = { + SMB_ACE4_EXECUTE, /* execute */ + SMB_ACE4_WRITE_DATA | SMB_ACE4_APPEND_DATA, /* write; GPFS specific */ + SMB_ACE4_READ_DATA /* read */ + }; + int i; + uint32_t posix_mask = 0x01; + uint32_t posix_bit; + uint32_t nfs4_bits; + + for(i=0; i<3; i++) { + nfs4_bits = posix_nfs4map[i]; + posix_bit = rwx & posix_mask; + + if (aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) { + if (posix_bit) + aceMask |= nfs4_bits; + else + aceMask &= ~nfs4_bits; + } else { + /* add deny bits when suitable */ + if (!posix_bit) + aceMask |= nfs4_bits; + else + aceMask &= ~nfs4_bits; + } /* other ace types are unexpected */ + + posix_mask <<= 1; + } + + return aceMask; +} + +static int gpfsacl_emu_chmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + struct smb_filename *fname = fsp->fsp_name; + char *path = fsp->fsp_name->base_name; + struct SMB4ACL_T *pacl = NULL; + int result; + bool haveAllowEntry[SMB_ACE4_WHO_EVERYONE + 1] = {False, False, False, False}; + int i; + files_struct fake_fsp = { 0 }; /* TODO: rationalize parametrization */ + struct SMB4ACE_T *smbace; + TALLOC_CTX *frame = talloc_stackframe(); + + DEBUG(10, ("gpfsacl_emu_chmod invoked for %s mode %o\n", path, mode)); + + result = gpfs_get_nfs4_acl(frame, fsp, &pacl); + if (result) { + TALLOC_FREE(frame); + return result; + } + + if (mode & ~(S_IRWXU | S_IRWXG | S_IRWXO)) { + DEBUG(2, ("WARNING: cutting extra mode bits %o on %s\n", mode, path)); + } + + for (smbace=smb_first_ace4(pacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) { + SMB_ACE4PROP_T *ace = smb_get_ace4(smbace); + uint32_t specid = ace->who.special_id; + + if (ace->flags&SMB_ACE4_ID_SPECIAL && + ace->aceType<=SMB_ACE4_ACCESS_DENIED_ACE_TYPE && + specid <= SMB_ACE4_WHO_EVERYONE) { + + uint32_t newMask; + + if (ace->aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) + haveAllowEntry[specid] = True; + + /* mode >> 6 for @owner, mode >> 3 for @group, + * mode >> 0 for @everyone */ + newMask = gpfsacl_mask_filter(ace->aceType, ace->aceMask, + mode >> ((SMB_ACE4_WHO_EVERYONE - specid) * 3)); + if (ace->aceMask!=newMask) { + DEBUG(10, ("ace changed for %s (%o -> %o) id=%d\n", + path, ace->aceMask, newMask, specid)); + } + ace->aceMask = newMask; + } + } + + /* make sure we have at least ALLOW entries + * for all the 3 special ids (@EVERYONE, @OWNER, @GROUP) + * - if necessary + */ + for(i = SMB_ACE4_WHO_OWNER; i<=SMB_ACE4_WHO_EVERYONE; i++) { + SMB_ACE4PROP_T ace = { 0 }; + + if (haveAllowEntry[i]==True) + continue; + + ace.aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace.flags |= SMB_ACE4_ID_SPECIAL; + ace.who.special_id = i; + + if (i==SMB_ACE4_WHO_GROUP) /* not sure it's necessary... */ + ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP; + + ace.aceMask = gpfsacl_mask_filter(ace.aceType, ace.aceMask, + mode >> ((SMB_ACE4_WHO_EVERYONE - i) * 3)); + + /* don't add unnecessary aces */ + if (!ace.aceMask) + continue; + + /* we add it to the END - as windows expects allow aces */ + smb_add_ace4(pacl, &ace); + DEBUG(10, ("Added ALLOW ace for %s, mode=%o, id=%d, aceMask=%x\n", + path, mode, i, ace.aceMask)); + } + + /* don't add complementary DENY ACEs here */ + fake_fsp.fsp_name = synthetic_smb_fname(frame, + path, + NULL, + NULL, + fname->twrp, + 0); + if (fake_fsp.fsp_name == NULL) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + /* put the acl */ + if (gpfsacl_process_smbacl(handle, &fake_fsp, pacl) == False) { + TALLOC_FREE(frame); + return -1; + } + + TALLOC_FREE(frame); + return 0; /* ok for [f]chmod */ +} + +static int vfs_gpfs_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) +{ + SMB_STRUCT_STAT st; + int rc; + + rc = SMB_VFS_NEXT_FSTAT(handle, fsp, &st); + if (rc != 0) { + return -1; + } + + /* avoid chmod() if possible, to preserve acls */ + if ((st.st_ex_mode & ~S_IFMT) == mode) { + return 0; + } + + rc = gpfsacl_emu_chmod(handle, fsp, mode); + if (rc == 1) { + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + } + return rc; +} + +static uint32_t vfs_gpfs_winattrs_to_dosmode(unsigned int winattrs) +{ + uint32_t dosmode = 0; + + if (winattrs & GPFS_WINATTR_ARCHIVE){ + dosmode |= FILE_ATTRIBUTE_ARCHIVE; + } + if (winattrs & GPFS_WINATTR_HIDDEN){ + dosmode |= FILE_ATTRIBUTE_HIDDEN; + } + if (winattrs & GPFS_WINATTR_SYSTEM){ + dosmode |= FILE_ATTRIBUTE_SYSTEM; + } + if (winattrs & GPFS_WINATTR_READONLY){ + dosmode |= FILE_ATTRIBUTE_READONLY; + } + if (winattrs & GPFS_WINATTR_SPARSE_FILE) { + dosmode |= FILE_ATTRIBUTE_SPARSE; + } + if (winattrs & GPFS_WINATTR_OFFLINE) { + dosmode |= FILE_ATTRIBUTE_OFFLINE; + } + + return dosmode; +} + +static unsigned int vfs_gpfs_dosmode_to_winattrs(uint32_t dosmode) +{ + unsigned int winattrs = 0; + + if (dosmode & FILE_ATTRIBUTE_ARCHIVE){ + winattrs |= GPFS_WINATTR_ARCHIVE; + } + if (dosmode & FILE_ATTRIBUTE_HIDDEN){ + winattrs |= GPFS_WINATTR_HIDDEN; + } + if (dosmode & FILE_ATTRIBUTE_SYSTEM){ + winattrs |= GPFS_WINATTR_SYSTEM; + } + if (dosmode & FILE_ATTRIBUTE_READONLY){ + winattrs |= GPFS_WINATTR_READONLY; + } + if (dosmode & FILE_ATTRIBUTE_SPARSE) { + winattrs |= GPFS_WINATTR_SPARSE_FILE; + } + if (dosmode & FILE_ATTRIBUTE_OFFLINE) { + winattrs |= GPFS_WINATTR_OFFLINE; + } + + return winattrs; +} + +static struct timespec gpfs_timestruc64_to_timespec(struct gpfs_timestruc64 g) +{ + return (struct timespec) { .tv_sec = g.tv_sec, .tv_nsec = g.tv_nsec }; +} + +static NTSTATUS vfs_gpfs_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + struct gpfs_config_data *config; + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + const char *p = NULL; + struct gpfs_iattr64 iattr = { }; + unsigned int litemask = 0; + struct timespec ts; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + if (!config->winattr) { + return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode); + } + + if (fsp->fsp_flags.is_pathref && !config->pathref_ok.gpfs_fstat_x) { + if (fsp->fsp_flags.have_proc_fds) { + p = sys_proc_fd_path(fd, &buf); + } else { + p = fsp->fsp_name->base_name; + } + } + + if (p != NULL) { + ret = gpfswrap_stat_x(p, &litemask, &iattr, sizeof(iattr)); + } else { + ret = gpfswrap_fstat_x(fd, &litemask, &iattr, sizeof(iattr)); + } + if (ret == -1 && errno == ENOSYS) { + return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode); + } + + if (ret == -1 && errno == EACCES) { + int saved_errno = 0; + + /* + * According to MS-FSA 2.1.5.1.2.1 "Algorithm to Check Access to + * an Existing File" FILE_LIST_DIRECTORY on a directory implies + * FILE_READ_ATTRIBUTES for directory entries. Being able to + * open a file implies FILE_LIST_DIRECTORY. + */ + + set_effective_capability(DAC_OVERRIDE_CAPABILITY); + + if (p != NULL) { + ret = gpfswrap_stat_x(p, + &litemask, + &iattr, + sizeof(iattr)); + } else { + ret = gpfswrap_fstat_x(fd, + &litemask, + &iattr, + sizeof(iattr)); + } + if (ret == -1) { + saved_errno = errno; + } + + drop_effective_capability(DAC_OVERRIDE_CAPABILITY); + + if (saved_errno != 0) { + errno = saved_errno; + } + } + + if (ret == -1) { + DBG_WARNING("Getting winattrs failed for %s: %s\n", + fsp->fsp_name->base_name, strerror(errno)); + return map_nt_error_from_unix(errno); + } + + ts = gpfs_timestruc64_to_timespec(iattr.ia_createtime); + + *dosmode |= vfs_gpfs_winattrs_to_dosmode(iattr.ia_winflags); + update_stat_ex_create_time(&fsp->fsp_name->st, ts); + + return NT_STATUS_OK; +} + +static NTSTATUS vfs_gpfs_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + struct gpfs_config_data *config; + struct gpfs_winattr attrs = { }; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return NT_STATUS_INTERNAL_ERROR); + + if (!config->winattr) { + return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); + } + + attrs.winAttrs = vfs_gpfs_dosmode_to_winattrs(dosmode); + + if (!fsp->fsp_flags.is_pathref) { + ret = gpfswrap_set_winattrs(fsp_get_io_fd(fsp), + GPFS_WINATTR_SET_ATTRS, &attrs); + if (ret == -1) { + DBG_WARNING("Setting winattrs failed for %s: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return map_nt_error_from_unix(errno); + } + return NT_STATUS_OK; + } + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + ret = gpfswrap_set_winattrs_path(sys_proc_fd_path(fd, &buf), + GPFS_WINATTR_SET_ATTRS, + &attrs); + if (ret == -1) { + DBG_WARNING("Setting winattrs failed for " + "[%s][%s]: %s\n", + buf.buf, + fsp_str_dbg(fsp), + strerror(errno)); + return map_nt_error_from_unix(errno); + } + return NT_STATUS_OK; + } + + /* + * This is no longer a handle based call. + */ + ret = gpfswrap_set_winattrs_path(fsp->fsp_name->base_name, + GPFS_WINATTR_SET_ATTRS, + &attrs); + if (ret == -1) { + DBG_WARNING("Setting winattrs failed for [%s]: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return map_nt_error_from_unix(errno); + } + + return NT_STATUS_OK; +} + +static int timespec_to_gpfs_time( + struct timespec ts, gpfs_timestruc_t *gt, int idx, int *flags) +{ + if (is_omit_timespec(&ts)) { + return 0; + } + + if (ts.tv_sec < 0 || ts.tv_sec > UINT32_MAX) { + DBG_NOTICE("GPFS uses 32-bit unsigned timestamps " + "and cannot handle %jd.\n", + (intmax_t)ts.tv_sec); + errno = ERANGE; + return -1; + } + + *flags |= 1 << idx; + gt[idx].tv_sec = ts.tv_sec; + gt[idx].tv_nsec = ts.tv_nsec; + DBG_DEBUG("Setting GPFS time %d, flags 0x%x\n", idx, *flags); + + return 0; +} + +static int smbd_gpfs_set_times(struct files_struct *fsp, + struct smb_file_time *ft) +{ + gpfs_timestruc_t gpfs_times[4]; + int flags = 0; + int rc; + + ZERO_ARRAY(gpfs_times); + rc = timespec_to_gpfs_time(ft->atime, gpfs_times, 0, &flags); + if (rc != 0) { + return rc; + } + + rc = timespec_to_gpfs_time(ft->mtime, gpfs_times, 1, &flags); + if (rc != 0) { + return rc; + } + + /* No good mapping from LastChangeTime to ctime, not storing */ + rc = timespec_to_gpfs_time(ft->create_time, gpfs_times, 3, &flags); + if (rc != 0) { + return rc; + } + + if (!flags) { + DBG_DEBUG("nothing to do, return to avoid EINVAL\n"); + return 0; + } + + if (!fsp->fsp_flags.is_pathref) { + rc = gpfswrap_set_times(fsp_get_io_fd(fsp), flags, gpfs_times); + if (rc != 0) { + DBG_WARNING("gpfs_set_times(%s) failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + } + return rc; + } + + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + rc = gpfswrap_set_times_path(sys_proc_fd_path(fd, &buf), + flags, + gpfs_times); + if (rc != 0) { + DBG_WARNING("gpfs_set_times_path(%s,%s) failed: %s\n", + fsp_str_dbg(fsp), + buf.buf, + strerror(errno)); + } + return rc; + } + + /* + * This is no longer a handle based call. + */ + + rc = gpfswrap_set_times_path(fsp->fsp_name->base_name, + flags, + gpfs_times); + if (rc != 0) { + DBG_WARNING("gpfs_set_times_path(%s) failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + } + return rc; +} + +static int vfs_gpfs_fntimes(struct vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + + struct gpfs_winattr attrs; + int ret; + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct gpfs_config_data, + return -1); + + /* Try to use gpfs_set_times if it is enabled and available */ + if (config->settimes) { + return smbd_gpfs_set_times(fsp, ft); + } + + DBG_DEBUG("gpfs_set_times() not available or disabled, " + "use ntimes and winattr\n"); + + ret = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + if (ret == -1) { + /* don't complain if access was denied */ + if (errno != EPERM && errno != EACCES) { + DBG_WARNING("SMB_VFS_NEXT_FNTIMES failed: %s\n", + strerror(errno)); + } + return -1; + } + + if (is_omit_timespec(&ft->create_time)) { + DBG_DEBUG("Create Time is NULL\n"); + return 0; + } + + if (!config->winattr) { + return 0; + } + + attrs.winAttrs = 0; + attrs.creationTime.tv_sec = ft->create_time.tv_sec; + attrs.creationTime.tv_nsec = ft->create_time.tv_nsec; + + if (!fsp->fsp_flags.is_pathref) { + ret = gpfswrap_set_winattrs(fsp_get_io_fd(fsp), + GPFS_WINATTR_SET_CREATION_TIME, + &attrs); + if (ret == -1 && errno != ENOSYS) { + DBG_WARNING("Set GPFS ntimes failed %d\n", ret); + return -1; + } + return ret; + } + + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + ret = gpfswrap_set_winattrs_path( + sys_proc_fd_path(fd, &buf), + GPFS_WINATTR_SET_CREATION_TIME, + &attrs); + if (ret == -1 && errno != ENOSYS) { + DBG_WARNING("Set GPFS ntimes failed %d\n", ret); + return -1; + } + return ret; + } + + /* + * This is no longer a handle based call. + */ + ret = gpfswrap_set_winattrs_path(fsp->fsp_name->base_name, + GPFS_WINATTR_SET_CREATION_TIME, + &attrs); + if (ret == -1 && errno != ENOSYS) { + DBG_WARNING("Set GPFS ntimes failed %d\n", ret); + return -1; + } + + return 0; +} + +static int vfs_gpfs_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, uint32_t mode, + off_t offset, off_t len) +{ + if (mode == (VFS_FALLOCATE_FL_PUNCH_HOLE|VFS_FALLOCATE_FL_KEEP_SIZE) && + !fsp->fsp_flags.is_sparse && + lp_strict_allocate(SNUM(fsp->conn))) { + /* + * This is from a ZERO_DATA request on a non-sparse + * file. GPFS does not support FL_KEEP_SIZE and thus + * cannot fill the whole again in the subsequent + * fallocate(FL_KEEP_SIZE). Deny this FL_PUNCH_HOLE + * call to not end up with a hole in a non-sparse + * file. + */ + errno = ENOTSUP; + return -1; + } + + return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); +} + +static int vfs_gpfs_ftruncate(vfs_handle_struct *handle, files_struct *fsp, + off_t len) +{ + int result; + struct gpfs_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (!config->ftruncate) { + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len); + } + + result = gpfswrap_ftruncate(fsp_get_io_fd(fsp), len); + if ((result == -1) && (errno == ENOSYS)) { + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len); + } + return result; +} + +static bool vfs_gpfs_is_offline(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct gpfs_winattr attrs; + struct gpfs_config_data *config; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return false); + + if (!config->winattr) { + return false; + } + + ret = gpfswrap_get_winattrs(fsp_get_pathref_fd(fsp), &attrs); + if (ret == -1) { + return false; + } + + if ((attrs.winAttrs & GPFS_WINATTR_OFFLINE) != 0) { + DBG_DEBUG("%s is offline\n", fsp_str_dbg(fsp)); + return true; + } + + DBG_DEBUG("%s is online\n", fsp_str_dbg(fsp)); + return false; +} + +static bool vfs_gpfs_fsp_is_offline(struct vfs_handle_struct *handle, + struct files_struct *fsp) +{ + struct gpfs_fsp_extension *ext; + + ext = VFS_FETCH_FSP_EXTENSION(handle, fsp); + if (ext == NULL) { + /* + * Something bad happened, always ask. + */ + return vfs_gpfs_is_offline(handle, fsp, + &fsp->fsp_name->st); + } + + if (ext->offline) { + /* + * As long as it's offline, ask. + */ + ext->offline = vfs_gpfs_is_offline(handle, fsp, + &fsp->fsp_name->st); + } + + return ext->offline; +} + +static bool vfs_gpfs_aio_force(struct vfs_handle_struct *handle, + struct files_struct *fsp) +{ + return vfs_gpfs_fsp_is_offline(handle, fsp); +} + +static ssize_t vfs_gpfs_sendfile(vfs_handle_struct *handle, int tofd, + files_struct *fsp, const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + if (vfs_gpfs_fsp_is_offline(handle, fsp)) { + errno = ENOSYS; + return -1; + } + return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, hdr, offset, n); +} + +#ifdef O_PATH +static int vfs_gpfs_check_pathref_fstat_x(struct gpfs_config_data *config, + struct connection_struct *conn) +{ + struct gpfs_iattr64 iattr = {0}; + unsigned int litemask = 0; + int saved_errno; + int fd; + int ret; + + fd = open(conn->connectpath, O_PATH); + if (fd == -1) { + DBG_ERR("openat() of share with O_PATH failed: %s\n", + strerror(errno)); + return -1; + } + + ret = gpfswrap_fstat_x(fd, &litemask, &iattr, sizeof(iattr)); + if (ret == 0) { + close(fd); + config->pathref_ok.gpfs_fstat_x = true; + return 0; + } + + saved_errno = errno; + ret = close(fd); + if (ret != 0) { + DBG_ERR("close failed: %s\n", strerror(errno)); + return -1; + } + + if (saved_errno != EBADF) { + DBG_ERR("gpfswrap_fstat_x() of O_PATH handle failed: %s\n", + strerror(saved_errno)); + return -1; + } + + return 0; +} +#endif + +static int vfs_gpfs_check_pathref(struct gpfs_config_data *config, + struct connection_struct *conn) +{ +#ifndef O_PATH + /* + * This code path leaves all struct gpfs_config_data.pathref_ok members + * initialized to false. + */ + return 0; +#else + int ret; + + ret = vfs_gpfs_check_pathref_fstat_x(config, conn); + if (ret != 0) { + return -1; + } + + return 0; +#endif +} + +static int vfs_gpfs_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct gpfs_config_data *config; + int ret; + bool check_fstype; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + if (IS_IPC(handle->conn)) { + return 0; + } + + ret = gpfswrap_init(); + if (ret < 0) { + DBG_ERR("Could not load GPFS library.\n"); + return ret; + } + + ret = gpfswrap_lib_init(0); + if (ret < 0) { + DBG_ERR("Could not open GPFS device file: %s\n", + strerror(errno)); + return ret; + } + + ret = gpfswrap_register_cifs_export(); + if (ret < 0) { + DBG_ERR("Failed to register with GPFS: %s\n", strerror(errno)); + return ret; + } + + config = talloc_zero(handle->conn, struct gpfs_config_data); + if (!config) { + DEBUG(0, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + check_fstype = lp_parm_bool(SNUM(handle->conn), "gpfs", + "check_fstype", true); + + if (check_fstype) { + const char *connectpath = handle->conn->connectpath; + struct statfs buf = { 0 }; + + ret = statfs(connectpath, &buf); + if (ret != 0) { + DBG_ERR("statfs failed for share %s at path %s: %s\n", + service, connectpath, strerror(errno)); + TALLOC_FREE(config); + return ret; + } + + if (buf.f_type != GPFS_SUPER_MAGIC) { + DBG_ERR("SMB share %s, path %s not in GPFS file system." + " statfs magic: 0x%jx\n", + service, + connectpath, + (uintmax_t)buf.f_type); + errno = EINVAL; + TALLOC_FREE(config); + return -1; + } + } + + ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params); + if (ret < 0) { + TALLOC_FREE(config); + return ret; + } + + config->sharemodes = lp_parm_bool(SNUM(handle->conn), "gpfs", + "sharemodes", true); + + config->leases = lp_parm_bool(SNUM(handle->conn), "gpfs", + "leases", true); + + config->hsm = lp_parm_bool(SNUM(handle->conn), "gpfs", + "hsm", false); + + config->syncio = lp_parm_bool(SNUM(handle->conn), "gpfs", + "syncio", false); + + config->winattr = lp_parm_bool(SNUM(handle->conn), "gpfs", + "winattr", false); + + config->ftruncate = lp_parm_bool(SNUM(handle->conn), "gpfs", + "ftruncate", true); + + config->getrealfilename = lp_parm_bool(SNUM(handle->conn), "gpfs", + "getrealfilename", true); + + config->dfreequota = lp_parm_bool(SNUM(handle->conn), "gpfs", + "dfreequota", false); + + config->acl = lp_parm_bool(SNUM(handle->conn), "gpfs", "acl", true); + + config->settimes = lp_parm_bool(SNUM(handle->conn), "gpfs", + "settimes", true); + config->recalls = lp_parm_bool(SNUM(handle->conn), "gpfs", + "recalls", true); + + ret = vfs_gpfs_check_pathref(config, handle->conn); + if (ret != 0) { + DBG_ERR("vfs_gpfs_check_pathref() on [%s] failed\n", + handle->conn->connectpath); + TALLOC_FREE(config); + return -1; + } + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct gpfs_config_data, + return -1); + + if (config->leases) { + /* + * GPFS lease code is based on kernel oplock code + * so make sure it is turned on + */ + if (!lp_kernel_oplocks(SNUM(handle->conn))) { + DEBUG(5, ("Enabling kernel oplocks for " + "gpfs:leases to work\n")); + lp_do_parameter(SNUM(handle->conn), "kernel oplocks", + "true"); + } + + /* + * as the kernel does not properly support Level II oplocks + * and GPFS leases code is based on kernel infrastructure, we + * need to turn off Level II oplocks if gpfs:leases is enabled + */ + if (lp_level2_oplocks(SNUM(handle->conn))) { + DEBUG(5, ("gpfs:leases are enabled, disabling " + "Level II oplocks\n")); + lp_do_parameter(SNUM(handle->conn), "level2 oplocks", + "false"); + } + } + + /* + * Unless we have an async implementation of get_dos_attributes turn + * this off. + */ + lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false"); + + return 0; +} + +static int get_gpfs_quota(const char *pathname, int type, int id, + struct gpfs_quotaInfo *qi) +{ + int ret; + + ret = gpfswrap_quotactl(pathname, GPFS_QCMD(Q_GETQUOTA, type), id, qi); + + if (ret) { + if (errno == GPFS_E_NO_QUOTA_INST) { + DEBUG(10, ("Quotas disabled on GPFS filesystem.\n")); + } else if (errno != ENOSYS) { + DEBUG(0, ("Get quota failed, type %d, id, %d, " + "errno %d.\n", type, id, errno)); + } + + return ret; + } + + DEBUG(10, ("quota type %d, id %d, blk u:%lld h:%lld s:%lld gt:%u\n", + type, id, qi->blockUsage, qi->blockHardLimit, + qi->blockSoftLimit, qi->blockGraceTime)); + + return ret; +} + +static void vfs_gpfs_disk_free_quota(struct gpfs_quotaInfo qi, time_t cur_time, + uint64_t *dfree, uint64_t *dsize) +{ + uint64_t usage, limit; + + /* + * The quota reporting is done in units of 1024 byte blocks, but + * sys_fsusage uses units of 512 byte blocks, adjust the block number + * accordingly. Also filter possibly negative usage counts from gpfs. + */ + usage = qi.blockUsage < 0 ? 0 : (uint64_t)qi.blockUsage * 2; + limit = (uint64_t)qi.blockHardLimit * 2; + + /* + * When the grace time for the exceeded soft block quota has been + * exceeded, the soft block quota becomes an additional hard limit. + */ + if (qi.blockSoftLimit && + qi.blockGraceTime && cur_time > qi.blockGraceTime) { + /* report disk as full */ + *dfree = 0; + *dsize = MIN(*dsize, usage); + } + + if (!qi.blockHardLimit) + return; + + if (usage >= limit) { + /* report disk as full */ + *dfree = 0; + *dsize = MIN(*dsize, usage); + + } else { + /* limit has not been reached, determine "free space" */ + *dfree = MIN(*dfree, limit - usage); + *dsize = MIN(*dsize, limit); + } +} + +static uint64_t vfs_gpfs_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + struct security_unix_token *utok; + struct gpfs_quotaInfo qi_user = { 0 }, qi_group = { 0 }; + struct gpfs_config_data *config; + int err; + time_t cur_time; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct gpfs_config_data, + return (uint64_t)-1); + if (!config->dfreequota) { + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + + err = sys_fsusage(smb_fname->base_name, dfree, dsize); + if (err) { + DEBUG (0, ("Could not get fs usage, errno %d\n", errno)); + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + + /* sys_fsusage returns units of 512 bytes */ + *bsize = 512; + + DEBUG(10, ("fs dfree %llu, dsize %llu\n", + (unsigned long long)*dfree, (unsigned long long)*dsize)); + + utok = handle->conn->session_info->unix_token; + + err = get_gpfs_quota(smb_fname->base_name, + GPFS_USRQUOTA, utok->uid, &qi_user); + if (err) { + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + + /* + * If new files created under this folder get this folder's + * GID, then available space is governed by the quota of the + * folder's GID, not the primary group of the creating user. + */ + if (VALID_STAT(smb_fname->st) && + S_ISDIR(smb_fname->st.st_ex_mode) && + smb_fname->st.st_ex_mode & S_ISGID) { + become_root(); + err = get_gpfs_quota(smb_fname->base_name, GPFS_GRPQUOTA, + smb_fname->st.st_ex_gid, &qi_group); + unbecome_root(); + + } else { + err = get_gpfs_quota(smb_fname->base_name, GPFS_GRPQUOTA, + utok->gid, &qi_group); + } + + if (err) { + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + + cur_time = time(NULL); + + /* Adjust free space and size according to quota limits. */ + vfs_gpfs_disk_free_quota(qi_user, cur_time, dfree, dsize); + vfs_gpfs_disk_free_quota(qi_group, cur_time, dfree, dsize); + + return *dfree / 2; +} + +static int vfs_gpfs_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + switch(qtype) { + /* + * User/group quota are being used for disk-free + * determination, which in this module is done directly + * by the disk-free function. It's important that this + * module does not return wrong quota values by mistake, + * which would modify the correct values set by disk-free. + * User/group quota are also being used for processing + * NT_TRANSACT_GET_USER_QUOTA in smb1 protocol, which is + * currently not supported by this module. + */ + case SMB_USER_QUOTA_TYPE: + case SMB_GROUP_QUOTA_TYPE: + errno = ENOSYS; + return -1; + default: + return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, + qtype, id, dq); + } +} + +static uint32_t vfs_gpfs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + struct gpfs_config_data *config; + uint32_t next; + + next = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return next); + + if (config->hsm) { + next |= FILE_SUPPORTS_REMOTE_STORAGE; + } + return next; +} + +static int vfs_gpfs_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *_how) +{ + struct vfs_open_how how = *_how; + struct gpfs_config_data *config = NULL; + struct gpfs_fsp_extension *ext = NULL; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct gpfs_config_data, + return -1); + + if (config->hsm && !config->recalls && + !fsp->fsp_flags.is_pathref && + vfs_gpfs_fsp_is_offline(handle, fsp)) + { + DBG_DEBUG("Refusing access to offline file %s\n", + fsp_str_dbg(fsp)); + errno = EACCES; + return -1; + } + + if (config->syncio) { + how.flags |= O_SYNC; + } + + ext = VFS_ADD_FSP_EXTENSION(handle, fsp, struct gpfs_fsp_extension, + NULL); + if (ext == NULL) { + errno = ENOMEM; + return -1; + } + + /* + * Assume the file is offline until gpfs tells us it's online. + */ + *ext = (struct gpfs_fsp_extension) { .offline = true }; + + ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, &how); + if (ret == -1) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + } + return ret; +} + +static ssize_t vfs_gpfs_pread(vfs_handle_struct *handle, files_struct *fsp, + void *data, size_t n, off_t offset) +{ + ssize_t ret; + bool was_offline; + + was_offline = vfs_gpfs_fsp_is_offline(handle, fsp); + + ret = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + + if ((ret != -1) && was_offline) { + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return ret; +} + +struct vfs_gpfs_pread_state { + struct files_struct *fsp; + ssize_t ret; + bool was_offline; + struct vfs_aio_state vfs_aio_state; +}; + +static void vfs_gpfs_pread_done(struct tevent_req *subreq); + +static struct tevent_req *vfs_gpfs_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, size_t n, + off_t offset) +{ + struct tevent_req *req, *subreq; + struct vfs_gpfs_pread_state *state; + + req = tevent_req_create(mem_ctx, &state, struct vfs_gpfs_pread_state); + if (req == NULL) { + return NULL; + } + state->was_offline = vfs_gpfs_fsp_is_offline(handle, fsp); + state->fsp = fsp; + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_gpfs_pread_done, req); + return req; +} + +static void vfs_gpfs_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_gpfs_pread_state *state = tevent_req_data( + req, struct vfs_gpfs_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t vfs_gpfs_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_gpfs_pread_state *state = tevent_req_data( + req, struct vfs_gpfs_pread_state); + struct files_struct *fsp = state->fsp; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + + if ((state->ret != -1) && state->was_offline) { + DEBUG(10, ("sending notify\n")); + notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return state->ret; +} + +static ssize_t vfs_gpfs_pwrite(vfs_handle_struct *handle, files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + ssize_t ret; + bool was_offline; + + was_offline = vfs_gpfs_fsp_is_offline(handle, fsp); + + ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + + if ((ret != -1) && was_offline) { + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return ret; +} + +struct vfs_gpfs_pwrite_state { + struct files_struct *fsp; + ssize_t ret; + bool was_offline; + struct vfs_aio_state vfs_aio_state; +}; + +static void vfs_gpfs_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *vfs_gpfs_pwrite_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, size_t n, + off_t offset) +{ + struct tevent_req *req, *subreq; + struct vfs_gpfs_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, struct vfs_gpfs_pwrite_state); + if (req == NULL) { + return NULL; + } + state->was_offline = vfs_gpfs_fsp_is_offline(handle, fsp); + state->fsp = fsp; + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, vfs_gpfs_pwrite_done, req); + return req; +} + +static void vfs_gpfs_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct vfs_gpfs_pwrite_state *state = tevent_req_data( + req, struct vfs_gpfs_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t vfs_gpfs_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_gpfs_pwrite_state *state = tevent_req_data( + req, struct vfs_gpfs_pwrite_state); + struct files_struct *fsp = state->fsp; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + + if ((state->ret != -1) && state->was_offline) { + DEBUG(10, ("sending notify\n")); + notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return state->ret; +} + + +static struct vfs_fn_pointers vfs_gpfs_fns = { + .connect_fn = vfs_gpfs_connect, + .disk_free_fn = vfs_gpfs_disk_free, + .get_quota_fn = vfs_gpfs_get_quota, + .fs_capabilities_fn = vfs_gpfs_capabilities, + .filesystem_sharemode_fn = vfs_gpfs_filesystem_sharemode, + .linux_setlease_fn = vfs_gpfs_setlease, + .get_real_filename_at_fn = vfs_gpfs_get_real_filename_at, + .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv, + .fget_dos_attributes_fn = vfs_gpfs_fget_dos_attributes, + .fset_dos_attributes_fn = vfs_gpfs_fset_dos_attributes, + .fget_nt_acl_fn = gpfsacl_fget_nt_acl, + .fset_nt_acl_fn = gpfsacl_fset_nt_acl, + .sys_acl_get_fd_fn = gpfsacl_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = gpfsacl_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = gpfsacl_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = gpfsacl_sys_acl_delete_def_fd, + .fchmod_fn = vfs_gpfs_fchmod, + .close_fn = vfs_gpfs_close, + .stat_fn = nfs4_acl_stat, + .fstat_fn = nfs4_acl_fstat, + .lstat_fn = nfs4_acl_lstat, + .fstatat_fn = nfs4_acl_fstatat, + .fntimes_fn = vfs_gpfs_fntimes, + .aio_force_fn = vfs_gpfs_aio_force, + .sendfile_fn = vfs_gpfs_sendfile, + .fallocate_fn = vfs_gpfs_fallocate, + .openat_fn = vfs_gpfs_openat, + .pread_fn = vfs_gpfs_pread, + .pread_send_fn = vfs_gpfs_pread_send, + .pread_recv_fn = vfs_gpfs_pread_recv, + .pwrite_fn = vfs_gpfs_pwrite, + .pwrite_send_fn = vfs_gpfs_pwrite_send, + .pwrite_recv_fn = vfs_gpfs_pwrite_recv, + .ftruncate_fn = vfs_gpfs_ftruncate +}; + +static_decl_vfs; +NTSTATUS vfs_gpfs_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "gpfs", + &vfs_gpfs_fns); +} diff --git a/source3/modules/vfs_hpuxacl.c b/source3/modules/vfs_hpuxacl.c new file mode 100644 index 0000000..31903a1 --- /dev/null +++ b/source3/modules/vfs_hpuxacl.c @@ -0,0 +1,1174 @@ +/* + * Unix SMB/Netbios implementation. + * VFS module to get and set HP-UX ACLs + * Copyright (C) Michael Adam 2006,2008 + * + * 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 . + */ + +/* + * This module supports JFS (POSIX) ACLs on VxFS (Veritas * Filesystem). + * These are available on HP-UX 11.00 if JFS 3.3 is installed. + * On HP-UX 11i (11.11 and above) these ACLs are supported out of + * the box. + * + * There is another form of ACLs on HFS. These ACLs have a + * completely different API and their own set of userland tools. + * Since HFS seems to be considered deprecated, HFS acls + * are not supported. (They could be supported through a separate + * vfs-module if there is demand.) + */ + +/* ================================================================= + * NOTE: + * + * The original hpux-acl code in lib/sysacls.c was based upon the + * solaris acl code in the same file. Now for the new modularized + * acl implementation, I have taken the code from vfs_solarisacls.c + * and did similar adaptations as were done before, essentially + * reusing the original internal aclsort functions. + * The check for the presence of the acl() call has been adopted, and + * a check for the presence of the aclsort() call has been added. + * + * Michael Adam + * + * ================================================================= */ + + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "modules/vfs_hpuxacl.h" + + +/* + * including standard header + * + * included here as a quick hack for the special HP-UX-situation: + * + * The problem is that, on HP-UX, jfs/posix acls are + * defined in , while the deprecated hfs acls + * are defined inside . + * + */ +/* GROUP is defined somewhere else so undef it here... */ +#undef GROUP +#include +/* dl.h: needed to check for acl call via shl_findsym */ +#include + +typedef struct acl HPUX_ACE_T; +typedef struct acl *HPUX_ACL_T; +typedef int HPUX_ACL_TAG_T; /* the type of an ACL entry */ +typedef ushort HPUX_PERM_T; + +/* Structure to capture the count for each type of ACE. + * (for hpux_internal_aclsort */ +struct hpux_acl_types { + int n_user; + int n_def_user; + int n_user_obj; + int n_def_user_obj; + + int n_group; + int n_def_group; + int n_group_obj; + int n_def_group_obj; + + int n_other; + int n_other_obj; + int n_def_other_obj; + + int n_class_obj; + int n_def_class_obj; + + int n_illegal_obj; +}; + +/* for convenience: check if hpux acl entry is a default entry? */ +#define _IS_DEFAULT(ace) ((ace).a_type & ACL_DEFAULT) +#define _IS_OF_TYPE(ace, type) ( \ + (((type) == SMB_ACL_TYPE_ACCESS) && !_IS_DEFAULT(ace)) \ + || \ + (((type) == SMB_ACL_TYPE_DEFAULT) && _IS_DEFAULT(ace)) \ +) + + +/* prototypes for private functions */ + +static HPUX_ACL_T hpux_acl_init(int count); +static bool smb_acl_to_hpux_acl(SMB_ACL_T smb_acl, + HPUX_ACL_T *solariacl, int *count, + SMB_ACL_TYPE_T type); +static SMB_ACL_T hpux_acl_to_smb_acl(HPUX_ACL_T hpuxacl, int count, + SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx); +static HPUX_ACL_TAG_T smb_tag_to_hpux_tag(SMB_ACL_TAG_T smb_tag); +static SMB_ACL_TAG_T hpux_tag_to_smb_tag(HPUX_ACL_TAG_T hpux_tag); +static bool hpux_add_to_acl(HPUX_ACL_T *hpux_acl, int *count, + HPUX_ACL_T add_acl, int add_count, SMB_ACL_TYPE_T type); +static bool hpux_acl_get_file(const char *name, HPUX_ACL_T *hpuxacl, + int *count); +static SMB_ACL_PERM_T hpux_perm_to_smb_perm(const HPUX_PERM_T perm); +static HPUX_PERM_T smb_perm_to_hpux_perm(const SMB_ACL_PERM_T perm); +#if 0 +static bool hpux_acl_check(HPUX_ACL_T hpux_acl, int count); +#endif +/* aclsort (internal) and helpers: */ +static bool hpux_acl_sort(HPUX_ACL_T acl, int count); +static int hpux_internal_aclsort(int acl_count, int calclass, HPUX_ACL_T aclp); +static void hpux_count_obj(int acl_count, HPUX_ACL_T aclp, + struct hpux_acl_types *acl_type_count); +static void hpux_swap_acl_entries(HPUX_ACE_T *aclp0, HPUX_ACE_T *aclp1); +static bool hpux_prohibited_duplicate_type(int acl_type); + +static bool hpux_acl_call_present(void); +static bool hpux_aclsort_call_present(void); + + +/* public functions - the api */ + +static SMB_ACL_T hpuxacl_sys_acl_get_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result = NULL; + int count; + HPUX_ACL_T hpux_acl = NULL; + const char *path_p = smb_fname->base_name; + + DEBUG(10, ("hpuxacl_sys_acl_get_file called for file '%s'.\n", + path_p)); + + if(hpux_acl_call_present() == False) { + /* Looks like we don't have the acl() system call on HPUX. + * May be the system doesn't have the latest version of JFS. + */ + goto done; + } + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type)); + errno = EINVAL; + goto done; + } + + DEBUGADD(10, ("getting %s acl\n", + ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default"))); + + if (!hpux_acl_get_file(path_p, &hpux_acl, &count)) { + goto done; + } + result = hpux_acl_to_smb_acl(hpux_acl, count, type, mem_ctx); + if (result == NULL) { + DEBUG(10, ("conversion hpux_acl -> smb_acl failed (%s).\n", + strerror(errno))); + } + + done: + DEBUG(10, ("hpuxacl_sys_acl_get_file %s.\n", + ((result == NULL) ? "failed" : "succeeded" ))); + SAFE_FREE(hpux_acl); + return result; +} + + +/* + * get the access ACL of a file referred to by a fd + */ +SMB_ACL_T hpuxacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + /* + * HPUX doesn't have the facl call. Fake it using the path.... JRA. + */ + /* + * We know we're in the same conn context. So we + * can use the relative path. + */ + DEBUG(10, ("redirecting call of hpuxacl_sys_acl_get_fd to " + "hpuxacl_sys_acl_get_file (no facl syscall on HPUX).\n")); + + return hpuxacl_sys_acl_get_file(handle, + fsp->fsp_name->base_name, + type, + mem_ctx); +} + + +int hpuxacl_sys_acl_set_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname_in, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + int ret = -1; + HPUX_ACL_T hpux_acl = NULL; + int count; + struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + DEBUG(10, ("hpuxacl_sys_acl_set_file called for file '%s'\n", + name)); + + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if(hpux_acl_call_present() == False) { + /* Looks like we don't have the acl() system call on HPUX. + * May be the system doesn't have the latest version of JFS. + */ + goto done; + } + + if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT)) { + errno = EINVAL; + DEBUG(10, ("invalid smb acl type given (%d).\n", type)); + goto done; + } + DEBUGADD(10, ("setting %s acl\n", + ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default"))); + + if(!smb_acl_to_hpux_acl(theacl, &hpux_acl, &count, type)) { + DEBUG(10, ("conversion smb_acl -> hpux_acl failed (%s).\n", + strerror(errno))); + goto done; + } + + /* + * We can directly use SMB_VFS_STAT here, as if this was a + * POSIX call on a symlink, we've already refused it. + * For a Windows acl mapped call on a symlink, we want to follow + * it. + */ + ret = SMB_VFS_STAT(handle->conn, smb_fname); + if (ret != 0) { + DEBUG(10, ("Error in stat call: %s\n", strerror(errno))); + goto done; + } + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + /* + * if the file is a directory, there is extra work to do: + * since the hpux acl call stores both the access acl and + * the default acl as provided, we have to get the acl part + * that has _not_ been specified in "type" from the file first + * and concatenate it with the acl provided. + */ + HPUX_ACL_T other_acl; + int other_count; + SMB_ACL_TYPE_T other_type; + + other_type = (type == SMB_ACL_TYPE_ACCESS) + ? SMB_ACL_TYPE_DEFAULT + : SMB_ACL_TYPE_ACCESS; + DEBUGADD(10, ("getting acl from filesystem\n")); + if (!hpux_acl_get_file(smb_fname->base_name, &other_acl, + &other_count)) { + DEBUG(10, ("error getting acl from directory\n")); + goto done; + } + DEBUG(10, ("adding %s part of fs acl to given acl\n", + ((other_type == SMB_ACL_TYPE_ACCESS) + ? "access" + : "default"))); + if (!hpux_add_to_acl(&hpux_acl, &count, other_acl, + other_count, other_type)) + { + DEBUG(10, ("error adding other acl.\n")); + SAFE_FREE(other_acl); + goto done; + } + SAFE_FREE(other_acl); + } + else if (type != SMB_ACL_TYPE_ACCESS) { + errno = EINVAL; + goto done; + } + + if (!hpux_acl_sort(hpux_acl, count)) { + DEBUG(10, ("resulting acl is not valid!\n")); + goto done; + } + DEBUG(10, ("resulting acl is valid.\n")); + + ret = acl(discard_const_p(char, smb_fname->base_name), ACL_SET, count, + hpux_acl); + if (ret != 0) { + DEBUG(0, ("ERROR calling acl: %s\n", strerror(errno))); + } + + done: + DEBUG(10, ("hpuxacl_sys_acl_set_file %s.\n", + ((ret != 0) ? "failed" : "succeeded"))); + TALLOC_FREE(smb_fname); + SAFE_FREE(hpux_acl); + return ret; +} + +/* + * set the access ACL on the file referred to by a fd + */ +int hpuxacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + /* + * HPUX doesn't have the facl call. Fake it using the path.... JRA. + */ + /* + * We know we're in the same conn context. So we + * can use the relative path. + */ + DEBUG(10, ("redirecting call of hpuxacl_sys_acl_set_fd to " + "hpuxacl_sys_acl_set_file (no facl syscall on HPUX)\n")); + + return hpuxacl_sys_acl_set_file(handle, + fsp->fsp_name->base_name, + type, theacl); +} + +/* + * delete the default ACL of a directory + * + * This is achieved by fetching the access ACL and rewriting it + * directly, via the hpux system call: the ACL_SET call on + * directories writes both the access and the default ACL as provided. + * + * XXX: posix acl_delete_def_file returns an error if + * the file referred to by path is not a directory. + * this function does not complain but the actions + * have no effect on a file other than a directory. + * But sys_acl_delete_default_file is only called in + * smbd/posixacls.c after having checked that the file + * is a directory, anyways. So implementing the extra + * check is considered unnecessary. --- Agreed? XXX + */ +int hpuxacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + SMB_ACL_T smb_acl; + int ret = -1; + HPUX_ACL_T hpux_acl; + int count; + + DBG_DEBUG("entering hpuxacl_sys_acl_delete_def_fd.\n"); + + smb_acl = hpuxacl_sys_acl_get_file(handle, fsp->fsp_name->base_name, + SMB_ACL_TYPE_ACCESS); + if (smb_acl == NULL) { + DBG_DEBUG("getting file acl failed!\n"); + goto done; + } + if (!smb_acl_to_hpux_acl(smb_acl, &hpux_acl, &count, + SMB_ACL_TYPE_ACCESS)) + { + DBG_DEBUG("conversion smb_acl -> hpux_acl failed.\n"); + goto done; + } + if (!hpux_acl_sort(hpux_acl, count)) { + DBG_DEBUG("resulting acl is not valid!\n"); + goto done; + } + ret = acl(discard_const_p(char, fsp->fsp_name->base_name), + ACL_SET, count, hpux_acl); + if (ret != 0) { + DBG_DEBUG("settinge file acl failed!\n"); + } + + done: + DBG_DEBUG("hpuxacl_sys_acl_delete_def_fd %s.\n", + ((ret != 0) ? "failed" : "succeeded" )); + TALLOC_FREE(smb_acl); + return ret; +} + +/* + * private functions + */ + +static HPUX_ACL_T hpux_acl_init(int count) +{ + HPUX_ACL_T hpux_acl = + (HPUX_ACL_T)SMB_MALLOC(sizeof(HPUX_ACE_T) * count); + if (hpux_acl == NULL) { + errno = ENOMEM; + } + return hpux_acl; +} + +/* + * Convert the SMB acl to the ACCESS or DEFAULT part of a + * hpux ACL, as desired. + */ +static bool smb_acl_to_hpux_acl(SMB_ACL_T smb_acl, + HPUX_ACL_T *hpux_acl, int *count, + SMB_ACL_TYPE_T type) +{ + bool ret = False; + int i; + int check_which, check_rc; + + DEBUG(10, ("entering smb_acl_to_hpux_acl\n")); + + *hpux_acl = NULL; + *count = 0; + + for (i = 0; i < smb_acl->count; i++) { + const struct smb_acl_entry *smb_entry = &(smb_acl->acl[i]); + HPUX_ACE_T hpux_entry; + + ZERO_STRUCT(hpux_entry); + + hpux_entry.a_type = smb_tag_to_hpux_tag(smb_entry->a_type); + if (hpux_entry.a_type == 0) { + DEBUG(10, ("smb_tag to hpux_tag failed\n")); + goto fail; + } + switch(hpux_entry.a_type) { + case USER: + DEBUG(10, ("got tag type USER with uid %d\n", + smb_entry->info.user.uid)); + hpux_entry.a_id = (uid_t)smb_entry->info.user.uid; + break; + case GROUP: + DEBUG(10, ("got tag type GROUP with gid %d\n", + smb_entry->info.group.gid)); + hpux_entry.a_id = (uid_t)smb_entry->info.group.gid; + break; + default: + break; + } + if (type == SMB_ACL_TYPE_DEFAULT) { + DEBUG(10, ("adding default bit to hpux ace\n")); + hpux_entry.a_type |= ACL_DEFAULT; + } + + hpux_entry.a_perm = + smb_perm_to_hpux_perm(smb_entry->a_perm); + DEBUG(10, ("assembled the following hpux ace:\n")); + DEBUGADD(10, (" - type: 0x%04x\n", hpux_entry.a_type)); + DEBUGADD(10, (" - id: %d\n", hpux_entry.a_id)); + DEBUGADD(10, (" - perm: o%o\n", hpux_entry.a_perm)); + if (!hpux_add_to_acl(hpux_acl, count, &hpux_entry, + 1, type)) + { + DEBUG(10, ("error adding acl entry\n")); + goto fail; + } + DEBUG(10, ("count after adding: %d (i: %d)\n", *count, i)); + DEBUG(10, ("test, if entry has been copied into acl:\n")); + DEBUGADD(10, (" - type: 0x%04x\n", + (*hpux_acl)[(*count)-1].a_type)); + DEBUGADD(10, (" - id: %d\n", + (*hpux_acl)[(*count)-1].a_id)); + DEBUGADD(10, (" - perm: o%o\n", + (*hpux_acl)[(*count)-1].a_perm)); + } + + ret = True; + goto done; + + fail: + SAFE_FREE(*hpux_acl); + done: + DEBUG(10, ("smb_acl_to_hpux_acl %s\n", + ((ret == True) ? "succeeded" : "failed"))); + return ret; +} + +/* + * convert either the access or the default part of a + * soaris acl to the SMB_ACL format. + */ +static SMB_ACL_T hpux_acl_to_smb_acl(HPUX_ACL_T hpux_acl, int count, + SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result; + int i; + + if ((result = sys_acl_init(mem_ctx)) == NULL) { + DEBUG(10, ("error allocating memory for SMB_ACL\n")); + goto fail; + } + for (i = 0; i < count; i++) { + SMB_ACL_ENTRY_T smb_entry; + SMB_ACL_PERM_T smb_perm; + + if (!_IS_OF_TYPE(hpux_acl[i], type)) { + continue; + } + result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count + 1); + if (result->acl == NULL) { + DEBUG(10, ("error reallocating memory for SMB_ACL\n")); + goto fail; + } + smb_entry = &result->acl[result->count]; + if (sys_acl_set_tag_type(smb_entry, + hpux_tag_to_smb_tag(hpux_acl[i].a_type)) != 0) + { + DEBUG(10, ("invalid tag type given: 0x%04x\n", + hpux_acl[i].a_type)); + goto fail; + } + /* intentionally not checking return code here: */ + sys_acl_set_qualifier(smb_entry, (void *)&hpux_acl[i].a_id); + smb_perm = hpux_perm_to_smb_perm(hpux_acl[i].a_perm); + if (sys_acl_set_permset(smb_entry, &smb_perm) != 0) { + DEBUG(10, ("invalid permset given: %d\n", + hpux_acl[i].a_perm)); + goto fail; + } + result->count += 1; + } + goto done; + fail: + TALLOC_FREE(result); + done: + DEBUG(10, ("hpux_acl_to_smb_acl %s\n", + ((result == NULL) ? "failed" : "succeeded"))); + return result; +} + + + +static HPUX_ACL_TAG_T smb_tag_to_hpux_tag(SMB_ACL_TAG_T smb_tag) +{ + HPUX_ACL_TAG_T hpux_tag = 0; + + DEBUG(10, ("smb_tag_to_hpux_tag\n")); + DEBUGADD(10, (" --> got smb tag 0x%04x\n", smb_tag)); + + switch (smb_tag) { + case SMB_ACL_USER: + hpux_tag = USER; + break; + case SMB_ACL_USER_OBJ: + hpux_tag = USER_OBJ; + break; + case SMB_ACL_GROUP: + hpux_tag = GROUP; + break; + case SMB_ACL_GROUP_OBJ: + hpux_tag = GROUP_OBJ; + break; + case SMB_ACL_OTHER: + hpux_tag = OTHER_OBJ; + break; + case SMB_ACL_MASK: + hpux_tag = CLASS_OBJ; + break; + default: + DEBUGADD(10, (" !!! unknown smb tag type 0x%04x\n", smb_tag)); + break; + } + + DEBUGADD(10, (" --> determined hpux tag 0x%04x\n", hpux_tag)); + + return hpux_tag; +} + +static SMB_ACL_TAG_T hpux_tag_to_smb_tag(HPUX_ACL_TAG_T hpux_tag) +{ + SMB_ACL_TAG_T smb_tag = 0; + + DEBUG(10, ("hpux_tag_to_smb_tag:\n")); + DEBUGADD(10, (" --> got hpux tag 0x%04x\n", hpux_tag)); + + hpux_tag &= ~ACL_DEFAULT; + + switch (hpux_tag) { + case USER: + smb_tag = SMB_ACL_USER; + break; + case USER_OBJ: + smb_tag = SMB_ACL_USER_OBJ; + break; + case GROUP: + smb_tag = SMB_ACL_GROUP; + break; + case GROUP_OBJ: + smb_tag = SMB_ACL_GROUP_OBJ; + break; + case OTHER_OBJ: + smb_tag = SMB_ACL_OTHER; + break; + case CLASS_OBJ: + smb_tag = SMB_ACL_MASK; + break; + default: + DEBUGADD(10, (" !!! unknown hpux tag type: 0x%04x\n", + hpux_tag)); + break; + } + + DEBUGADD(10, (" --> determined smb tag 0x%04x\n", smb_tag)); + + return smb_tag; +} + + +/* + * The permission bits used in the following two permission conversion + * functions are same, but the functions make us independent of the concrete + * permission data types. + */ +static SMB_ACL_PERM_T hpux_perm_to_smb_perm(const HPUX_PERM_T perm) +{ + SMB_ACL_PERM_T smb_perm = 0; + smb_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0); + smb_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0); + smb_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); + return smb_perm; +} + + +static HPUX_PERM_T smb_perm_to_hpux_perm(const SMB_ACL_PERM_T perm) +{ + HPUX_PERM_T hpux_perm = 0; + hpux_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0); + hpux_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0); + hpux_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); + return hpux_perm; +} + + +static bool hpux_acl_get_file(const char *name, HPUX_ACL_T *hpux_acl, + int *count) +{ + bool result = False; + static HPUX_ACE_T dummy_ace; + + DEBUG(10, ("hpux_acl_get_file called for file '%s'\n", name)); + + /* + * The original code tries some INITIAL_ACL_SIZE + * and only did the ACL_CNT call upon failure + * (for performance reasons). + * For the sake of simplicity, I skip this for now. + * + * NOTE: There is a catch here on HP-UX: acl with cmd parameter + * ACL_CNT fails with errno EINVAL when called with a NULL + * pointer as last argument. So we need to use a dummy acl + * struct here (we make it static so it does not need to be + * instantiated or malloced each time this function is + * called). Btw: the count parameter does not seem to matter... + */ + *count = acl(discard_const_p(char, name), ACL_CNT, 0, &dummy_ace); + if (*count < 0) { + DEBUG(10, ("acl ACL_CNT failed: %s\n", strerror(errno))); + goto done; + } + *hpux_acl = hpux_acl_init(*count); + if (*hpux_acl == NULL) { + DEBUG(10, ("error allocating memory for hpux acl...\n")); + goto done; + } + *count = acl(discard_const_p(char, name), ACL_GET, *count, *hpux_acl); + if (*count < 0) { + DEBUG(10, ("acl ACL_GET failed: %s\n", strerror(errno))); + goto done; + } + result = True; + + done: + DEBUG(10, ("hpux_acl_get_file %s.\n", + ((result == True) ? "succeeded" : "failed" ))); + return result; +} + + + + +/* + * Add entries to a hpux ACL. + * + * Entries are directly added to the hpuxacl parameter. + * if memory allocation fails, this may result in hpuxacl + * being NULL. if the resulting acl is to be checked and is + * not valid, it is kept in hpuxacl but False is returned. + * + * The type of ACEs (access/default) to be added to the ACL can + * be selected via the type parameter. + * I use the SMB_ACL_TYPE_T type here. Since SMB_ACL_TYPE_ACCESS + * is defined as "0", this means that one can only add either + * access or default ACEs from the given ACL, not both at the same + * time. If it should become necessary to add all of an ACL, one + * would have to replace this parameter by another type. + */ +static bool hpux_add_to_acl(HPUX_ACL_T *hpux_acl, int *count, + HPUX_ACL_T add_acl, int add_count, + SMB_ACL_TYPE_T type) +{ + int i; + + if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT)) + { + DEBUG(10, ("invalid acl type given: %d\n", type)); + errno = EINVAL; + return False; + } + for (i = 0; i < add_count; i++) { + if (!_IS_OF_TYPE(add_acl[i], type)) { + continue; + } + ADD_TO_ARRAY(NULL, HPUX_ACE_T, add_acl[i], + hpux_acl, count); + if (hpux_acl == NULL) { + DEBUG(10, ("error enlarging acl.\n")); + errno = ENOMEM; + return False; + } + } + return True; +} + + +/* + * sort the ACL and check it for validity + * + * [original comment from lib/sysacls.c:] + * + * if it's a minimal ACL with only 4 entries then we + * need to recalculate the mask permissions to make + * sure that they are the same as the GROUP_OBJ + * permissions as required by the UnixWare acl() system call. + * + * (note: since POSIX allows minimal ACLs which only contain + * 3 entries - ie there is no mask entry - we should, in theory, + * check for this and add a mask entry if necessary - however + * we "know" that the caller of this interface always specifies + * a mask, so in practice "this never happens" (tm) - if it *does* + * happen aclsort() will fail and return an error and someone will + * have to fix it...) + */ +static bool hpux_acl_sort(HPUX_ACL_T hpux_acl, int count) +{ + int fixmask = (count <= 4); + + if (hpux_internal_aclsort(count, fixmask, hpux_acl) != 0) { + errno = EINVAL; + return False; + } + return True; +} + + +/* + * Helpers for hpux_internal_aclsort: + * - hpux_count_obj + * - hpux_swap_acl_entries + * - hpux_prohibited_duplicate_type + * - hpux_get_needed_class_perm + */ + +/* hpux_count_obj: + * Counts the different number of objects in a given array of ACL + * structures. + * Inputs: + * + * acl_count - Count of ACLs in the array of ACL structures. + * aclp - Array of ACL structures. + * acl_type_count - Pointer to acl_types structure. Should already be + * allocated. + * Output: + * + * acl_type_count - This structure is filled up with counts of various + * acl types. + */ + +static void hpux_count_obj(int acl_count, HPUX_ACL_T aclp, struct hpux_acl_types *acl_type_count) +{ + int i; + + memset(acl_type_count, 0, sizeof(struct hpux_acl_types)); + + for(i=0;in_user++; + break; + case USER_OBJ: + acl_type_count->n_user_obj++; + break; + case DEF_USER_OBJ: + acl_type_count->n_def_user_obj++; + break; + case GROUP: + acl_type_count->n_group++; + break; + case GROUP_OBJ: + acl_type_count->n_group_obj++; + break; + case DEF_GROUP_OBJ: + acl_type_count->n_def_group_obj++; + break; + case OTHER_OBJ: + acl_type_count->n_other_obj++; + break; + case DEF_OTHER_OBJ: + acl_type_count->n_def_other_obj++; + break; + case CLASS_OBJ: + acl_type_count->n_class_obj++; + break; + case DEF_CLASS_OBJ: + acl_type_count->n_def_class_obj++; + break; + case DEF_USER: + acl_type_count->n_def_user++; + break; + case DEF_GROUP: + acl_type_count->n_def_group++; + break; + default: + acl_type_count->n_illegal_obj++; + break; + } + } +} + +/* hpux_swap_acl_entries: Swaps two ACL entries. + * + * Inputs: aclp0, aclp1 - ACL entries to be swapped. + */ + +static void hpux_swap_acl_entries(HPUX_ACE_T *aclp0, HPUX_ACE_T *aclp1) +{ + HPUX_ACE_T temp_acl; + + temp_acl.a_type = aclp0->a_type; + temp_acl.a_id = aclp0->a_id; + temp_acl.a_perm = aclp0->a_perm; + + aclp0->a_type = aclp1->a_type; + aclp0->a_id = aclp1->a_id; + aclp0->a_perm = aclp1->a_perm; + + aclp1->a_type = temp_acl.a_type; + aclp1->a_id = temp_acl.a_id; + aclp1->a_perm = temp_acl.a_perm; +} + +/* hpux_prohibited_duplicate_type + * Identifies if given ACL type can have duplicate entries or + * not. + * + * Inputs: acl_type - ACL Type. + * + * Outputs: + * + * Return.. + * + * True - If the ACL type matches any of the prohibited types. + * False - If the ACL type doesn't match any of the prohibited types. + */ + +static bool hpux_prohibited_duplicate_type(int acl_type) +{ + switch(acl_type) { + case USER: + case GROUP: + case DEF_USER: + case DEF_GROUP: + return True; + default: + return False; + } +} + +/* hpux_get_needed_class_perm + * Returns the permissions of a ACL structure only if the ACL + * type matches one of the pre-determined types for computing + * CLASS_OBJ permissions. + * + * Inputs: aclp - Pointer to ACL structure. + */ + +static int hpux_get_needed_class_perm(struct acl *aclp) +{ + switch(aclp->a_type) { + case USER: + case GROUP_OBJ: + case GROUP: + case DEF_USER_OBJ: + case DEF_USER: + case DEF_GROUP_OBJ: + case DEF_GROUP: + case DEF_CLASS_OBJ: + case DEF_OTHER_OBJ: + return aclp->a_perm; + default: + return 0; + } +} + +/* hpux_internal_aclsort: aclsort for HPUX. + * + * -> The aclsort() system call is available on the latest HPUX General + * -> Patch Bundles. So for HPUX, we developed our version of aclsort + * -> function. Because, we don't want to update to a new + * -> HPUX GR bundle just for aclsort() call. + * + * aclsort sorts the array of ACL structures as per the description in + * aclsort man page. Refer to aclsort man page for more details + * + * Inputs: + * + * acl_count - Count of ACLs in the array of ACL structures. + * calclass - If this is not zero, then we compute the CLASS_OBJ + * permissions. + * aclp - Array of ACL structures. + * + * Outputs: + * + * aclp - Sorted array of ACL structures. + * + * Outputs: + * + * Returns 0 for success -1 for failure. Prints a message to the Samba + * debug log in case of failure. + */ + +static int hpux_internal_aclsort(int acl_count, int calclass, HPUX_ACL_T aclp) +{ + struct hpux_acl_types acl_obj_count; + int n_class_obj_perm = 0; + int i, j; + + DEBUG(10,("Entering hpux_internal_aclsort. (calclass = %d)\n", calclass)); + + if (hpux_aclsort_call_present()) { + DEBUG(10, ("calling hpux aclsort\n")); + return aclsort(acl_count, calclass, aclp); + } + + DEBUG(10, ("using internal aclsort\n")); + + if(!acl_count) { + DEBUG(10,("Zero acl count passed. Returning Success\n")); + return 0; + } + + if(aclp == NULL) { + DEBUG(0,("Null ACL pointer in hpux_acl_sort. Returning Failure. \n")); + return -1; + } + + /* Count different types of ACLs in the ACLs array */ + + hpux_count_obj(acl_count, aclp, &acl_obj_count); + + /* There should be only one entry each of type USER_OBJ, GROUP_OBJ, + * CLASS_OBJ and OTHER_OBJ + */ + + if ( (acl_obj_count.n_user_obj != 1) || + (acl_obj_count.n_group_obj != 1) || + (acl_obj_count.n_class_obj != 1) || + (acl_obj_count.n_other_obj != 1) ) + { + DEBUG(0,("hpux_internal_aclsort: More than one entry or no entries for \ +USER OBJ or GROUP_OBJ or OTHER_OBJ or CLASS_OBJ\n")); + return -1; + } + + /* If any of the default objects are present, there should be only + * one of them each. + */ + + if ( (acl_obj_count.n_def_user_obj > 1) || + (acl_obj_count.n_def_group_obj > 1) || + (acl_obj_count.n_def_other_obj > 1) || + (acl_obj_count.n_def_class_obj > 1) ) + { + DEBUG(0,("hpux_internal_aclsort: More than one entry for DEF_CLASS_OBJ \ +or DEF_USER_OBJ or DEF_GROUP_OBJ or DEF_OTHER_OBJ\n")); + return -1; + } + + /* We now have proper number of OBJ and DEF_OBJ entries. Now sort the acl + * structures. + * + * Sorting crieteria - First sort by ACL type. If there are multiple entries of + * same ACL type, sort by ACL id. + * + * I am using the trivial kind of sorting method here because, performance isn't + * really effected by the ACLs feature. More over there aren't going to be more + * than 17 entries on HPUX. + */ + + for(i=0; i aclp[j].a_type ) { + /* ACL entries out of order, swap them */ + hpux_swap_acl_entries((aclp+i), (aclp+j)); + } else if ( aclp[i].a_type == aclp[j].a_type ) { + /* ACL entries of same type, sort by id */ + if(aclp[i].a_id > aclp[j].a_id) { + hpux_swap_acl_entries((aclp+i), (aclp+j)); + } else if (aclp[i].a_id == aclp[j].a_id) { + /* We have a duplicate entry. */ + if(hpux_prohibited_duplicate_type(aclp[i].a_type)) { + DEBUG(0, ("hpux_internal_aclsort: Duplicate entry: Type(hex): %x Id: %d\n", + aclp[i].a_type, aclp[i].a_id)); + return -1; + } + } + } + } + } + + /* set the class obj permissions to the computed one. */ + if(calclass) { + int n_class_obj_index = -1; + + for(i=0;i. + */ + +/* + * This module supports JFS (POSIX) ACLs on VxFS (Veritas * Filesystem). + * These are available on HP-UX 11.00 if JFS 3.3 is installed. + * On HP-UX 11i (11.11 and above) these ACLs are supported out of + * the box. + * + * There is another form of ACLs on HFS. These ACLs have a + * completely different API and their own set of userland tools. + * Since HFS seems to be considered deprecated, HFS acls + * are not supported. (They could be supported through a separate + * vfs-module if there is demand.) + */ + +#ifndef __VFS_HPUXACL_H__ +#define __VFS_HPUXACL_H__ + +SMB_ACL_T hpuxacl_sys_acl_get_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + SMB_ACL_TYPE_T type); + +SMB_ACL_T hpuxacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp); + +int hpuxacl_sys_acl_set_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl); + +int hpuxacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_T theacl); + +int hpuxacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp); + +#endif + diff --git a/source3/modules/vfs_io_uring.c b/source3/modules/vfs_io_uring.c new file mode 100644 index 0000000..b33c09a --- /dev/null +++ b/source3/modules/vfs_io_uring.c @@ -0,0 +1,822 @@ +/* + * Use the io_uring of Linux (>= 5.1) + * + * Copyright (C) Volker Lendecke 2008 + * Copyright (C) Jeremy Allison 2010 + * Copyright (C) Stefan Metzmacher 2019 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "replace.h" + +/* + * liburing.h only needs a forward declaration + * of struct open_how. + * + * If struct open_how is defined in liburing/compat.h + * itself, hide it away in order to avoid conflicts + * with including linux/openat2.h or defining 'struct open_how' + * in libreplace. + */ +struct open_how; +#ifdef HAVE_STRUCT_OPEN_HOW_LIBURING_COMPAT_H +#define open_how __ignore_liburing_compat_h_open_how +#include +#undef open_how +#endif /* HAVE_STRUCT_OPEN_HOW_LIBURING_COMPAT_H */ + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/sys_rw.h" +#include "lib/util/iov_buf.h" +#include "smbprofile.h" +#include + +struct vfs_io_uring_request; + +struct vfs_io_uring_config { + struct io_uring uring; + struct tevent_fd *fde; + /* recursion guard. See comment above vfs_io_uring_queue_run() */ + bool busy; + /* recursion guard. See comment above vfs_io_uring_queue_run() */ + bool need_retry; + struct vfs_io_uring_request *queue; + struct vfs_io_uring_request *pending; +}; + +struct vfs_io_uring_request { + struct vfs_io_uring_request *prev, *next; + struct vfs_io_uring_request **list_head; + struct vfs_io_uring_config *config; + struct tevent_req *req; + void (*completion_fn)(struct vfs_io_uring_request *cur, + const char *location); + struct timespec start_time; + struct timespec end_time; + SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes); + struct io_uring_sqe sqe; + struct io_uring_cqe cqe; +}; + +static void vfs_io_uring_finish_req(struct vfs_io_uring_request *cur, + const struct io_uring_cqe *cqe, + struct timespec end_time, + const char *location) +{ + struct tevent_req *req = + talloc_get_type_abort(cur->req, + struct tevent_req); + void *state = _tevent_req_data(req); + + talloc_set_destructor(state, NULL); + if (cur->list_head != NULL) { + DLIST_REMOVE((*cur->list_head), cur); + cur->list_head = NULL; + } + cur->cqe = *cqe; + + SMBPROFILE_BYTES_ASYNC_SET_IDLE(cur->profile_bytes); + cur->end_time = end_time; + + /* + * We rely on being inside the _send() function + * or tevent_req_defer_callback() being called + * already. + */ + cur->completion_fn(cur, location); +} + +static void vfs_io_uring_config_destroy(struct vfs_io_uring_config *config, + int ret, + const char *location) +{ + struct vfs_io_uring_request *cur = NULL, *next = NULL; + struct timespec start_time; + struct timespec end_time; + struct io_uring_cqe err_cqe = { + .res = ret, + }; + + PROFILE_TIMESTAMP(&start_time); + + if (config->uring.ring_fd != -1) { + /* TODO: cancel queued and pending requests */ + TALLOC_FREE(config->fde); + io_uring_queue_exit(&config->uring); + config->uring.ring_fd = -1; + } + + PROFILE_TIMESTAMP(&end_time); + + for (cur = config->pending; cur != NULL; cur = next) { + next = cur->next; + err_cqe.user_data = (uintptr_t)(void *)cur; + vfs_io_uring_finish_req(cur, &err_cqe, end_time, location); + } + + for (cur = config->queue; cur != NULL; cur = next) { + next = cur->next; + err_cqe.user_data = (uintptr_t)(void *)cur; + cur->start_time = start_time; + vfs_io_uring_finish_req(cur, &err_cqe, end_time, location); + } +} + +static int vfs_io_uring_config_destructor(struct vfs_io_uring_config *config) +{ + vfs_io_uring_config_destroy(config, -EUCLEAN, __location__); + return 0; +} + +static int vfs_io_uring_request_state_deny_destructor(void *_state) +{ + struct __vfs_io_uring_generic_state { + struct vfs_io_uring_request ur; + } *state = (struct __vfs_io_uring_generic_state *)_state; + struct vfs_io_uring_request *cur = &state->ur; + + /* our parent is gone */ + cur->req = NULL; + + /* remove ourself from any list */ + DLIST_REMOVE((*cur->list_head), cur); + cur->list_head = NULL; + + /* + * Our state is about to go away, + * all we can do is shutting down the whole uring. + * But that's ok as we're most likely called from exit_server() + */ + vfs_io_uring_config_destroy(cur->config, -ESHUTDOWN, __location__); + return 0; +} + +static void vfs_io_uring_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data); + +static int vfs_io_uring_connect(vfs_handle_struct *handle, const char *service, + const char *user) +{ + int ret; + struct vfs_io_uring_config *config; + unsigned num_entries; + bool sqpoll; + unsigned flags = 0; + + config = talloc_zero(handle->conn, struct vfs_io_uring_config); + if (config == NULL) { + DEBUG(0, ("talloc_zero() failed\n")); + return -1; + } + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct vfs_io_uring_config, + return -1); + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + num_entries = lp_parm_ulong(SNUM(handle->conn), + "io_uring", + "num_entries", + 128); + num_entries = MAX(num_entries, 1); + + sqpoll = lp_parm_bool(SNUM(handle->conn), + "io_uring", + "sqpoll", + false); + if (sqpoll) { + flags |= IORING_SETUP_SQPOLL; + } + + ret = io_uring_queue_init(num_entries, &config->uring, flags); + if (ret < 0) { + SMB_VFS_NEXT_DISCONNECT(handle); + errno = -ret; + return -1; + } + + talloc_set_destructor(config, vfs_io_uring_config_destructor); + +#ifdef HAVE_IO_URING_RING_DONTFORK + ret = io_uring_ring_dontfork(&config->uring); + if (ret < 0) { + SMB_VFS_NEXT_DISCONNECT(handle); + errno = -ret; + return -1; + } +#endif /* HAVE_IO_URING_RING_DONTFORK */ + + config->fde = tevent_add_fd(handle->conn->sconn->ev_ctx, + config, + config->uring.ring_fd, + TEVENT_FD_READ, + vfs_io_uring_fd_handler, + handle); + if (config->fde == NULL) { + ret = errno; + SMB_VFS_NEXT_DISCONNECT(handle); + errno = ret; + return -1; + } + + return 0; +} + +static void _vfs_io_uring_queue_run(struct vfs_io_uring_config *config) +{ + struct vfs_io_uring_request *cur = NULL, *next = NULL; + struct io_uring_cqe *cqe = NULL; + unsigned cqhead; + unsigned nr = 0; + struct timespec start_time; + struct timespec end_time; + int ret; + + PROFILE_TIMESTAMP(&start_time); + + if (config->uring.ring_fd == -1) { + vfs_io_uring_config_destroy(config, -ESTALE, __location__); + return; + } + + for (cur = config->queue; cur != NULL; cur = next) { + struct io_uring_sqe *sqe = NULL; + void *state = _tevent_req_data(cur->req); + + next = cur->next; + + sqe = io_uring_get_sqe(&config->uring); + if (sqe == NULL) { + break; + } + + talloc_set_destructor(state, + vfs_io_uring_request_state_deny_destructor); + DLIST_REMOVE(config->queue, cur); + *sqe = cur->sqe; + DLIST_ADD_END(config->pending, cur); + cur->list_head = &config->pending; + SMBPROFILE_BYTES_ASYNC_SET_BUSY(cur->profile_bytes); + + cur->start_time = start_time; + } + + ret = io_uring_submit(&config->uring); + if (ret == -EAGAIN || ret == -EBUSY) { + /* We just retry later */ + } else if (ret < 0) { + vfs_io_uring_config_destroy(config, ret, __location__); + return; + } + + PROFILE_TIMESTAMP(&end_time); + + io_uring_for_each_cqe(&config->uring, cqhead, cqe) { + cur = (struct vfs_io_uring_request *)io_uring_cqe_get_data(cqe); + vfs_io_uring_finish_req(cur, cqe, end_time, __location__); + nr++; + } + + io_uring_cq_advance(&config->uring, nr); +} + +/* + * Wrapper function to prevent recursion which could happen + * if we called _vfs_io_uring_queue_run() directly without + * recursion checks. + * + * Looking at the pread call, we can have: + * + * vfs_io_uring_pread_send() + * ->vfs_io_uring_pread_submit() <----------------------------------- + * ->vfs_io_uring_request_submit() | + * ->vfs_io_uring_queue_run() | + * ->_vfs_io_uring_queue_run() | + * | + * But inside _vfs_io_uring_queue_run() looks like: | + * | + * _vfs_io_uring_queue_run() { | + * if (THIS_IO_COMPLETED) { | + * ->vfs_io_uring_finish_req() | + * ->cur->completion_fn() | + * } | + * } | + * | + * cur->completion_fn() for pread is set to vfs_io_uring_pread_completion() | + * | + * vfs_io_uring_pread_completion() { | + * if (READ_TERMINATED) { | + * -> tevent_req_done() - We're done, go back up the stack. | + * return; | + * } | + * | + * We have a short read - adjust the io vectors | + * | + * ->vfs_io_uring_pread_submit() --------------------------------------- + * } + * + * So before calling _vfs_io_uring_queue_run() we backet it with setting + * a flag config->busy, and unset it once _vfs_io_uring_queue_run() finally + * exits the retry loop. + * + * If we end up back into vfs_io_uring_queue_run() we notice we've done so + * as config->busy is set and don't recurse into _vfs_io_uring_queue_run(). + * + * We set the second flag config->need_retry that tells us to loop in the + * vfs_io_uring_queue_run() call above us in the stack and return. + * + * When the outer call to _vfs_io_uring_queue_run() returns we are in + * a loop checking if config->need_retry was set. That happens if + * the short read case occurs and _vfs_io_uring_queue_run() ended up + * recursing into vfs_io_uring_queue_run(). + * + * Once vfs_io_uring_pread_completion() finishes without a short + * read (the READ_TERMINATED case, tevent_req_done() is called) + * then config->need_retry is left as false, we exit the loop, + * set config->busy to false so the next top level call into + * vfs_io_uring_queue_run() won't think it's a recursed call + * and return. + * + */ + +static void vfs_io_uring_queue_run(struct vfs_io_uring_config *config) +{ + if (config->busy) { + /* + * We've recursed due to short read/write. + * Set need_retry to ensure we retry the + * io_uring_submit(). + */ + config->need_retry = true; + return; + } + + /* + * Bracket the loop calling _vfs_io_uring_queue_run() + * with busy = true / busy = false. + * so we can detect recursion above. + */ + + config->busy = true; + + do { + config->need_retry = false; + _vfs_io_uring_queue_run(config); + } while (config->need_retry); + + config->busy = false; +} + +static void vfs_io_uring_request_submit(struct vfs_io_uring_request *cur) +{ + struct vfs_io_uring_config *config = cur->config; + + io_uring_sqe_set_data(&cur->sqe, cur); + DLIST_ADD_END(config->queue, cur); + cur->list_head = &config->queue; + + vfs_io_uring_queue_run(config); +} + +static void vfs_io_uring_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + vfs_handle_struct *handle = (vfs_handle_struct *)private_data; + struct vfs_io_uring_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct vfs_io_uring_config, + smb_panic(__location__)); + + vfs_io_uring_queue_run(config); +} + +struct vfs_io_uring_pread_state { + struct files_struct *fsp; + off_t offset; + struct iovec iov; + size_t nread; + struct vfs_io_uring_request ur; +}; + +static void vfs_io_uring_pread_submit(struct vfs_io_uring_pread_state *state); +static void vfs_io_uring_pread_completion(struct vfs_io_uring_request *cur, + const char *location); + +static struct tevent_req *vfs_io_uring_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct vfs_io_uring_pread_state *state = NULL; + struct vfs_io_uring_config *config = NULL; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct vfs_io_uring_config, + smb_panic(__location__)); + + req = tevent_req_create(mem_ctx, &state, + struct vfs_io_uring_pread_state); + if (req == NULL) { + return NULL; + } + state->ur.config = config; + state->ur.req = req; + state->ur.completion_fn = vfs_io_uring_pread_completion; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p, + state->ur.profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes); + + ok = sys_valid_io_range(offset, n); + if (!ok) { + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + state->fsp = fsp; + state->offset = offset; + state->iov.iov_base = (void *)data; + state->iov.iov_len = n; + vfs_io_uring_pread_submit(state); + + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + tevent_req_defer_callback(req, ev); + return req; +} + +static void vfs_io_uring_pread_submit(struct vfs_io_uring_pread_state *state) +{ + io_uring_prep_readv(&state->ur.sqe, + fsp_get_io_fd(state->fsp), + &state->iov, 1, + state->offset); + vfs_io_uring_request_submit(&state->ur); +} + +static void vfs_io_uring_pread_completion(struct vfs_io_uring_request *cur, + const char *location) +{ + struct vfs_io_uring_pread_state *state = tevent_req_data( + cur->req, struct vfs_io_uring_pread_state); + struct iovec *iov = &state->iov; + int num_iov = 1; + bool ok; + + /* + * We rely on being inside the _send() function + * or tevent_req_defer_callback() being called + * already. + */ + + if (cur->cqe.res < 0) { + int err = -cur->cqe.res; + _tevent_req_error(cur->req, err, location); + return; + } + + if (cur->cqe.res == 0) { + /* + * We reached EOF, we're done + */ + tevent_req_done(cur->req); + return; + } + + ok = iov_advance(&iov, &num_iov, cur->cqe.res); + if (!ok) { + /* This is not expected! */ + DBG_ERR("iov_advance() failed cur->cqe.res=%d > iov_len=%d\n", + (int)cur->cqe.res, + (int)state->iov.iov_len); + tevent_req_error(cur->req, EIO); + return; + } + + /* sys_valid_io_range() already checked the boundaries */ + state->nread += state->ur.cqe.res; + if (num_iov == 0) { + /* We're done */ + tevent_req_done(cur->req); + return; + } + + /* + * sys_valid_io_range() already checked the boundaries + * now try to get the rest. + */ + state->offset += state->ur.cqe.res; + vfs_io_uring_pread_submit(state); +} + +static ssize_t vfs_io_uring_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_io_uring_pread_state *state = tevent_req_data( + req, struct vfs_io_uring_pread_state); + ssize_t ret; + + SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes); + vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time, + &state->ur.start_time); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + vfs_aio_state->error = 0; + ret = state->nread; + + tevent_req_received(req); + return ret; +} + +struct vfs_io_uring_pwrite_state { + struct files_struct *fsp; + off_t offset; + struct iovec iov; + size_t nwritten; + struct vfs_io_uring_request ur; +}; + +static void vfs_io_uring_pwrite_submit(struct vfs_io_uring_pwrite_state *state); +static void vfs_io_uring_pwrite_completion(struct vfs_io_uring_request *cur, + const char *location); + +static struct tevent_req *vfs_io_uring_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct vfs_io_uring_pwrite_state *state = NULL; + struct vfs_io_uring_config *config = NULL; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct vfs_io_uring_config, + smb_panic(__location__)); + + req = tevent_req_create(mem_ctx, &state, + struct vfs_io_uring_pwrite_state); + if (req == NULL) { + return NULL; + } + state->ur.config = config; + state->ur.req = req; + state->ur.completion_fn = vfs_io_uring_pwrite_completion; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p, + state->ur.profile_bytes, n); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes); + + ok = sys_valid_io_range(offset, n); + if (!ok) { + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + state->fsp = fsp; + state->offset = offset; + state->iov.iov_base = discard_const(data); + state->iov.iov_len = n; + vfs_io_uring_pwrite_submit(state); + + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + tevent_req_defer_callback(req, ev); + return req; +} + +static void vfs_io_uring_pwrite_submit(struct vfs_io_uring_pwrite_state *state) +{ + io_uring_prep_writev(&state->ur.sqe, + fsp_get_io_fd(state->fsp), + &state->iov, 1, + state->offset); + vfs_io_uring_request_submit(&state->ur); +} + +static void vfs_io_uring_pwrite_completion(struct vfs_io_uring_request *cur, + const char *location) +{ + struct vfs_io_uring_pwrite_state *state = tevent_req_data( + cur->req, struct vfs_io_uring_pwrite_state); + struct iovec *iov = &state->iov; + int num_iov = 1; + bool ok; + + /* + * We rely on being inside the _send() function + * or tevent_req_defer_callback() being called + * already. + */ + + if (cur->cqe.res < 0) { + int err = -cur->cqe.res; + _tevent_req_error(cur->req, err, location); + return; + } + + if (cur->cqe.res == 0) { + /* + * Ensure we can never spin. + */ + tevent_req_error(cur->req, ENOSPC); + return; + } + + ok = iov_advance(&iov, &num_iov, cur->cqe.res); + if (!ok) { + /* This is not expected! */ + DBG_ERR("iov_advance() failed cur->cqe.res=%d > iov_len=%d\n", + (int)cur->cqe.res, + (int)state->iov.iov_len); + tevent_req_error(cur->req, EIO); + return; + } + + /* sys_valid_io_range() already checked the boundaries */ + state->nwritten += state->ur.cqe.res; + if (num_iov == 0) { + /* We're done */ + tevent_req_done(cur->req); + return; + } + + /* + * sys_valid_io_range() already checked the boundaries + * now try to write the rest. + */ + state->offset += state->ur.cqe.res; + vfs_io_uring_pwrite_submit(state); +} + +static ssize_t vfs_io_uring_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_io_uring_pwrite_state *state = tevent_req_data( + req, struct vfs_io_uring_pwrite_state); + ssize_t ret; + + SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes); + vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time, + &state->ur.start_time); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + vfs_aio_state->error = 0; + ret = state->nwritten; + + tevent_req_received(req); + return ret; +} + +struct vfs_io_uring_fsync_state { + struct vfs_io_uring_request ur; +}; + +static void vfs_io_uring_fsync_completion(struct vfs_io_uring_request *cur, + const char *location); + +static struct tevent_req *vfs_io_uring_fsync_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req = NULL; + struct vfs_io_uring_fsync_state *state = NULL; + struct vfs_io_uring_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct vfs_io_uring_config, + smb_panic(__location__)); + + req = tevent_req_create(mem_ctx, &state, + struct vfs_io_uring_fsync_state); + if (req == NULL) { + return NULL; + } + state->ur.config = config; + state->ur.req = req; + state->ur.completion_fn = vfs_io_uring_fsync_completion; + + SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p, + state->ur.profile_bytes, 0); + SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes); + + io_uring_prep_fsync(&state->ur.sqe, + fsp_get_io_fd(fsp), + 0); /* fsync_flags */ + vfs_io_uring_request_submit(&state->ur); + + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + tevent_req_defer_callback(req, ev); + return req; +} + +static void vfs_io_uring_fsync_completion(struct vfs_io_uring_request *cur, + const char *location) +{ + /* + * We rely on being inside the _send() function + * or tevent_req_defer_callback() being called + * already. + */ + + if (cur->cqe.res < 0) { + int err = -cur->cqe.res; + _tevent_req_error(cur->req, err, location); + return; + } + + if (cur->cqe.res > 0) { + /* This is not expected! */ + DBG_ERR("got cur->cqe.res=%d\n", (int)cur->cqe.res); + tevent_req_error(cur->req, EIO); + return; + } + + tevent_req_done(cur->req); +} + +static int vfs_io_uring_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct vfs_io_uring_fsync_state *state = tevent_req_data( + req, struct vfs_io_uring_fsync_state); + + SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes); + vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time, + &state->ur.start_time); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + vfs_aio_state->error = 0; + + tevent_req_received(req); + return 0; +} + +static struct vfs_fn_pointers vfs_io_uring_fns = { + .connect_fn = vfs_io_uring_connect, + .pread_send_fn = vfs_io_uring_pread_send, + .pread_recv_fn = vfs_io_uring_pread_recv, + .pwrite_send_fn = vfs_io_uring_pwrite_send, + .pwrite_recv_fn = vfs_io_uring_pwrite_recv, + .fsync_send_fn = vfs_io_uring_fsync_send, + .fsync_recv_fn = vfs_io_uring_fsync_recv, +}; + +static_decl_vfs; +NTSTATUS vfs_io_uring_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "io_uring", &vfs_io_uring_fns); +} diff --git a/source3/modules/vfs_linux_xfs_sgid.c b/source3/modules/vfs_linux_xfs_sgid.c new file mode 100644 index 0000000..a08e2d4 --- /dev/null +++ b/source3/modules/vfs_linux_xfs_sgid.c @@ -0,0 +1,128 @@ +/* + * Module to work around a bug in Linux XFS: + * http://oss.sgi.com/bugzilla/show_bug.cgi?id=280 + * + * Copyright (c) Volker Lendecke 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/filesys.h" +#include "smbd/smbd.h" + +static int linux_xfs_sgid_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *dname = NULL; + struct smb_filename *fname = NULL; + int mkdir_res; + int res; + NTSTATUS status; + + DEBUG(10, ("Calling linux_xfs_sgid_mkdirat(%s)\n", + smb_fname->base_name)); + + mkdir_res = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + if (mkdir_res == -1) { + DEBUG(10, ("SMB_VFS_NEXT_MKDIRAT returned error: %s\n", + strerror(errno))); + return mkdir_res; + } + + fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (fname == NULL) { + return -1; + } + + status = SMB_VFS_PARENT_PATHNAME(handle->conn, + talloc_tos(), + fname, + &dname, + NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("SMB_VFS_PARENT_PATHNAME() failed with %s\n", + nt_errstr(status)); + /* return success, we did the mkdir */ + return mkdir_res; + } + + res = SMB_VFS_NEXT_STAT(handle, dname); + if (res == -1) { + DBG_DEBUG("NEXT_STAT(%s) failed: %s\n", + smb_fname_str_dbg(dname), + strerror(errno)); + /* return success, we did the mkdir */ + return mkdir_res; + } + if ((dname->st.st_ex_mode & S_ISGID) == 0) { + /* No SGID to inherit */ + DEBUG(10, ("No SGID to inherit\n")); + TALLOC_FREE(dname); + return mkdir_res; + } + TALLOC_FREE(dname); + + res = SMB_VFS_NEXT_STAT(handle, fname); + if (res == -1) { + DBG_NOTICE("Could not stat just created dir %s: %s\n", + smb_fname_str_dbg(fname), + strerror(errno)); + /* return success, we did the mkdir */ + TALLOC_FREE(fname); + return mkdir_res; + } + fname->st.st_ex_mode |= S_ISGID; + fname->st.st_ex_mode &= ~S_IFDIR; + + /* + * Yes, we have to do this as root. If you do it as + * non-privileged user, XFS on Linux will just ignore us and + * return success. What can you do... + */ + become_root(); + res = SMB_VFS_NEXT_FCHMOD(handle, smb_fname->fsp, fname->st.st_ex_mode); + unbecome_root(); + + if (res == -1) { + DBG_NOTICE("CHMOD(%s, %o) failed: %s\n", + smb_fname_str_dbg(fname), + (int)fname->st.st_ex_mode, + strerror(errno)); + /* return success, we did the mkdir */ + TALLOC_FREE(fname); + return mkdir_res; + } + + TALLOC_FREE(fname); + return mkdir_res; +} + +static struct vfs_fn_pointers linux_xfs_sgid_fns = { + .mkdirat_fn = linux_xfs_sgid_mkdirat, +}; + +static_decl_vfs; +NTSTATUS vfs_linux_xfs_sgid_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "linux_xfs_sgid", &linux_xfs_sgid_fns); +} diff --git a/source3/modules/vfs_media_harmony.c b/source3/modules/vfs_media_harmony.c new file mode 100644 index 0000000..a027254 --- /dev/null +++ b/source3/modules/vfs_media_harmony.c @@ -0,0 +1,1873 @@ +/* + * $Id: media_harmony.c,v 1.1 2007/11/06 10:07:22 stuart_hc Exp $ + * + * Samba VFS module supporting multiple AVID clients sharing media. + * + * Copyright (C) 2005 Philip de Nier + * Copyright (C) 2012 Andrew Klaassen + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + + +/* + * Media Harmony is a Samba VFS module that allows multiple AVID + * clients to share media. Each client sees their own copy of the + * AVID msmMMOB.mdb and msmFMID.pmr files and Creating directories. + * + * Add this module to the vfs objects option in your Samba share + * configuration. + * eg. + * + * [avid_win] + * path = /video + * vfs objects = media_harmony + * ... + * + * It is recommended that you separate out Samba shares for Mac + * and Windows clients, and add the following options to the shares + * for Windows clients (NOTE: replace @ with *): + * + * veto files = /.DS_Store/._@/.Trash@/.Spotlight@/.hidden/.hotfiles@/.vol/ + * delete veto files = yes + * + * This prevents hidden files from Mac clients interfering with Windows + * clients. If you find any more problem hidden files then add them to + * the list. + * + * + * Andrew Klaassen, 2012-03-14 + * To prevent Avid clients from interrupting each other (via Avid's habit + * of launching a database refresh whenever it notices an mtime update + * on media directories, i.e. whenever one editor adds new material to a + * shared share), I've added code that causes stat information for anything + * directly under "Avid MediaFile/MXF" to be taken from + * dirname_clientaddr_clientuser if it exists. These files ~aren't~ + * hidden, unlike the client-suffixed database files. + * + * For example, stat information for + * Avid MediaFiles/MXF/1 + * will come from + * Avid MediaFiles/MXF/1_192.168.1.10_dave + * for dave working on 192.168.1.10, but will come from + * Avid MediaFile/MXF/1_192.168.1.11_susan + * for susan working on 192.168.1.11. If those alternate + * directories don't exist, the user will get the actual directory's stat + * info. When an editor wants to force a database refresh, they update + * the mtime on "their" file. This will cause Avid + * on that client to see an updated mtime for "Avid MediaFiles/MXF/1", + * which will trigger an Avid database refresh just for that editor. + * + * + * Notes: + * - This module is designed to work with AVID editing applications that + * look in the Avid MediaFiles or OMFI MediaFiles directory for media. + * It is not designed to work as expected in all circumstances for + * general use. For example: it is possibly to open client specific + * files such as msmMMOB.mdb_192.168.1.10_userx even though is doesn't + * show up in a directory listing. + * + */ + + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "../smbd/globals.h" +#include "auth.h" +#include "../lib/tsocket/tsocket.h" + +#define MH_INFO_DEBUG 10 +#define MH_ERR_DEBUG 0 + +static const char* MDB_FILENAME = "msmMMOB.mdb"; +static const size_t MDB_FILENAME_LEN = 11; +static const char* PMR_FILENAME = "msmFMID.pmr"; +static const size_t PMR_FILENAME_LEN = 11; +static const char* CREATING_DIRNAME = "Creating"; +static const size_t CREATING_DIRNAME_LEN = 8; +static const char* AVID_MEDIAFILES_DIRNAME = "Avid MediaFiles"; +static const size_t AVID_MEDIAFILES_DIRNAME_LEN = 15; +static const char* OMFI_MEDIAFILES_DIRNAME = "OMFI MediaFiles"; +static const size_t OMFI_MEDIAFILES_DIRNAME_LEN = 15; +static const char* APPLE_DOUBLE_PREFIX = "._"; +static const size_t APPLE_DOUBLE_PREFIX_LEN = 2; +static const char* AVID_MXF_DIRNAME = "Avid MediaFiles/MXF"; +static const size_t AVID_MXF_DIRNAME_LEN = 19; + +static int vfs_mh_debug_level = DBGC_VFS; + +/* supplements the directory list stream */ +typedef struct mh_dirinfo_struct +{ + DIR* dirstream; + char *dirpath; + char *clientPath; + bool isInMediaFiles; + char *clientMDBFilename; + char *clientPMRFilename; + char *clientCreatingDirname; +} mh_dirinfo_struct; + + +/* Add "__" suffix to path or filename. + * + * Success: return 0 + * Failure: set errno, path NULL, return -1 + */ +static int alloc_append_client_suffix(vfs_handle_struct *handle, + char **path) +{ + int status = 0; + char *raddr = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering with *path '%s'\n", *path)); + + raddr = tsocket_address_inet_addr_string( + handle->conn->sconn->remote_address, talloc_tos()); + if (raddr == NULL) + { + errno = ENOMEM; + status = -1; + goto err; + } + + /* talloc_asprintf_append uses talloc_realloc, which + * frees original 'path' memory so we don't have to. + */ + *path = talloc_asprintf_append(*path, "_%s_%s", + raddr, + handle->conn->session_info->unix_info->sanitized_username); + if (*path == NULL) + { + DEBUG(MH_ERR_DEBUG, ("alloc_append_client_suffix " + "out of memory\n")); + errno = ENOMEM; + status = -1; + goto err; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path)); +err: + TALLOC_FREE(raddr); + return status; +} + + +/* Returns True if the file or directory begins with the appledouble + * prefix. + */ +static bool is_apple_double(const char* fname) +{ + bool ret = False; + + DEBUG(MH_INFO_DEBUG, ("Entering with fname '%s'\n", fname)); + + if (strncmp(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN) + == 0) + { + ret = True; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n", + ret == True ? "True" : "False")); + return ret; +} + +static bool starts_with_media_dir(const char* media_dirname, + size_t media_dirname_len, const char* path) +{ + bool ret = False; + const char *path_start; + + DEBUG(MH_INFO_DEBUG, ("Entering with media_dirname '%s' " + "path '%s'\n", media_dirname, path)); + + /* Sometimes Samba gives us "./OMFI MediaFiles". */ + if (strncmp(path, "./", 2) == 0) + { + path_start = &path[2]; + } + else { + path_start = path; + } + + if (strncmp(media_dirname, path_start, media_dirname_len) == 0 + && + ( + path_start[media_dirname_len] == '\0' + || + path_start[media_dirname_len] == '/' + ) + ) + { + ret = True; + } + + DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n", + ret == True ? "True" : "False")); + return ret; +} + +/* + * Returns True if the file or directory referenced by the path is below + * the AVID_MEDIAFILES_DIRNAME or OMFI_MEDIAFILES_DIRNAME directory + * The AVID_MEDIAFILES_DIRNAME and OMFI_MEDIAFILES_DIRNAME are assumed to + * be in the root directory, which is generally a safe assumption + * in the fixed-path world of Avid. + */ +static bool is_in_media_files(const char* path) +{ + bool ret = False; + + DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path)); + + if ( + starts_with_media_dir(AVID_MEDIAFILES_DIRNAME, + AVID_MEDIAFILES_DIRNAME_LEN, path) + || + starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME, + OMFI_MEDIAFILES_DIRNAME_LEN, path) + ) + { + ret = True; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n", + ret == True ? "True" : "False")); + return ret; +} + +/* + * Returns depth of path under media directory. Deals with the + * occasional ..../. and ..../.. paths that get passed to stat. + * + * Assumes is_in_media_files has already been called and has returned + * true for the path; if it hasn't, this function will likely crash + * and burn. + * + * Not foolproof; something like "Avid MediaFiles/MXF/../foo/1" + * would fool it. Haven't seen paths like that getting to the + * stat function yet, so ignoring that possibility for now. + */ +static int depth_from_media_dir(const char* media_dirname, + size_t media_dirname_len, const char* path) +{ + int transition_count = 0; + const char *path_start; + const char *pathPtr; + + DEBUG(MH_INFO_DEBUG, ("Entering with media_dirname '%s' " + "path '%s'\n", media_dirname, path)); + + /* Sometimes Samba gives us "./OMFI MediaFiles". */ + if (strncmp(path, "./", 2) == 0) + { + path_start = &path[2]; + } + else { + path_start = path; + } + + if (path_start[media_dirname_len] == '\0') + { + goto out; + } + + pathPtr = &path_start[media_dirname_len + 1]; + + while(1) + { + if (*pathPtr == '\0' || *pathPtr == '/') + { + if ( + *(pathPtr - 1) == '.' + && + *(pathPtr - 2) == '.' + && + *(pathPtr - 3) == '/' + ) + { + transition_count--; + } + else if ( + ! + ( + *(pathPtr - 1) == '/' + || + ( + *(pathPtr - 1) == '.' + && + *(pathPtr - 2) == '/' + ) + ) + ) + { + transition_count++; + } + } + if (*pathPtr == '\0') + { + break; + } + pathPtr++; + } + + DEBUG(MH_INFO_DEBUG, ("Leaving with transition_count '%i'\n", + transition_count)); +out: + return transition_count; +} + +/* Identifies MDB and PMR files at end of path. */ +static bool is_avid_database( + char *path, + size_t path_len, + const char *avid_db_filename, + const size_t avid_db_filename_len) +{ + bool ret = False; + + DEBUG(MH_INFO_DEBUG, ("Entering with path '%s', " + "avid_db_filename '%s', " + "path_len '%i', " + "avid_db_filename_len '%i'\n", + path, avid_db_filename, + (int)path_len, (int)avid_db_filename_len)); + + if ( + path_len > avid_db_filename_len + && + strcmp(&path[path_len - avid_db_filename_len], + avid_db_filename) == 0 + && + ( + path[path_len - avid_db_filename_len - 1] == '/' + || + (path_len > avid_db_filename_len + + APPLE_DOUBLE_PREFIX_LEN + && + path[path_len - avid_db_filename_len + - APPLE_DOUBLE_PREFIX_LEN - 1] == '/' + && + is_apple_double(&path[path_len + - avid_db_filename_len + - APPLE_DOUBLE_PREFIX_LEN])) + ) + ) + { + ret = True; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n", + ret == True ? "True" : "False")); + return ret; +} + + +/* Add client suffix to paths to MDB_FILENAME, PMR_FILENAME and + * CREATING_SUBDIRNAME. + * + * Caller must free newPath. + * + * Success: return 0 + * Failure: set errno, newPath NULL, return -1 + */ +static int alloc_get_client_path(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const char *path, + char **newPath) +{ + /* replace /CREATING_DIRNAME/ or /._CREATING_DIRNAME/ + * directory in path - potentially in middle of path + * - with suffixed name. + */ + int status = 0; + char* pathPtr; + size_t intermPathLen; + + DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path)); + + *newPath = talloc_strdup(ctx, path); + if (*newPath == NULL) + { + DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path ENOMEM #1\n")); + errno = ENOMEM; + status = -1; + goto out; + } + DEBUG(MH_INFO_DEBUG, ("newPath #1 %s\n", *newPath)); + if ( + (pathPtr = strstr(path, CREATING_DIRNAME)) != NULL + && + ( + *(pathPtr + CREATING_DIRNAME_LEN) == '\0' + || + *(pathPtr + CREATING_DIRNAME_LEN) == '/' + ) + && + ( + (pathPtr - path > 0 + && + *(pathPtr - 1) == '/') + || + (pathPtr - path > APPLE_DOUBLE_PREFIX_LEN + && + *(pathPtr - APPLE_DOUBLE_PREFIX_LEN - 1) == '/' + && + is_apple_double(pathPtr - APPLE_DOUBLE_PREFIX_LEN)) + ) + ) + { + /* Insert client suffix into path. */ + (*newPath)[pathPtr - path + CREATING_DIRNAME_LEN] = '\0'; + DEBUG(MH_INFO_DEBUG, ("newPath #2 %s\n", *newPath)); + + if ((status = alloc_append_client_suffix(handle, newPath))) + { + goto out; + } + + DEBUG(MH_INFO_DEBUG, ("newPath #3 %s\n", *newPath)); + *newPath = talloc_strdup_append(*newPath, + pathPtr + CREATING_DIRNAME_LEN); + if (*newPath == NULL) + { + DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path " + "ENOMEM #2\n")); + errno = ENOMEM; + status = -1; + goto out; + } + DEBUG(MH_INFO_DEBUG, ("newPath #4 %s\n", *newPath)); + } + + /* replace /MDB_FILENAME or /PMR_FILENAME or /._MDB_FILENAME + * or /._PMR_FILENAME at newPath end with suffixed name. + */ + intermPathLen = strlen(*newPath); + if ( + is_avid_database(*newPath, intermPathLen, + MDB_FILENAME, MDB_FILENAME_LEN) + || + is_avid_database(*newPath, intermPathLen, + PMR_FILENAME, PMR_FILENAME_LEN) + ) + { + DEBUG(MH_INFO_DEBUG, ("newPath #5 %s\n", *newPath)); + if ((status = alloc_append_client_suffix(handle, newPath))) + { + goto out; + } + DEBUG(MH_INFO_DEBUG, ("newPath #6 %s\n", *newPath)); + } +out: + /* newPath must be freed in caller. */ + DEBUG(MH_INFO_DEBUG, ("Leaving with *newPath '%s'\n", *newPath)); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int alloc_get_client_smb_fname(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname, + struct smb_filename **clientFname) +{ + int status = 0; + + DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + *clientFname = cp_smb_filename(ctx, smb_fname); + if ((*clientFname) == NULL) { + DEBUG(MH_ERR_DEBUG, ("alloc_get_client_smb_fname " + "NTERR\n")); + errno = ENOMEM; + status = -1; + goto err; + } + if ((status = alloc_get_client_path(handle, ctx, + smb_fname->base_name, + &(*clientFname)->base_name))) + { + goto err; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name " + "'%s'\n", (*clientFname)->base_name)); +err: + return status; +} + + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int alloc_set_client_dirinfo_path(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + char **path, + const char *avid_db_filename) +{ + int status = 0; + + DEBUG(MH_INFO_DEBUG, ("Entering with avid_db_filename '%s'\n", + avid_db_filename)); + + if ((*path = talloc_strdup(ctx, avid_db_filename)) == NULL) + { + DEBUG(MH_ERR_DEBUG, ("alloc_set_client_dirinfo_path " + "ENOMEM\n")); + errno = ENOMEM; + status = -1; + goto err; + } + if ((status = alloc_append_client_suffix(handle, path))) + { + goto err; + } + DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path)); +err: + return status; +} + +/* + * Replace mtime on clientFname with mtime from client-suffixed + * equivalent, if it exists. + * + * Success: return 0 + * Failure: set errno, return -1 + */ +static int set_fake_mtime(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + struct smb_filename **clientFname, + int (*statFn)(const char *, SMB_STRUCT_STAT *, bool)) +{ + int status = 0; + char *statPath; + SMB_STRUCT_STAT fakeStat; + int copy_len; + + DEBUG(MH_INFO_DEBUG, ("Entering with (*clientFname)->base_name " + "'%s', (*clientFname)->st.st_ex_mtime %s", + (*clientFname)->base_name, + ctime(&((*clientFname)->st.st_ex_mtime.tv_sec)))); + + if ( + depth_from_media_dir(AVID_MXF_DIRNAME, + AVID_MXF_DIRNAME_LEN, + (*clientFname)->base_name) + != 1 + && + depth_from_media_dir(OMFI_MEDIAFILES_DIRNAME, + OMFI_MEDIAFILES_DIRNAME_LEN, + (*clientFname)->base_name) + != 0 + ) + { + goto out; + } + + copy_len = strlen((*clientFname)->base_name); + + /* Hack to deal with occasional "Avid MediaFiles/MXF/1/." paths. + * We know we're under a media dir, so paths are at least 2 chars + * long. + */ + if ((*clientFname)->base_name[copy_len - 1] == '.' && + (*clientFname)->base_name[copy_len - 2] == '/') + { + copy_len -= 2; + } + + if (((statPath = talloc_strndup(ctx, + (*clientFname)->base_name, copy_len)) == NULL)) + { + errno = ENOMEM; + status = -1; + goto err; + } + if ((status = alloc_append_client_suffix(handle, &statPath))) + { + goto err; + } + + DEBUG(MH_INFO_DEBUG, ("Fake stat'ing '%s'\n", statPath)); + if (statFn(statPath, &fakeStat, + lp_fake_directory_create_times(SNUM(handle->conn)))) + { + /* This can fail for legitimate reasons - i.e. the + * fakeStat directory doesn't exist, which is okay + * - so we don't set status. But if it does fail, + * we need to skip over the mtime assignment. + */ + goto err; + } + + DEBUG(MH_INFO_DEBUG, ("Setting fake mtime from '%s'\n", statPath)); + (*clientFname)->st.st_ex_mtime = fakeStat.st_ex_mtime; +err: + TALLOC_FREE(statPath); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name " + "'%s', (*clientFname)->st.st_ex_mtime %s", + (*clientFname)->base_name, + ctime(&((*clientFname)->st.st_ex_mtime.tv_sec)))); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + int status; + struct smb_filename *clientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) + { + status = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf); + goto out; + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &clientFname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_STATVFS(handle, clientFname, statbuf); +err: + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with path '%s'\n", + smb_fname->base_name)); + return status; +} + +static int alloc_set_client_dirinfo(vfs_handle_struct *handle, + const char *fname, + struct mh_dirinfo_struct **dirInfo) +{ + int status = 0; + char *clientPath; + TALLOC_CTX *ctx; + + DEBUG(MH_INFO_DEBUG, ("Entering with fname '%s'\n", fname)); + + *dirInfo = talloc(NULL, struct mh_dirinfo_struct); + if (*dirInfo == NULL) + { + goto err; + } + + (*dirInfo)->dirpath = talloc_strdup(*dirInfo, fname); + if ((*dirInfo)->dirpath == NULL) + { + goto err; + } + + if (!is_in_media_files(fname)) + { + (*dirInfo)->clientPath = NULL; + (*dirInfo)->clientMDBFilename = NULL; + (*dirInfo)->clientPMRFilename = NULL; + (*dirInfo)->clientCreatingDirname = NULL; + (*dirInfo)->isInMediaFiles = False; + goto out; + } + + (*dirInfo)->isInMediaFiles = True; + + if (alloc_set_client_dirinfo_path(handle, + *dirInfo, + &((*dirInfo)->clientMDBFilename), + MDB_FILENAME)) + { + goto err; + } + + if (alloc_set_client_dirinfo_path(handle, + *dirInfo, + &((*dirInfo)->clientPMRFilename), + PMR_FILENAME)) + { + goto err; + } + + if (alloc_set_client_dirinfo_path(handle, + *dirInfo, + &((*dirInfo)->clientCreatingDirname), + CREATING_DIRNAME)) + { + goto err; + } + + clientPath = NULL; + ctx = talloc_tos(); + + if (alloc_get_client_path(handle, ctx, + fname, + &clientPath)) + { + goto err; + } + + (*dirInfo)->clientPath = talloc_strdup(*dirInfo, clientPath); + if ((*dirInfo)->clientPath == NULL) + { + goto err; + } + + TALLOC_FREE(clientPath); + +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with (*dirInfo)->dirpath '%s', " + "(*dirInfo)->clientPath '%s'\n", + (*dirInfo)->dirpath, + (*dirInfo)->clientPath)); + return status; + +err: + DEBUG(MH_ERR_DEBUG, ("Failing with fname '%s'\n", fname)); + TALLOC_FREE(*dirInfo); + status = -1; + errno = ENOMEM; + return status; +} + +static DIR *mh_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, + const char *mask, + uint32_t attr) +{ + struct mh_dirinfo_struct *dirInfo = NULL; + DIR *dirstream; + + DEBUG(MH_INFO_DEBUG, ("Entering with fsp->fsp_name->base_name '%s'\n", + fsp->fsp_name->base_name)); + + dirstream = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr); + if (!dirstream) + { + goto err; + } + + if (alloc_set_client_dirinfo(handle, fsp->fsp_name->base_name, + &dirInfo)) + { + goto err; + } + + dirInfo->dirstream = dirstream; + + if (! dirInfo->isInMediaFiles) { + goto out; + } + + if (set_fake_mtime(handle, fsp, &(fsp->fsp_name), sys_stat)) + { + goto err; + } + +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with dirInfo->dirpath '%s', " + "dirInfo->clientPath '%s', " + "fsp->fsp_name->st.st_ex_mtime %s", + dirInfo->dirpath, + dirInfo->clientPath, + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)))); + /* Success is freed in closedir. */ + return (DIR *) dirInfo; +err: + /* Failure is freed here. */ + DEBUG(MH_ERR_DEBUG, ("Failing with fsp->fsp_name->base_name '%s'\n", + fsp->fsp_name->base_name)); + TALLOC_FREE(dirInfo); + return NULL; +} + +/* + * skip MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME + * directory, skip other client's suffixed MDB_FILENAME and PMR_FILENAME + * filenames and CREATING_DIRNAME directory, replace this client's + * suffixed MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME + * directory with non suffixed. + * + * Success: return dirent + * End of data: return NULL + * Failure: set errno, return NULL + */ +static struct dirent * +mh_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp) +{ + mh_dirinfo_struct* dirInfo = (mh_dirinfo_struct*)dirp; + struct dirent *d = NULL; + int skip; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_readdir\n")); + + DEBUG(MH_INFO_DEBUG, ("dirInfo->dirpath '%s', " + "dirInfo->clientPath '%s', " + "dirInfo->isInMediaFiles '%s', " + "dirInfo->clientMDBFilename '%s', " + "dirInfo->clientPMRFilename '%s', " + "dirInfo->clientCreatingDirname '%s'\n", + dirInfo->dirpath, + dirInfo->clientPath, + dirInfo->isInMediaFiles ? "True" : "False", + dirInfo->clientMDBFilename, + dirInfo->clientPMRFilename, + dirInfo->clientCreatingDirname)); + + if (! dirInfo->isInMediaFiles) + { + d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream); + goto out; + } + + do + { + const char* dname; + bool isAppleDouble; + + skip = False; + d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream); + + if (d == NULL) + { + break; + } + + /* ignore apple double prefix for logic below */ + if (is_apple_double(d->d_name)) + { + dname = &d->d_name[APPLE_DOUBLE_PREFIX_LEN]; + isAppleDouble = True; + } + else + { + dname = d->d_name; + isAppleDouble = False; + } + + /* skip Avid-special files with no client suffix */ + if ( + strcmp(dname, MDB_FILENAME) == 0 + || + strcmp(dname, PMR_FILENAME) == 0 + || + strcmp(dname, CREATING_DIRNAME) == 0 + ) + { + skip = True; + } + /* chop client suffix off this client's suffixed files */ + else if (strcmp(dname, dirInfo->clientMDBFilename) == 0) + { + if (isAppleDouble) + { + d->d_name[MDB_FILENAME_LEN + + APPLE_DOUBLE_PREFIX_LEN] = '\0'; + } + else + { + d->d_name[MDB_FILENAME_LEN] = '\0'; + } + } + else if (strcmp(dname, dirInfo->clientPMRFilename) == 0) + { + if (isAppleDouble) + { + d->d_name[PMR_FILENAME_LEN + + APPLE_DOUBLE_PREFIX_LEN] = '\0'; + } + else + { + d->d_name[PMR_FILENAME_LEN] = '\0'; + } + } + else if (strcmp(dname, dirInfo->clientCreatingDirname) + == 0) + { + if (isAppleDouble) + { + d->d_name[CREATING_DIRNAME_LEN + + APPLE_DOUBLE_PREFIX_LEN] = '\0'; + } + else + { + d->d_name[CREATING_DIRNAME_LEN] = '\0'; + } + } + /* + * Anything that starts as an Avid-special file + * that's made it this far should be skipped. This + * is different from the original behaviour, which + * only skipped other client's suffixed files. + */ + else if ( + strncmp(MDB_FILENAME, dname, + MDB_FILENAME_LEN) == 0 + || + strncmp(PMR_FILENAME, dname, + PMR_FILENAME_LEN) == 0 + || + strncmp(CREATING_DIRNAME, dname, + CREATING_DIRNAME_LEN) == 0 + ) + { + skip = True; + } + } + while (skip); + +out: + DEBUG(MH_INFO_DEBUG, ("Leaving mh_readdir\n")); + return d; +} + +/* + * Success: no success result defined. + * Failure: no failure result defined. + */ +static void mh_rewinddir(vfs_handle_struct *handle, + DIR *dirp) +{ + DEBUG(MH_INFO_DEBUG, ("Entering and leaving mh_rewinddir\n")); + SMB_VFS_NEXT_REWINDDIR(handle, + ((mh_dirinfo_struct*)dirp)->dirstream); +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + int status; + struct smb_filename *clientFname = NULL; + const char *path = smb_fname->base_name; + struct smb_filename *full_fname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path)); + + if (!is_in_media_files(path)) { + status = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + goto out; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + full_fname, + &clientFname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + clientFname, + mode); +err: + TALLOC_FREE(full_fname); + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with path '%s'\n", path)); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_closedir(vfs_handle_struct *handle, + DIR *dirp) +{ + DIR *realdirp = ((mh_dirinfo_struct*)dirp)->dirstream; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_closedir\n")); + // Will this talloc_free destroy realdirp? + TALLOC_FREE(dirp); + + DEBUG(MH_INFO_DEBUG, ("Leaving mh_closedir\n")); + return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp); +} + +/* + * Success: return non-negative file descriptor + * Failure: set errno, return -1 + */ +static int mh_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int ret; + struct smb_filename *clientFname; + TALLOC_CTX *ctx; + + DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) { + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + goto out; + } + + clientFname = NULL; + ctx = talloc_tos(); + + if (alloc_get_client_smb_fname(handle, ctx, smb_fname, &clientFname)) { + ret = -1; + goto err; + } + + /* + * What about fsp->fsp_name? We also have to get correct stat info into + * fsp and smb_fname for DB files, don't we? + */ + + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s' " + "smb_fname->st.st_ex_mtime %s" + " fsp->fsp_name->st.st_ex_mtime %s", + smb_fname->base_name, + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)), + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)))); + + ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, clientFname, fsp, how); +err: + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + return ret; +} + +/* + * Success: return non-negative file descriptor + * Failure: set errno, return -1 + */ +static NTSTATUS mh_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result_fsp, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS status; + struct smb_filename *clientFname; + TALLOC_CTX *ctx; + + + DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + if (!is_in_media_files(smb_fname->base_name)) + { + status = SMB_VFS_NEXT_CREATE_FILE( + handle, + req, + dirfsp, + smb_fname, + access_mask, + share_access, + create_disposition, + create_options, + file_attributes, + oplock_request, + lease, + allocation_size, + private_flags, + sd, + ea_list, + result_fsp, + pinfo, + in_context_blobs, + out_context_blobs); + goto out; + } + + clientFname = NULL; + ctx = talloc_tos(); + + if (alloc_get_client_smb_fname(handle, ctx, + smb_fname, + &clientFname)) + { + status = map_nt_error_from_unix(errno); + goto err; + } + + /* This only creates files, so we don't have to worry about + * our fake directory stat'ing here. + */ + // But we still need to route stat calls for DB files + // properly, right? + status = SMB_VFS_NEXT_CREATE_FILE( + handle, + req, + dirfsp, + clientFname, + access_mask, + share_access, + create_disposition, + create_options, + file_attributes, + oplock_request, + lease, + allocation_size, + private_flags, + sd, + ea_list, + result_fsp, + pinfo, + in_context_blobs, + out_context_blobs); +err: + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s'" + "smb_fname->st.st_ex_mtime %s" + " fsp->fsp_name->st.st_ex_mtime %s", + smb_fname->base_name, + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)), + (*result_fsp) && VALID_STAT((*result_fsp)->fsp_name->st) ? + ctime(&((*result_fsp)->fsp_name->st.st_ex_mtime.tv_sec)) : + "No fsp time\n")); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int status = -1; + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + struct smb_filename *srcClientFname = NULL; + struct smb_filename *dstClientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering with " + "smb_fname_src->base_name '%s', " + "smb_fname_dst->base_name '%s'\n", + smb_fname_src->base_name, + smb_fname_dst->base_name)); + + if (!is_in_media_files(smb_fname_src->base_name) + && + !is_in_media_files(smb_fname_dst->base_name)) + { + status = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + goto out; + } + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + goto out; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + errno = ENOMEM; + goto out; + } + + if ((status = alloc_get_client_smb_fname(handle, + talloc_tos(), + full_fname_src, + &srcClientFname))) + { + goto err; + } + + if ((status = alloc_get_client_smb_fname(handle, + talloc_tos(), + full_fname_dst, + &dstClientFname))) + { + goto err; + } + + status = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp->conn->cwd_fsp, + srcClientFname, + dstfsp->conn->cwd_fsp, + dstClientFname); +err: + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + TALLOC_FREE(dstClientFname); + TALLOC_FREE(srcClientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname_src->base_name '%s'," + " smb_fname_dst->base_name '%s'\n", + smb_fname_src->base_name, + smb_fname_dst->base_name)); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int status = 0; + struct smb_filename *clientFname; + TALLOC_CTX *ctx; + + + DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) + { + status = SMB_VFS_NEXT_STAT(handle, smb_fname); + goto out; + } + + clientFname = NULL; + ctx = talloc_tos(); + + if ((status = alloc_get_client_smb_fname(handle, ctx, + smb_fname, + &clientFname))) + { + goto err; + } + DEBUG(MH_INFO_DEBUG, ("Stat'ing clientFname->base_name '%s'\n", + clientFname->base_name)); + if ((status = SMB_VFS_NEXT_STAT(handle, clientFname))) + { + goto err; + } + if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_stat))) + { + goto err; + } + + /* Unlike functions with const smb_filename, we have to + * modify smb_fname itself to pass our info back up. + */ + DEBUG(MH_INFO_DEBUG, ("Setting smb_fname '%s' stat " + "from clientFname '%s'\n", + smb_fname->base_name, + clientFname->base_name)); + smb_fname->st = clientFname->st; +err: + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s", + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)))); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int status = 0; + struct smb_filename *clientFname; + TALLOC_CTX *ctx; + + DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) + { + status = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + goto out; + } + + clientFname = NULL; + ctx = talloc_tos(); + + if ((status = alloc_get_client_smb_fname(handle, ctx, + smb_fname, + &clientFname))) + { + goto err; + } + if ((status = SMB_VFS_NEXT_LSTAT(handle, clientFname))) + { + goto err; + } + + if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_lstat))) + { + goto err; + } + /* Unlike functions with const smb_filename, we have to + * modify smb_fname itself to pass our info back up. + */ + smb_fname->st = clientFname->st; +err: + TALLOC_FREE(clientFname); +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s", + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)))); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_fstat(vfs_handle_struct *handle, + files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int status = 0; + + DEBUG(MH_INFO_DEBUG, ("Entering with fsp->fsp_name->base_name " + "'%s'\n", fsp_str_dbg(fsp))); + + if ((status = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf))) + { + goto out; + } + + if (fsp->fsp_name == NULL + || !is_in_media_files(fsp->fsp_name->base_name)) + { + goto out; + } + + if ((status = mh_stat(handle, fsp->fsp_name))) + { + goto out; + } + + *sbuf = fsp->fsp_name->st; +out: + DEBUG(MH_INFO_DEBUG, ("Leaving with fsp->fsp_name->st.st_ex_mtime " + "%s", + fsp->fsp_name != NULL ? + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)) : + "0")); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int status; + struct smb_filename *full_fname = NULL; + struct smb_filename *clientFname; + TALLOC_CTX *ctx; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_unlinkat\n")); + if (!is_in_media_files(smb_fname->base_name)) { + status = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto out; + } + + clientFname = NULL; + ctx = talloc_tos(); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + if ((status = alloc_get_client_smb_fname(handle, ctx, + full_fname, + &clientFname))) { + goto err; + } + + status = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + clientFname, + flags); +err: + TALLOC_FREE(full_fname); + TALLOC_FREE(clientFname); +out: + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int status; + struct smb_filename *clientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_lchown\n")); + if (!is_in_media_files(smb_fname->base_name)) + { + status = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid); + goto out; + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &clientFname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_LCHOWN(handle, clientFname, uid, gid); +err: + TALLOC_FREE(clientFname); +out: + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int status; + struct smb_filename *clientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_chdir\n")); + if (!is_in_media_files(smb_fname->base_name)) { + status = SMB_VFS_NEXT_CHDIR(handle, smb_fname); + goto out; + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &clientFname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_CHDIR(handle, clientFname); +err: + TALLOC_FREE(clientFname); +out: + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ + +static int mh_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int status = -1; + struct smb_filename *full_fname = NULL; + struct smb_filename *new_link_target = NULL; + struct smb_filename *newclientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_symlinkat\n")); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + status = -1; + goto err; + } + + if (!is_in_media_files(link_contents->base_name) && + !is_in_media_files(full_fname->base_name)) { + status = SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); + goto out; + } + + if ((status = alloc_get_client_smb_fname(handle, talloc_tos(), + link_contents, + &new_link_target))) { + goto err; + } + if ((status = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, + &newclientFname))) { + goto err; + } + + status = SMB_VFS_NEXT_SYMLINKAT(handle, + new_link_target, + handle->conn->cwd_fsp, + newclientFname); +err: + TALLOC_FREE(new_link_target); + TALLOC_FREE(newclientFname); +out: + TALLOC_FREE(full_fname); + return status; +} + +/* + * Success: return byte count + * Failure: set errno, return -1 + */ +static int mh_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + int status; + struct smb_filename *full_fname = NULL; + struct smb_filename *clientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_readlinkat\n")); + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + status = -1; + goto err; + } + + if (!is_in_media_files(full_fname->base_name)) { + status = SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + goto out; + } + + if ((status = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, + &clientFname))) { + goto err; + } + + status = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + clientFname, + buf, + bufsiz); + +err: + TALLOC_FREE(clientFname); +out: + TALLOC_FREE(full_fname); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int status; + struct smb_filename *old_full_fname = NULL; + struct smb_filename *oldclientFname = NULL; + struct smb_filename *new_full_fname = NULL; + struct smb_filename *newclientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_linkat\n")); + + old_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (old_full_fname == NULL) { + status = -1; + goto err; + } + + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + status = -1; + goto err; + } + + if (!is_in_media_files(old_full_fname->base_name) && + !is_in_media_files(new_full_fname->base_name)) { + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + + status = SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); + goto out; + } + + if ((status = alloc_get_client_smb_fname(handle, talloc_tos(), + old_full_fname, + &oldclientFname))) { + goto err; + } + if ((status = alloc_get_client_smb_fname(handle, talloc_tos(), + new_full_fname, + &newclientFname))) { + goto err; + } + + status = SMB_VFS_NEXT_LINKAT(handle, + handle->conn->cwd_fsp, + oldclientFname, + handle->conn->cwd_fsp, + newclientFname, + flags); + +err: + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + TALLOC_FREE(newclientFname); + TALLOC_FREE(oldclientFname); +out: + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int mh_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + int status; + struct smb_filename *full_fname = NULL; + struct smb_filename *clientFname = NULL; + TALLOC_CTX *ctx; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_mknodat\n")); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + status = -1; + goto err; + } + + if (!is_in_media_files(full_fname->base_name)) { + status = SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); + goto out; + } + + ctx = talloc_tos(); + + if ((status = alloc_get_client_smb_fname(handle, ctx, + full_fname, + &clientFname))) { + goto err; + } + + status = SMB_VFS_NEXT_MKNODAT(handle, + handle->conn->cwd_fsp, + clientFname, + mode, + dev); + +err: + TALLOC_FREE(clientFname); +out: + TALLOC_FREE(full_fname); + return status; +} + +/* + * Success: return path pointer + * Failure: set errno, return NULL pointer + */ +static struct smb_filename *mh_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + struct smb_filename *result_fname = NULL; + struct smb_filename *clientFname = NULL; + + DEBUG(MH_INFO_DEBUG, ("Entering mh_realpath\n")); + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + } + + if (alloc_get_client_smb_fname(handle, ctx, + smb_fname, + &clientFname) != 0) { + goto err; + } + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, clientFname); +err: + TALLOC_FREE(clientFname); + return result_fname; +} + +/* Ignoring get_real_filename function because the default + * doesn't do anything. + */ + +/* + * Success: return 0 + * Failure: set errno, return -1 + * In this case, "name" is an attr name. + */ + +/* VFS operations structure */ + +static struct vfs_fn_pointers vfs_mh_fns = { + /* Disk operations */ + + .statvfs_fn = mh_statvfs, + + /* Directory operations */ + + .fdopendir_fn = mh_fdopendir, + .readdir_fn = mh_readdir, + .rewind_dir_fn = mh_rewinddir, + .mkdirat_fn = mh_mkdirat, + .closedir_fn = mh_closedir, + + /* File operations */ + + .openat_fn = mh_openat, + .create_file_fn = mh_create_file, + .renameat_fn = mh_renameat, + .stat_fn = mh_stat, + .lstat_fn = mh_lstat, + .fstat_fn = mh_fstat, + .unlinkat_fn = mh_unlinkat, + .lchown_fn = mh_lchown, + .chdir_fn = mh_chdir, + .symlinkat_fn = mh_symlinkat, + .readlinkat_fn = mh_readlinkat, + .linkat_fn = mh_linkat, + .mknodat_fn = mh_mknodat, + .realpath_fn = mh_realpath, + + /* EA operations. */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + + /* aio operations */ +}; + +static_decl_vfs; +NTSTATUS vfs_media_harmony_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "media_harmony", &vfs_mh_fns); + if (!NT_STATUS_IS_OK(ret)) + { + goto out; + } + + vfs_mh_debug_level = debug_add_class("media_harmony"); + + if (vfs_mh_debug_level == -1) { + vfs_mh_debug_level = DBGC_VFS; + DEBUG(1, ("media_harmony_init: Couldn't register custom " + "debugging class.\n")); + } else { + DEBUG(3, ("media_harmony_init: Debug class number of " + "'media_harmony': %d\n", + vfs_mh_debug_level)); + } + +out: + return ret; +} diff --git a/source3/modules/vfs_nfs4acl_xattr.c b/source3/modules/vfs_nfs4acl_xattr.c new file mode 100644 index 0000000..1fd3519 --- /dev/null +++ b/source3/modules/vfs_nfs4acl_xattr.c @@ -0,0 +1,580 @@ +/* + * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa. + * + * Copyright (C) Jiri Sasek, 2007 + * based on the foobar.c module which is copyrighted by Volker Lendecke + * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006 + * + * based on vfs_fake_acls: + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Andrew Bartlett, 2002,2012 + * Copyright (C) Ralph Boehme 2017 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "libcli/security/security_token.h" +#include "libcli/security/dom_sid.h" +#include "nfs4_acls.h" +#include "librpc/gen_ndr/ndr_nfs4acl.h" +#include "nfs4acl_xattr.h" +#include "nfs4acl_xattr_ndr.h" +#include "nfs4acl_xattr_xdr.h" +#include "nfs4acl_xattr_nfs.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static const struct enum_list nfs4acl_encoding[] = { + {NFS4ACL_ENCODING_NDR, "ndr"}, + {NFS4ACL_ENCODING_XDR, "xdr"}, + {NFS4ACL_ENCODING_NFS, "nfs"}, +}; + +/* + * Check if someone changed the POSIX mode, for files we expect 0666, for + * directories 0777. Discard the ACL blob if the mode is different. + */ +static bool nfs4acl_validate_blob(vfs_handle_struct *handle, + files_struct *fsp) +{ + struct nfs4acl_config *config = NULL; + mode_t expected_mode; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + if (!config->validate_mode) { + return true; + } + + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + expected_mode = 0777; + } else { + expected_mode = 0666; + } + if ((fsp->fsp_name->st.st_ex_mode & expected_mode) == expected_mode) { + return true; + } + + ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, + fsp, + config->xattr_name); + if (ret != 0 && errno != ENOATTR) { + DBG_ERR("Removing NFS4 xattr failed: %s\n", strerror(errno)); + return false; + } + + return true; +} + +static NTSTATUS nfs4acl_get_blob(struct vfs_handle_struct *handle, + files_struct *fsp, + TALLOC_CTX *mem_ctx, + DATA_BLOB *blob) +{ + struct nfs4acl_config *config = NULL; + size_t allocsize = 256; + ssize_t length; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + *blob = data_blob_null; + + ok = nfs4acl_validate_blob(handle, fsp); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + + do { + + allocsize *= 4; + ok = data_blob_realloc(mem_ctx, blob, allocsize); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + length = SMB_VFS_NEXT_FGETXATTR(handle, + fsp, + config->xattr_name, + blob->data, + blob->length); + } while (length == -1 && errno == ERANGE && allocsize <= 65536); + + if (length == -1) { + return map_nt_error_from_unix(errno); + } + + return NT_STATUS_OK; +} + +static NTSTATUS nfs4acl_xattr_default_sd( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + struct security_descriptor **sd) +{ + struct nfs4acl_config *config = NULL; + enum default_acl_style default_acl_style; + mode_t required_mode; + SMB_STRUCT_STAT sbuf = smb_fname->st; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + default_acl_style = config->default_acl_style; + + if (!VALID_STAT(sbuf)) { + ret = vfs_stat_smb_basename(handle->conn, + smb_fname, + &sbuf); + if (ret != 0) { + return map_nt_error_from_unix(errno); + } + } + + if (S_ISDIR(sbuf.st_ex_mode)) { + required_mode = 0777; + } else { + required_mode = 0666; + } + if ((sbuf.st_ex_mode & required_mode) != required_mode) { + default_acl_style = DEFAULT_ACL_POSIX; + } + + return make_default_filesystem_acl(mem_ctx, + default_acl_style, + smb_fname->base_name, + &sbuf, + sd); +} + +static NTSTATUS nfs4acl_blob_to_smb4(struct vfs_handle_struct *handle, + DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct SMB4ACL_T **smb4acl) +{ + struct nfs4acl_config *config = NULL; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + switch (config->encoding) { + case NFS4ACL_ENCODING_NDR: + status = nfs4acl_ndr_blob_to_smb4(handle, mem_ctx, blob, smb4acl); + break; + case NFS4ACL_ENCODING_XDR: + status = nfs4acl_xdr_blob_to_smb4(handle, mem_ctx, blob, smb4acl); + break; + case NFS4ACL_ENCODING_NFS: + status = nfs4acl_nfs_blob_to_smb4(handle, mem_ctx, blob, smb4acl); + break; + default: + status = NT_STATUS_INTERNAL_ERROR; + break; + } + + return status; +} + +static NTSTATUS nfs4acl_xattr_fget_nt_acl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **sd) +{ + struct SMB4ACL_T *smb4acl = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + DATA_BLOB blob; + NTSTATUS status; + + status = nfs4acl_get_blob(handle, fsp, frame, &blob); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + TALLOC_FREE(frame); + return nfs4acl_xattr_default_sd( + handle, fsp->fsp_name, mem_ctx, sd); + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + status = nfs4acl_blob_to_smb4(handle, &blob, frame, &smb4acl); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + status = smb_fget_nt_acl_nfs4(fsp, NULL, security_info, mem_ctx, + sd, smb4acl); + TALLOC_FREE(frame); + return status; +} + +static bool nfs4acl_smb4acl_set_fn(vfs_handle_struct *handle, + files_struct *fsp, + struct SMB4ACL_T *smb4acl) +{ + struct nfs4acl_config *config = NULL; + DATA_BLOB blob; + NTSTATUS status; + int saved_errno = 0; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return false); + + switch (config->encoding) { + case NFS4ACL_ENCODING_NDR: + status = nfs4acl_smb4acl_to_ndr_blob(handle, talloc_tos(), + smb4acl, &blob); + break; + case NFS4ACL_ENCODING_XDR: + status = nfs4acl_smb4acl_to_xdr_blob(handle, talloc_tos(), + smb4acl, &blob); + break; + case NFS4ACL_ENCODING_NFS: + status = nfs4acl_smb4acl_to_nfs_blob(handle, talloc_tos(), + smb4acl, &blob); + break; + default: + status = NT_STATUS_INTERNAL_ERROR; + break; + } + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, config->xattr_name, + blob.data, blob.length, 0); + if (ret != 0) { + saved_errno = errno; + } + data_blob_free(&blob); + if (saved_errno != 0) { + errno = saved_errno; + } + if (ret != 0) { + DBG_ERR("can't store acl in xattr: %s\n", strerror(errno)); + return false; + } + + return true; +} + +static NTSTATUS nfs4acl_xattr_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + struct nfs4acl_config *config = NULL; + const struct security_token *token = NULL; + mode_t existing_mode; + mode_t expected_mode; + mode_t restored_mode; + bool chown_needed = false; + struct dom_sid_buf buf; + NTSTATUS status; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct nfs4acl_config, + return NT_STATUS_INTERNAL_ERROR); + + if (!VALID_STAT(fsp->fsp_name->st)) { + DBG_ERR("Invalid stat info on [%s]\n", fsp_str_dbg(fsp)); + return NT_STATUS_INTERNAL_ERROR; + } + + existing_mode = fsp->fsp_name->st.st_ex_mode; + if (S_ISDIR(existing_mode)) { + expected_mode = 0777; + } else { + expected_mode = 0666; + } + if (!config->validate_mode) { + existing_mode = 0; + expected_mode = 0; + } + if ((existing_mode & expected_mode) != expected_mode) { + + restored_mode = existing_mode | expected_mode; + + ret = SMB_VFS_NEXT_FCHMOD(handle, + fsp, + restored_mode); + if (ret != 0) { + DBG_ERR("Resetting POSIX mode on [%s] from [0%o]: %s\n", + fsp_str_dbg(fsp), existing_mode, + strerror(errno)); + return map_nt_error_from_unix(errno); + } + } + + status = smb_set_nt_acl_nfs4(handle, + fsp, + &config->nfs4_params, + security_info_sent, + psd, + nfs4acl_smb4acl_set_fn); + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + return status; + } + + /* + * We got access denied. If we're already root, or we didn't + * need to do a chown, or the fsp isn't open with WRITE_OWNER + * access, just return. + */ + + if ((security_info_sent & SECINFO_OWNER) && + (psd->owner_sid != NULL)) + { + chown_needed = true; + } + if ((security_info_sent & SECINFO_GROUP) && + (psd->group_sid != NULL)) + { + chown_needed = true; + } + + if (get_current_uid(handle->conn) == 0 || + chown_needed == false) + { + return NT_STATUS_ACCESS_DENIED; + } + status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Only allow take-ownership, not give-ownership. That's the way Windows + * implements SEC_STD_WRITE_OWNER. MS-FSA 2.1.5.16 just states: If + * InputBuffer.OwnerSid is not a valid owner SID for a file in the + * objectstore, as determined in an implementation specific manner, the + * object store MUST return STATUS_INVALID_OWNER. + */ + token = get_current_nttok(fsp->conn); + if (!security_token_is_sid(token, psd->owner_sid)) { + return NT_STATUS_INVALID_OWNER; + } + + DBG_DEBUG("overriding chown on file %s for sid %s\n", + fsp_str_dbg(fsp), + dom_sid_str_buf(psd->owner_sid, &buf)); + + status = smb_set_nt_acl_nfs4(handle, + fsp, + &config->nfs4_params, + security_info_sent, + psd, + nfs4acl_smb4acl_set_fn); + return status; +} + +static int nfs4acl_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct nfs4acl_config *config = NULL; + const struct enum_list *default_acl_style_list = NULL; + const char *default_xattr_name = NULL; + bool default_validate_mode = true; + int enumval; + unsigned nfs_version; + int ret; + + default_acl_style_list = get_default_acl_style_list(); + + config = talloc_zero(handle->conn, struct nfs4acl_config); + if (config == NULL) { + DBG_ERR("talloc_zero() failed\n"); + return -1; + } + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + TALLOC_FREE(config); + return ret; + } + + ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params); + if (ret < 0) { + TALLOC_FREE(config); + return ret; + } + + enumval = lp_parm_enum(SNUM(handle->conn), + "nfs4acl_xattr", + "encoding", + nfs4acl_encoding, + NFS4ACL_ENCODING_NDR); + if (enumval == -1) { + DBG_ERR("Invalid \"nfs4acl_xattr:encoding\" parameter\n"); + return -1; + } + config->encoding = (enum nfs4acl_encoding)enumval; + + switch (config->encoding) { + case NFS4ACL_ENCODING_XDR: + default_xattr_name = NFS4ACL_XDR_XATTR_NAME; + break; + case NFS4ACL_ENCODING_NFS: + default_xattr_name = NFS4ACL_NFS_XATTR_NAME; + default_validate_mode = false; + break; + case NFS4ACL_ENCODING_NDR: + default: + default_xattr_name = NFS4ACL_NDR_XATTR_NAME; + break; + } + + nfs_version = (unsigned)lp_parm_int(SNUM(handle->conn), + "nfs4acl_xattr", + "version", + 41); + switch (nfs_version) { + case 40: + config->nfs_version = ACL4_XATTR_VERSION_40; + break; + case 41: + config->nfs_version = ACL4_XATTR_VERSION_41; + break; + default: + config->nfs_version = ACL4_XATTR_VERSION_DEFAULT; + break; + } + + config->default_acl_style = lp_parm_enum(SNUM(handle->conn), + "nfs4acl_xattr", + "default acl style", + default_acl_style_list, + DEFAULT_ACL_EVERYONE); + + config->xattr_name = lp_parm_substituted_string(config, lp_sub, + SNUM(handle->conn), + "nfs4acl_xattr", + "xattr_name", + default_xattr_name); + + config->nfs4_id_numeric = lp_parm_bool(SNUM(handle->conn), + "nfs4acl_xattr", + "nfs4_id_numeric", + false); + + + config->validate_mode = lp_parm_bool(SNUM(handle->conn), + "nfs4acl_xattr", + "validate_mode", + default_validate_mode); + + SMB_VFS_HANDLE_SET_DATA(handle, config, NULL, struct nfs4acl_config, + return -1); + + /* + * Ensure we have the parameters correct if we're using this module. + */ + DBG_NOTICE("Setting 'inherit acls = true', " + "'dos filemode = true', " + "'force unknown acl user = true', " + "'create mask = 0666', " + "'directory mask = 0777' and " + "'store dos attributes = yes' " + "for service [%s]\n", service); + + lp_do_parameter(SNUM(handle->conn), "inherit acls", "true"); + lp_do_parameter(SNUM(handle->conn), "dos filemode", "true"); + lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true"); + lp_do_parameter(SNUM(handle->conn), "create mask", "0666"); + lp_do_parameter(SNUM(handle->conn), "directory mask", "0777"); + lp_do_parameter(SNUM(handle->conn), "store dos attributes", "yes"); + + return 0; +} + +/* + As long as Samba does not support an exiplicit method for a module + to define conflicting vfs methods, we should override all conflicting + methods here. That way, we know we are using the NFSv4 storage + + Function declarations taken from vfs_solarisacl +*/ + +static SMB_ACL_T nfs4acl_xattr_fail__sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + return (SMB_ACL_T)NULL; +} + +static int nfs4acl_xattr_fail__sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + return -1; +} + +static int nfs4acl_xattr_fail__sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + return -1; +} + +static int nfs4acl_xattr_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) +{ + return -1; +} + +/* VFS operations structure */ + +static struct vfs_fn_pointers nfs4acl_xattr_fns = { + .connect_fn = nfs4acl_connect, + .fget_nt_acl_fn = nfs4acl_xattr_fget_nt_acl, + .fset_nt_acl_fn = nfs4acl_xattr_fset_nt_acl, + + .sys_acl_get_fd_fn = nfs4acl_xattr_fail__sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = nfs4acl_xattr_fail__sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = nfs4acl_xattr_fail__sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = nfs4acl_xattr_fail__sys_acl_delete_def_fd, +}; + +static_decl_vfs; +NTSTATUS vfs_nfs4acl_xattr_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "nfs4acl_xattr", + &nfs4acl_xattr_fns); +} diff --git a/source3/modules/vfs_not_implemented.c b/source3/modules/vfs_not_implemented.c new file mode 100644 index 0000000..b00a499 --- /dev/null +++ b/source3/modules/vfs_not_implemented.c @@ -0,0 +1,1193 @@ +/* + * VFS module with "not implemented " helper functions for other modules. + * + * Copyright (C) Tim Potter, 1999-2000 + * Copyright (C) Alexander Bokovoy, 2002 + * Copyright (C) Stefan (metze) Metzmacher, 2003,2018 + * Copyright (C) Jeremy Allison 2009 + * + * 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 "lib/util/tevent_unix.h" +#include "lib/util/tevent_ntstatus.h" + +_PUBLIC_ +int vfs_not_implemented_connect( + vfs_handle_struct *handle, + const char *service, + const char *user) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +void vfs_not_implemented_disconnect(vfs_handle_struct *handle) +{ + ; +} + +_PUBLIC_ +uint64_t vfs_not_implemented_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + *bsize = 0; + *dfree = 0; + *dsize = 0; + return 0; +} + +_PUBLIC_ +int vfs_not_implemented_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_set_quota(vfs_handle_struct *handle, + enum SMB_QUOTA_TYPE qtype, + unid_t id, SMB_DISK_QUOTA *dq) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_get_shadow_copy_data(vfs_handle_struct *handle, + files_struct *fsp, + struct shadow_copy_data *shadow_copy_data, + bool labels) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +uint32_t vfs_not_implemented_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + return 0; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_get_dfs_referrals(struct vfs_handle_struct *handle, + struct dfs_GetDFSReferral *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **base_path, + char **snap_path) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +_PUBLIC_ +DIR *vfs_not_implemented_fdopendir(vfs_handle_struct *handle, files_struct *fsp, + const char *mask, uint32_t attr) +{ + errno = ENOSYS; + return NULL; +} + +_PUBLIC_ +struct dirent *vfs_not_implemented_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + errno = ENOSYS; + return NULL; +} + +_PUBLIC_ +void vfs_not_implemented_rewind_dir(vfs_handle_struct *handle, DIR *dirp) +{ + ; +} + +_PUBLIC_ +int vfs_not_implemented_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_closedir(vfs_handle_struct *handle, DIR *dir) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_create_file(struct vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result, int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +int vfs_not_implemented_close_fn(vfs_handle_struct *handle, files_struct *fsp) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_pread(vfs_handle_struct *handle, files_struct *fsp, + void *data, size_t n, off_t offset) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, size_t n, off_t offset) +{ + return NULL; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + vfs_aio_state->error = ENOSYS; + return -1; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_pwrite(vfs_handle_struct *handle, files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + return NULL; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + vfs_aio_state->error = ENOSYS; + return -1; +} + +_PUBLIC_ +off_t vfs_not_implemented_lseek(vfs_handle_struct *handle, files_struct *fsp, + off_t offset, int whence) +{ + errno = ENOSYS; + return (off_t) - 1; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_sendfile(vfs_handle_struct *handle, int tofd, + files_struct *fromfsp, const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_recvfile(vfs_handle_struct *handle, int fromfd, + files_struct *tofsp, off_t offset, size_t n) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_fsync_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + return NULL; +} + +_PUBLIC_ +int vfs_not_implemented_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + vfs_aio_state->error = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fstatat( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +uint64_t vfs_not_implemented_get_alloc_size(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const SMB_STRUCT_STAT *sbuf) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fchmod(vfs_handle_struct *handle, files_struct *fsp, + mode_t mode) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fchown(vfs_handle_struct *handle, files_struct *fsp, + uid_t uid, gid_t gid) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct smb_filename *vfs_not_implemented_getwd(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + errno = ENOSYS; + return NULL; +} + +_PUBLIC_ +int vfs_not_implemented_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_ftruncate(vfs_handle_struct *handle, files_struct *fsp, + off_t offset) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fallocate(vfs_handle_struct *handle, files_struct *fsp, + uint32_t mode, off_t offset, off_t len) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +bool vfs_not_implemented_lock(vfs_handle_struct *handle, files_struct *fsp, int op, + off_t offset, off_t count, int type) +{ + errno = ENOSYS; + return false; +} + +_PUBLIC_ +int vfs_not_implemented_filesystem_sharemode(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fcntl(struct vfs_handle_struct *handle, + struct files_struct *fsp, int cmd, + va_list cmd_arg) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_linux_setlease(struct vfs_handle_struct *handle, + struct files_struct *fsp, int leasetype) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +bool vfs_not_implemented_getlock(vfs_handle_struct *handle, files_struct *fsp, + off_t *poffset, off_t *pcount, int *ptype, + pid_t *ppid) +{ + errno = ENOSYS; + return false; +} + +_PUBLIC_ +int vfs_not_implemented_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_vfs_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct smb_filename *vfs_not_implemented_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + errno = ENOSYS; + return NULL; +} + +_PUBLIC_ +int vfs_not_implemented_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + uint flags) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +struct file_id vfs_not_implemented_file_id_create(vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id; + ZERO_STRUCT(id); + errno = ENOSYS; + return id; +} + +_PUBLIC_ +uint64_t vfs_not_implemented_fs_file_id(vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + errno = ENOSYS; + return 0; +} + +struct vfs_not_implemented_offload_read_state { + bool dummy; +}; + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct vfs_not_implemented_offload_read_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct vfs_not_implemented_offload_read_state); + if (req == NULL) { + return NULL; + } + + tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED); + return tevent_req_post(req, ev); +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_offload_read_recv(struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *_token_blob) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct vfs_not_implemented_offload_write_state { + uint64_t unused; +}; + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_offload_write_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req; + struct vfs_not_implemented_offload_write_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct vfs_not_implemented_offload_write_state); + if (req == NULL) { + return NULL; + } + + tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED); + return tevent_req_post(req, ev); +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fget_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + return NT_STATUS_INVALID_DEVICE_REQUEST; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_set_compression(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + return NT_STATUS_INVALID_DEVICE_REQUEST; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fstreaminfo(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *num_streams, + struct stream_struct **streams) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +const char *vfs_not_implemented_connectpath( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + errno = ENOSYS; + return NULL; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_brl_lock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + struct lock_struct *plock) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +bool vfs_not_implemented_brl_unlock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + const struct lock_struct *plock) +{ + errno = ENOSYS; + return false; +} + +_PUBLIC_ +bool vfs_not_implemented_strict_lock_check(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct lock_struct *plock) +{ + errno = ENOSYS; + return false; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_translate_name(struct vfs_handle_struct *handle, + const char *mapped_name, + enum vfs_translate_direction direction, + TALLOC_CTX *mem_ctx, char **pmapped_name) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_parent_pathname(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const struct smb_filename *smb_fname_in, + struct smb_filename **parent_dir_out, + struct smb_filename **atname_out) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fsctl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *ctx, + uint32_t function, + uint16_t req_flags, /* Needed for UNICODE ... */ + const uint8_t *_in_data, + uint32_t in_len, + uint8_t **_out_data, + uint32_t max_out_len, uint32_t *out_len) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **pattr_data) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +struct vfs_not_implemented_get_dos_attributes_state { + struct vfs_aio_state aio_state; + uint32_t dosmode; +}; + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_get_dos_attributes_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + struct smb_filename *smb_fname) +{ + struct tevent_req *req = NULL; + struct vfs_not_implemented_get_dos_attributes_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct vfs_not_implemented_get_dos_attributes_state); + if (req == NULL) { + return NULL; + } + + tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED); + return tevent_req_post(req, ev); +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_get_dos_attributes_recv( + struct tevent_req *req, + struct vfs_aio_state *aio_state, + uint32_t *dosmode) +{ + struct vfs_not_implemented_get_dos_attributes_state *state = + tevent_req_data(req, + struct vfs_not_implemented_get_dos_attributes_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *aio_state = state->aio_state; + *dosmode = state->dosmode; + tevent_req_received(req); + return NT_STATUS_OK; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +SMB_ACL_T vfs_not_implemented_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + errno = ENOSYS; + return (SMB_ACL_T) NULL; +} + +_PUBLIC_ +int vfs_not_implemented_sys_acl_blob_get_fd(vfs_handle_struct *handle, + files_struct *fsp, TALLOC_CTX *mem_ctx, + char **blob_description, DATA_BLOB *blob) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_sys_acl_set_fd(vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_sys_acl_delete_def_fd(vfs_handle_struct *handle, + struct files_struct *fsp) +{ + errno = ENOSYS; + return -1; +} + +struct vfs_not_implemented_getxattrat_state { + struct vfs_aio_state aio_state; + ssize_t xattr_size; + uint8_t *xattr_value; +}; + +_PUBLIC_ +struct tevent_req *vfs_not_implemented_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct tevent_req *req = NULL; + struct vfs_not_implemented_getxattrat_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct vfs_not_implemented_getxattrat_state); + if (req == NULL) { + return NULL; + } + + tevent_req_error(req, ENOSYS); + return tevent_req_post(req, ev); +} + +_PUBLIC_ +ssize_t vfs_not_implemented_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct vfs_not_implemented_getxattrat_state *state = tevent_req_data( + req, struct vfs_not_implemented_getxattrat_state); + ssize_t xattr_size; + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *aio_state = state->aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_fgetxattr(vfs_handle_struct *handle, + struct files_struct *fsp, const char *name, + void *value, size_t size) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +ssize_t vfs_not_implemented_flistxattr(vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fremovexattr(vfs_handle_struct *handle, + struct files_struct *fsp, const char *name) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +int vfs_not_implemented_fsetxattr(vfs_handle_struct *handle, struct files_struct *fsp, + const char *name, const void *value, size_t size, + int flags) +{ + errno = ENOSYS; + return -1; +} + +_PUBLIC_ +bool vfs_not_implemented_aio_force(struct vfs_handle_struct *handle, + struct files_struct *fsp) +{ + errno = ENOSYS; + return false; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_audit_file(struct vfs_handle_struct *handle, + struct smb_filename *file, + struct security_acl *sacl, + uint32_t access_requested, + uint32_t access_denied) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_durable_cookie(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cookie) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_durable_disconnect(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + DATA_BLOB *new_cookie) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +_PUBLIC_ +NTSTATUS vfs_not_implemented_durable_reconnect(struct vfs_handle_struct *handle, + struct smb_request *smb1req, + struct smbXsrv_open *op, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + struct files_struct **fsp, + DATA_BLOB *new_cookie) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* VFS operations structure */ + +static struct vfs_fn_pointers vfs_not_implemented_fns = { + /* Disk operations */ + + .connect_fn = vfs_not_implemented_connect, + .disconnect_fn = vfs_not_implemented_disconnect, + .disk_free_fn = vfs_not_implemented_disk_free, + .get_quota_fn = vfs_not_implemented_get_quota, + .set_quota_fn = vfs_not_implemented_set_quota, + .get_shadow_copy_data_fn = vfs_not_implemented_get_shadow_copy_data, + .statvfs_fn = vfs_not_implemented_statvfs, + .fs_capabilities_fn = vfs_not_implemented_fs_capabilities, + .get_dfs_referrals_fn = vfs_not_implemented_get_dfs_referrals, + .create_dfs_pathat_fn = vfs_not_implemented_create_dfs_pathat, + .read_dfs_pathat_fn = vfs_not_implemented_read_dfs_pathat, + .snap_check_path_fn = vfs_not_implemented_snap_check_path, + .snap_create_fn = vfs_not_implemented_snap_create, + .snap_delete_fn = vfs_not_implemented_snap_delete, + + /* Directory operations */ + + .fdopendir_fn = vfs_not_implemented_fdopendir, + .readdir_fn = vfs_not_implemented_readdir, + .rewind_dir_fn = vfs_not_implemented_rewind_dir, + .mkdirat_fn = vfs_not_implemented_mkdirat, + .closedir_fn = vfs_not_implemented_closedir, + + /* File operations */ + + .openat_fn = vfs_not_implemented_openat, + .create_file_fn = vfs_not_implemented_create_file, + .close_fn = vfs_not_implemented_close_fn, + .pread_fn = vfs_not_implemented_pread, + .pread_send_fn = vfs_not_implemented_pread_send, + .pread_recv_fn = vfs_not_implemented_pread_recv, + .pwrite_fn = vfs_not_implemented_pwrite, + .pwrite_send_fn = vfs_not_implemented_pwrite_send, + .pwrite_recv_fn = vfs_not_implemented_pwrite_recv, + .lseek_fn = vfs_not_implemented_lseek, + .sendfile_fn = vfs_not_implemented_sendfile, + .recvfile_fn = vfs_not_implemented_recvfile, + .renameat_fn = vfs_not_implemented_renameat, + .fsync_send_fn = vfs_not_implemented_fsync_send, + .fsync_recv_fn = vfs_not_implemented_fsync_recv, + .stat_fn = vfs_not_implemented_stat, + .fstat_fn = vfs_not_implemented_fstat, + .lstat_fn = vfs_not_implemented_lstat, + .fstatat_fn = vfs_not_implemented_fstatat, + .get_alloc_size_fn = vfs_not_implemented_get_alloc_size, + .unlinkat_fn = vfs_not_implemented_unlinkat, + .fchmod_fn = vfs_not_implemented_fchmod, + .fchown_fn = vfs_not_implemented_fchown, + .lchown_fn = vfs_not_implemented_lchown, + .chdir_fn = vfs_not_implemented_chdir, + .getwd_fn = vfs_not_implemented_getwd, + .fntimes_fn = vfs_not_implemented_fntimes, + .ftruncate_fn = vfs_not_implemented_ftruncate, + .fallocate_fn = vfs_not_implemented_fallocate, + .lock_fn = vfs_not_implemented_lock, + .filesystem_sharemode_fn = vfs_not_implemented_filesystem_sharemode, + .fcntl_fn = vfs_not_implemented_fcntl, + .linux_setlease_fn = vfs_not_implemented_linux_setlease, + .getlock_fn = vfs_not_implemented_getlock, + .symlinkat_fn = vfs_not_implemented_symlinkat, + .readlinkat_fn = vfs_not_implemented_vfs_readlinkat, + .linkat_fn = vfs_not_implemented_linkat, + .mknodat_fn = vfs_not_implemented_mknodat, + .realpath_fn = vfs_not_implemented_realpath, + .fchflags_fn = vfs_not_implemented_fchflags, + .file_id_create_fn = vfs_not_implemented_file_id_create, + .fs_file_id_fn = vfs_not_implemented_fs_file_id, + .offload_read_send_fn = vfs_not_implemented_offload_read_send, + .offload_read_recv_fn = vfs_not_implemented_offload_read_recv, + .offload_write_send_fn = vfs_not_implemented_offload_write_send, + .offload_write_recv_fn = vfs_not_implemented_offload_write_recv, + .fget_compression_fn = vfs_not_implemented_fget_compression, + .set_compression_fn = vfs_not_implemented_set_compression, + + .fstreaminfo_fn = vfs_not_implemented_fstreaminfo, + .get_real_filename_at_fn = vfs_not_implemented_get_real_filename_at, + .connectpath_fn = vfs_not_implemented_connectpath, + .brl_lock_windows_fn = vfs_not_implemented_brl_lock_windows, + .brl_unlock_windows_fn = vfs_not_implemented_brl_unlock_windows, + .strict_lock_check_fn = vfs_not_implemented_strict_lock_check, + .translate_name_fn = vfs_not_implemented_translate_name, + .parent_pathname_fn = vfs_not_implemented_parent_pathname, + .fsctl_fn = vfs_not_implemented_fsctl, + .freaddir_attr_fn = vfs_not_implemented_freaddir_attr, + .audit_file_fn = vfs_not_implemented_audit_file, + + /* DOS attributes. */ + .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv, + .fget_dos_attributes_fn = vfs_not_implemented_fget_dos_attributes, + .fset_dos_attributes_fn = vfs_not_implemented_fset_dos_attributes, + + /* NT ACL operations. */ + + .fget_nt_acl_fn = vfs_not_implemented_fget_nt_acl, + .fset_nt_acl_fn = vfs_not_implemented_fset_nt_acl, + + /* POSIX ACL operations. */ + + .sys_acl_get_fd_fn = vfs_not_implemented_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = vfs_not_implemented_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = vfs_not_implemented_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = vfs_not_implemented_sys_acl_delete_def_fd, + + /* EA operations. */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = vfs_not_implemented_fgetxattr, + .flistxattr_fn = vfs_not_implemented_flistxattr, + .fremovexattr_fn = vfs_not_implemented_fremovexattr, + .fsetxattr_fn = vfs_not_implemented_fsetxattr, + + /* aio operations */ + .aio_force_fn = vfs_not_implemented_aio_force, + + /* durable handle operations */ + .durable_cookie_fn = vfs_not_implemented_durable_cookie, + .durable_disconnect_fn = vfs_not_implemented_durable_disconnect, + .durable_reconnect_fn = vfs_not_implemented_durable_reconnect, +}; + +static_decl_vfs; +NTSTATUS vfs_not_implemented_init(TALLOC_CTX *ctx) +{ + /* + * smb_vfs_assert_all_fns() makes sure every + * call is implemented. + */ + smb_vfs_assert_all_fns(&vfs_not_implemented_fns, "vfs_not_implemented"); + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "vfs_not_implemented", + &vfs_not_implemented_fns); +} diff --git a/source3/modules/vfs_offline.c b/source3/modules/vfs_offline.c new file mode 100644 index 0000000..06edb76 --- /dev/null +++ b/source3/modules/vfs_offline.c @@ -0,0 +1,50 @@ +/* + Unix SMB/CIFS implementation. + Samba VFS module for marking all files as offline. + + (c) Uri Simchoni, 2015 + + 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" + +static uint32_t offline_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | + FILE_SUPPORTS_REMOTE_STORAGE; +} + +static NTSTATUS offline_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + *dosmode |= FILE_ATTRIBUTE_OFFLINE; + return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode); +} + +static struct vfs_fn_pointers offline_fns = { + .fs_capabilities_fn = offline_fs_capabilities, + .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv, + .fget_dos_attributes_fn = offline_fget_dos_attributes, +}; + +static_decl_vfs; +NTSTATUS vfs_offline_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "offline", + &offline_fns); +} diff --git a/source3/modules/vfs_posix_eadb.c b/source3/modules/vfs_posix_eadb.c new file mode 100644 index 0000000..b3e21b0 --- /dev/null +++ b/source3/modules/vfs_posix_eadb.c @@ -0,0 +1,450 @@ +/* + * Store posix-level xattrs in a tdb (posix:eadb format) + * + * Copyright (C) Andrew Bartlett, 2011 + * + * Based on vfs_xattr_tdb by + * Copyright (C) Volker Lendecke, 2007 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "librpc/gen_ndr/xattr.h" +#include "librpc/gen_ndr/ndr_xattr.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include +#include "lib/tdb_wrap/tdb_wrap.h" +#include "ntvfs/posix/posix_eadb.h" +#include "param/param.h" +#include "lib/param/loadparm.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* + * Worker routine for getxattr and fgetxattr + */ + +static ssize_t posix_eadb_getattr(struct tdb_wrap *db_ctx, + const char *fname, int fd, + const char *name, void *value, size_t size) +{ + ssize_t result = -1; + NTSTATUS status; + DATA_BLOB blob; + + DEBUG(10, ("posix_eadb_getattr called for file %s/fd %d, name %s\n", + fname, fd, name)); + + status = pull_xattr_blob_tdb_raw(db_ctx, talloc_tos(), name, fname, fd, size, &blob); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + errno = ENOATTR; + return -1; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("posix_eadb_fetch_attrs failed: %s\n", + nt_errstr(status))); + errno = EINVAL; + return -1; + } + + if (blob.length > size) { + errno = ERANGE; + goto fail; + } + + memcpy(value, blob.data, blob.length); + result = blob.length; + + fail: + return result; +} + +static ssize_t posix_eadb_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, void *value, size_t size) +{ + struct tdb_wrap *db; + + SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1); + + return posix_eadb_getattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name, value, size); +} + +/* + * Worker routine for setxattr and fsetxattr + */ + +static int posix_eadb_setattr(struct tdb_wrap *db_ctx, + const char *fname, int fd, const char *name, + const void *value, size_t size, int flags) +{ + NTSTATUS status; + DATA_BLOB data = data_blob_const(value, size); + + DEBUG(10, ("posix_eadb_setattr called for file %s/fd %d, name %s\n", + fname, fd, name)); + + status = push_xattr_blob_tdb_raw(db_ctx, name, fname, fd, &data); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("push_xattr_blob_tdb_raw failed: %s\n", + nt_errstr(status))); + return -1; + } + + return 0; +} + +static int posix_eadb_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, const void *value, + size_t size, int flags) +{ + struct tdb_wrap *db; + + SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1); + + return posix_eadb_setattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name, value, size, flags); +} + +/* + * Worker routine for listxattr and flistxattr + */ + +static ssize_t posix_eadb_listattr(struct tdb_wrap *db_ctx, + const char *fname, int fd, char *list, + size_t size) +{ + DATA_BLOB blob; + NTSTATUS status; + + status = list_posix_eadb_raw(db_ctx, talloc_tos(), fname, fd, &blob); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("posix_eadb_fetch_attrs failed: %s\n", + nt_errstr(status))); + errno = EINVAL; + return -1; + } + + if (blob.length > size) { + errno = ERANGE; + TALLOC_FREE(blob.data); + return -1; + } + + memcpy(list, blob.data, blob.length); + + TALLOC_FREE(blob.data); + return blob.length; +} + +static ssize_t posix_eadb_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + struct tdb_wrap *db; + + SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1); + + return posix_eadb_listattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), list, size); +} + +/* + * Worker routine for removexattr and fremovexattr + */ + +static int posix_eadb_removeattr(struct tdb_wrap *db_ctx, + const char *fname, int fd, const char *name) +{ + NTSTATUS status; + + status = delete_posix_eadb_raw(db_ctx, name, fname, fd); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("delete_posix_eadb_raw failed: %s\n", + nt_errstr(status))); + return -1; + } + return 0; +} + +static int posix_eadb_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name) +{ + struct tdb_wrap *db; + + SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1); + + return posix_eadb_removeattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name); +} + +/* + * Open the tdb file upon VFS_CONNECT + */ + +static bool posix_eadb_init(int snum, struct tdb_wrap **p_db) +{ + struct tdb_wrap *db; + struct loadparm_context *lp_ctx; + const char *eadb = lp_parm_const_string(snum, "posix", "eadb", NULL); + + if (!eadb) { + DEBUG(0, ("Can not use vfs_posix_eadb without posix:eadb set\n")); + return false; + } + + lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers()); + + become_root(); + db = tdb_wrap_open(NULL, eadb, 50000, + lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT), + O_RDWR|O_CREAT, 0600); + + unbecome_root(); + talloc_unlink(NULL, lp_ctx); + /* now we know dbname is not NULL */ + + if (db == NULL) { +#if defined(ENOTSUP) + errno = ENOTSUP; +#else + errno = ENOSYS; +#endif + return false; + } + + *p_db = db; + return true; +} + +/* + * On unlink we need to delete the tdb record + */ +static int posix_eadb_unlink_internal(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *smb_fname_tmp = NULL; + int ret = -1; + + struct tdb_wrap *ea_tdb; + + SMB_VFS_HANDLE_GET_DATA(handle, ea_tdb, struct tdb_wrap, return -1); + + smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname); + if (smb_fname_tmp == NULL) { + errno = ENOMEM; + return -1; + } + + /* + * TODO: use SMB_VFS_STATX() once we have that. + */ + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + goto out; + } + + if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) { + ret = SMB_VFS_NEXT_LSTAT(handle, full_fname); + } else { + ret = SMB_VFS_NEXT_STAT(handle, full_fname); + } + if (ret == -1) { + goto out; + } + smb_fname_tmp->st = full_fname->st; + + if (smb_fname_tmp->st.st_ex_nlink == 1) { + NTSTATUS status; + + /* Only remove record on last link to file. */ + + if (tdb_transaction_start(ea_tdb->tdb) != 0) { + ret = -1; + goto out; + } + + status = unlink_posix_eadb_raw(ea_tdb, + full_fname->base_name, + -1); + if (!NT_STATUS_IS_OK(status)) { + tdb_transaction_cancel(ea_tdb->tdb); + ret = -1; + goto out; + } + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname_tmp, + flags); + + if (ret == -1) { + tdb_transaction_cancel(ea_tdb->tdb); + goto out; + } else { + if (tdb_transaction_commit(ea_tdb->tdb) != 0) { + ret = -1; + goto out; + } + } + +out: + TALLOC_FREE(smb_fname_tmp); + TALLOC_FREE(full_fname); + return ret; +} + +/* + * On rmdir we need to delete the tdb record + */ +static int posix_eadb_rmdir_internal(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + NTSTATUS status; + struct tdb_wrap *ea_tdb; + int ret; + struct smb_filename *full_fname = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, ea_tdb, struct tdb_wrap, return -1); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + if (tdb_transaction_start(ea_tdb->tdb) != 0) { + TALLOC_FREE(full_fname); + return -1; + } + + status = unlink_posix_eadb_raw(ea_tdb, full_fname->base_name, -1); + TALLOC_FREE(full_fname); + if (!NT_STATUS_IS_OK(status)) { + tdb_transaction_cancel(ea_tdb->tdb); + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + + if (ret == -1) { + tdb_transaction_cancel(ea_tdb->tdb); + } else { + if (tdb_transaction_commit(ea_tdb->tdb) != 0) { + return -1; + } + } + + return ret; +} + +static int posix_eadb_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + + if (flags & AT_REMOVEDIR) { + ret = posix_eadb_rmdir_internal(handle, + dirfsp, + smb_fname); + } else { + ret = posix_eadb_unlink_internal(handle, + dirfsp, + smb_fname, + flags); + } + return ret; +} + +/* + * Destructor for the VFS private data + */ + +static void close_xattr_db(void **data) +{ + struct tdb_wrap **p_db = (struct tdb_wrap **)data; + TALLOC_FREE(*p_db); +} + +static int posix_eadb_connect(vfs_handle_struct *handle, const char *service, + const char *user) +{ + char *sname = NULL; + int res, snum; + struct tdb_wrap *db; + + res = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (res < 0) { + return res; + } + + snum = find_service(talloc_tos(), service, &sname); + if (snum == -1 || sname == NULL) { + /* + * Should not happen, but we should not fail just *here*. + */ + return 0; + } + + if (!posix_eadb_init(snum, &db)) { + DEBUG(5, ("Could not init xattr tdb\n")); + lp_do_parameter(snum, "ea support", "False"); + return 0; + } + + lp_do_parameter(snum, "ea support", "True"); + + SMB_VFS_HANDLE_SET_DATA(handle, db, close_xattr_db, + struct tdb_wrap, return -1); + + return 0; +} + +static struct vfs_fn_pointers vfs_posix_eadb_fns = { + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = posix_eadb_fgetxattr, + .fsetxattr_fn = posix_eadb_fsetxattr, + .flistxattr_fn = posix_eadb_flistxattr, + .fremovexattr_fn = posix_eadb_fremovexattr, + .unlinkat_fn = posix_eadb_unlinkat, + .connect_fn = posix_eadb_connect, +}; + +static_decl_vfs; +NTSTATUS vfs_posix_eadb_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "posix_eadb", + &vfs_posix_eadb_fns); +} diff --git a/source3/modules/vfs_posixacl.c b/source3/modules/vfs_posixacl.c new file mode 100644 index 0000000..feb819d --- /dev/null +++ b/source3/modules/vfs_posixacl.c @@ -0,0 +1,389 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls + Copyright (C) Volker Lendecke 2006 + + 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/filesys.h" +#include "smbd/smbd.h" +#include "modules/vfs_posixacl.h" + +/* prototypes for static functions first - for clarity */ + +static bool smb_ace_to_internal(acl_entry_t posix_ace, + struct smb_acl_entry *ace); +static struct smb_acl_t *smb_acl_to_internal(acl_t acl, TALLOC_CTX *mem_ctx); +static int smb_acl_set_mode(acl_entry_t entry, SMB_ACL_PERM_T perm); +static acl_t smb_acl_to_posix(const struct smb_acl_t *acl); + + +/* public functions - the api */ + +SMB_ACL_T posixacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + struct smb_acl_t *result; + acl_t acl = NULL; + acl_type_t acl_type; + + switch(type) { + case SMB_ACL_TYPE_ACCESS: + acl_type = ACL_TYPE_ACCESS; + break; + case SMB_ACL_TYPE_DEFAULT: + acl_type = ACL_TYPE_DEFAULT; + break; + default: + errno = EINVAL; + return NULL; + } + if (!fsp->fsp_flags.is_pathref && (acl_type == ACL_TYPE_ACCESS)) { + /* POSIX API only allows ACL_TYPE_ACCESS fetched on fd. */ + acl = acl_get_fd(fsp_get_io_fd(fsp)); + } else if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + acl = acl_get_file(sys_proc_fd_path(fd, &buf), acl_type); + } else { + /* + * This is no longer a handle based call. + */ + acl = acl_get_file(fsp->fsp_name->base_name, acl_type); + } + if (acl == NULL) { + return NULL; + } + + result = smb_acl_to_internal(acl, mem_ctx); + acl_free(acl); + return result; +} + +int posixacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + int res; + acl_t acl = smb_acl_to_posix(theacl); + acl_type_t acl_type; + int fd = fsp_get_pathref_fd(fsp); + + if (acl == NULL) { + return -1; + } + + switch(type) { + case SMB_ACL_TYPE_ACCESS: + acl_type = ACL_TYPE_ACCESS; + break; + case SMB_ACL_TYPE_DEFAULT: + acl_type = ACL_TYPE_DEFAULT; + break; + default: + acl_free(acl); + errno = EINVAL; + return -1; + } + + if (!fsp->fsp_flags.is_pathref && type == SMB_ACL_TYPE_ACCESS) { + res = acl_set_fd(fd, acl); + } else if (fsp->fsp_flags.have_proc_fds) { + struct sys_proc_fd_path_buf buf; + + res = acl_set_file(sys_proc_fd_path(fd, &buf), acl_type, acl); + } else { + /* + * This is no longer a handle based call. + */ + res = acl_set_file(fsp->fsp_name->base_name, + acl_type, + acl); + } + + acl_free(acl); + return res; +} + +int posixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + if (fsp->fsp_flags.have_proc_fds) { + int fd = fsp_get_pathref_fd(fsp); + struct sys_proc_fd_path_buf buf; + + return acl_delete_def_file(sys_proc_fd_path(fd, &buf)); + } + + /* + * This is no longer a handle based call. + */ + return acl_delete_def_file(fsp->fsp_name->base_name); +} + +/* private functions */ + +static bool smb_ace_to_internal(acl_entry_t posix_ace, + struct smb_acl_entry *ace) +{ + acl_tag_t tag; + acl_permset_t permset; + + if (acl_get_tag_type(posix_ace, &tag) != 0) { + DEBUG(0, ("smb_acl_get_tag_type failed\n")); + return False; + } + + switch(tag) { + case ACL_USER: + ace->a_type = SMB_ACL_USER; + break; + case ACL_USER_OBJ: + ace->a_type = SMB_ACL_USER_OBJ; + break; + case ACL_GROUP: + ace->a_type = SMB_ACL_GROUP; + break; + case ACL_GROUP_OBJ: + ace->a_type = SMB_ACL_GROUP_OBJ; + break; + case ACL_OTHER: + ace->a_type = SMB_ACL_OTHER; + break; + case ACL_MASK: + ace->a_type = SMB_ACL_MASK; + break; +#ifdef HAVE_ACL_EVERYONE + case ACL_EVERYONE: + DEBUG(1, ("ACL tag type ACL_EVERYONE. FreeBSD with ZFS? Use 'vfs objects = zfsacl'\n")); + return false; +#endif + default: + DEBUG(0, ("unknown tag type %d\n", (unsigned int)tag)); + return False; + } + switch(ace->a_type) { + case SMB_ACL_USER: { + uid_t *puid = (uid_t *)acl_get_qualifier(posix_ace); + if (puid == NULL) { + DEBUG(0, ("smb_acl_get_qualifier failed\n")); + return False; + } + ace->info.user.uid = *puid; + acl_free(puid); + break; + } + + case SMB_ACL_GROUP: { + gid_t *pgid = (uid_t *)acl_get_qualifier(posix_ace); + if (pgid == NULL) { + DEBUG(0, ("smb_acl_get_qualifier failed\n")); + return False; + } + ace->info.group.gid = *pgid; + acl_free(pgid); + break; + } + default: + break; + } + if (acl_get_permset(posix_ace, &permset) != 0) { + DEBUG(0, ("smb_acl_get_mode failed\n")); + return False; + } + ace->a_perm = 0; +#ifdef HAVE_ACL_GET_PERM_NP + ace->a_perm |= (acl_get_perm_np(permset, ACL_READ) ? SMB_ACL_READ : 0); + ace->a_perm |= (acl_get_perm_np(permset, ACL_WRITE) ? SMB_ACL_WRITE : 0); + ace->a_perm |= (acl_get_perm_np(permset, ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); +#else + ace->a_perm |= (acl_get_perm(permset, ACL_READ) ? SMB_ACL_READ : 0); + ace->a_perm |= (acl_get_perm(permset, ACL_WRITE) ? SMB_ACL_WRITE : 0); + ace->a_perm |= (acl_get_perm(permset, ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); +#endif + return True; +} + +static struct smb_acl_t *smb_acl_to_internal(acl_t acl, TALLOC_CTX *mem_ctx) +{ + struct smb_acl_t *result = sys_acl_init(mem_ctx); + int entry_id = ACL_FIRST_ENTRY; + acl_entry_t e; + if (result == NULL) { + return NULL; + } + while (acl_get_entry(acl, entry_id, &e) == 1) { + + entry_id = ACL_NEXT_ENTRY; + + result->acl = talloc_realloc(result, result->acl, + struct smb_acl_entry, result->count+1); + if (result->acl == NULL) { + TALLOC_FREE(result); + DEBUG(0, ("talloc_realloc failed\n")); + errno = ENOMEM; + return NULL; + } + + if (!smb_ace_to_internal(e, &result->acl[result->count])) { + TALLOC_FREE(result); + return NULL; + } + + result->count += 1; + } + return result; +} + +static int smb_acl_set_mode(acl_entry_t entry, SMB_ACL_PERM_T perm) +{ + int ret; + acl_permset_t permset; + + if ((ret = acl_get_permset(entry, &permset)) != 0) { + return ret; + } + if ((ret = acl_clear_perms(permset)) != 0) { + return ret; + } + if ((perm & SMB_ACL_READ) && + ((ret = acl_add_perm(permset, ACL_READ)) != 0)) { + return ret; + } + if ((perm & SMB_ACL_WRITE) && + ((ret = acl_add_perm(permset, ACL_WRITE)) != 0)) { + return ret; + } + if ((perm & SMB_ACL_EXECUTE) && + ((ret = acl_add_perm(permset, ACL_EXECUTE)) != 0)) { + return ret; + } + + return 0; +} + +static acl_t smb_acl_to_posix(const struct smb_acl_t *acl) +{ + acl_t result; + int i; + + result = acl_init(acl->count); + if (result == NULL) { + DEBUG(10, ("acl_init failed\n")); + return NULL; + } + + for (i=0; icount; i++) { + const struct smb_acl_entry *entry = &acl->acl[i]; + acl_entry_t e; + acl_tag_t tag; + + if (acl_create_entry(&result, &e) != 0) { + DEBUG(1, ("acl_create_entry failed: %s\n", + strerror(errno))); + goto fail; + } + + switch (entry->a_type) { + case SMB_ACL_USER: + tag = ACL_USER; + break; + case SMB_ACL_USER_OBJ: + tag = ACL_USER_OBJ; + break; + case SMB_ACL_GROUP: + tag = ACL_GROUP; + break; + case SMB_ACL_GROUP_OBJ: + tag = ACL_GROUP_OBJ; + break; + case SMB_ACL_OTHER: + tag = ACL_OTHER; + break; + case SMB_ACL_MASK: + tag = ACL_MASK; + break; + default: + DEBUG(1, ("Unknown tag value %d\n", entry->a_type)); + goto fail; + } + + if (acl_set_tag_type(e, tag) != 0) { + DEBUG(10, ("acl_set_tag_type(%d) failed: %s\n", + tag, strerror(errno))); + goto fail; + } + + switch (entry->a_type) { + case SMB_ACL_USER: + if (acl_set_qualifier(e, &entry->info.user.uid) != 0) { + DEBUG(1, ("acl_set_qualifiier failed: %s\n", + strerror(errno))); + goto fail; + } + break; + case SMB_ACL_GROUP: + if (acl_set_qualifier(e, &entry->info.group.gid) != 0) { + DEBUG(1, ("acl_set_qualifiier failed: %s\n", + strerror(errno))); + goto fail; + } + break; + default: /* Shut up, compiler! :-) */ + break; + } + + if (smb_acl_set_mode(e, entry->a_perm) != 0) { + goto fail; + } + } + + if (acl_valid(result) != 0) { + char *acl_string = sys_acl_to_text(acl, NULL); + DEBUG(0, ("smb_acl_to_posix: ACL %s is invalid for set (%s)\n", + acl_string, strerror(errno))); + SAFE_FREE(acl_string); + goto fail; + } + + return result; + + fail: + if (result != NULL) { + acl_free(result); + } + return NULL; +} + +/* VFS operations structure */ + +static struct vfs_fn_pointers posixacl_fns = { + .sys_acl_get_fd_fn = posixacl_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = posixacl_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = posixacl_sys_acl_delete_def_fd, +}; + +static_decl_vfs; +NTSTATUS vfs_posixacl_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "posixacl", + &posixacl_fns); +} diff --git a/source3/modules/vfs_posixacl.h b/source3/modules/vfs_posixacl.h new file mode 100644 index 0000000..91166ed --- /dev/null +++ b/source3/modules/vfs_posixacl.h @@ -0,0 +1,38 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set posix acls + Copyright (C) Volker Lendecke 2006 + Copyright (C) Michael Adam 2008 + + 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 . +*/ + +#ifndef __VFS_POSIXACL_H__ +#define __VFS_POSIXACL_H__ + +SMB_ACL_T posixacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx); + +int posixacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl); + +int posixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp); + +#endif + diff --git a/source3/modules/vfs_prealloc.c b/source3/modules/vfs_prealloc.c new file mode 100644 index 0000000..51e9ad0 --- /dev/null +++ b/source3/modules/vfs_prealloc.c @@ -0,0 +1,222 @@ +/* + * XFS preallocation support module. + * + * Copyright (c) James Peach 2006 + * + * 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/filesys.h" +#include "smbd/smbd.h" + +/* Extent preallocation module. + * + * The purpose of this module is to preallocate space on the filesystem when + * we have a good idea of how large files are supposed to be. This lets writes + * proceed without having to allocate new extents and results in better file + * layouts on disk. + * + * Currently only implemented for XFS. This module is based on an original idea + * and implementation by Sebastian Brings. + * + * Tunables. + * + * prealloc: Number of bytes to preallocate for a file with + * the matching extension. + * prealloc:debug Debug level at which to emit messages. + * + * Example. + * + * prealloc:mpeg = 500M # Preallocate *.mpeg to 500 MiB. + */ + +#ifdef HAVE_XFS_LIBXFS_H +#include +#define lock_type xfs_flock64_t +#else +#define lock_type struct flock64 +#endif + +#define MODULE "prealloc" +static int module_debug; + +static int preallocate_space(int fd, off_t size) +{ + int err; + lock_type fl = {0}; + + if (size <= 0) { + return 0; + } + + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = size; + + /* IMPORTANT: We use RESVSP because we want the extents to be + * allocated, but we don't want the allocation to show up in + * st_size or persist after the close(2). + */ + +#if defined(XFS_IOC_RESVSP64) + /* On Linux this comes in via libxfs.h. */ + err = xfsctl(NULL, fd, XFS_IOC_RESVSP64, &fl); +#elif defined(F_RESVSP64) + /* On IRIX, this comes from fcntl.h. */ + err = fcntl(fd, F_RESVSP64, &fl); +#else + err = -1; + errno = ENOSYS; +#endif + if (err) { + DEBUG(module_debug, + ("%s: preallocate failed on fd=%d size=%lld: %s\n", + MODULE, fd, (long long)size, strerror(errno))); + } + + return err; +} + +static int prealloc_connect( + struct vfs_handle_struct * handle, + const char * service, + const char * user) +{ + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + module_debug = lp_parm_int(SNUM(handle->conn), + MODULE, "debug", 100); + + return 0; +} + +static int prealloc_openat(struct vfs_handle_struct* handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int fd; + off_t size = 0; + const char * dot; + char fext[10]; + + if (!(how->flags & (O_CREAT|O_TRUNC))) { + /* Caller is not intending to rewrite the file. Let's not mess + * with the allocation in this case. + */ + goto normal_open; + } + + *fext = '\0'; + dot = strrchr(smb_fname->base_name, '.'); + if (dot && *++dot) { + if (strlen(dot) < sizeof(fext)) { + bool ok; + strncpy(fext, dot, sizeof(fext)); + ok = strlower_m(fext); + if (!ok); + goto normal_open; + } + } + } + + if (*fext == '\0') { + goto normal_open; + } + + /* Syntax for specifying preallocation size is: + * MODULE: = + * where + * is the file extension in lower case + * is a size like 10, 10K, 10M + */ + size = conv_str_size(lp_parm_const_string(SNUM(handle->conn), MODULE, + fext, NULL)); + if (size <= 0) { + /* No need to preallocate this file. */ + goto normal_open; + } + + fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + if (fd < 0) { + return fd; + } + + /* Preallocate only if the file is being created or replaced. Note that + * Samba won't ever pass down O_TRUNC, which is why we have to handle + * truncate calls specially. + */ + if ((how->flags & O_CREAT) || (how->flags & O_TRUNC)) { + off_t * psize; + + psize = VFS_ADD_FSP_EXTENSION(handle, fsp, off_t, NULL); + if (psize == NULL || *psize == -1) { + return fd; + } + + DEBUG(module_debug, + ("%s: preallocating %s (fd=%d) to %lld bytes\n", + MODULE, smb_fname_str_dbg(smb_fname), fd, + (long long)size)); + + *psize = size; + if (preallocate_space(fd, *psize) < 0) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + } + } + + return fd; + +normal_open: + /* We are not creating or replacing a file. Skip the + * preallocation. + */ + DEBUG(module_debug, ("%s: skipping preallocation for %s\n", + MODULE, smb_fname_str_dbg(smb_fname))); + return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); +} + +static int prealloc_ftruncate(vfs_handle_struct * handle, + files_struct * fsp, + off_t offset) +{ + off_t *psize; + int ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); + + /* Maintain the allocated space even in the face of truncates. */ + if ((psize = VFS_FETCH_FSP_EXTENSION(handle, fsp))) { + preallocate_space(fsp_get_io_fd(fsp), *psize); + } + + return ret; +} + +static struct vfs_fn_pointers prealloc_fns = { + .openat_fn = prealloc_openat, + .ftruncate_fn = prealloc_ftruncate, + .connect_fn = prealloc_connect, +}; + +static_decl_vfs; +NTSTATUS vfs_prealloc_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + MODULE, &prealloc_fns); +} diff --git a/source3/modules/vfs_preopen.c b/source3/modules/vfs_preopen.c new file mode 100644 index 0000000..d1327f6 --- /dev/null +++ b/source3/modules/vfs_preopen.c @@ -0,0 +1,757 @@ +/* + * Force a readahead of files by opening them and reading the first bytes + * + * Copyright (C) Volker Lendecke 2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "lib/util/sys_rw.h" +#include "lib/util/sys_rw_data.h" +#include "lib/util/smb_strtox.h" +#include "lib/util_matching.h" +#include "lib/global_contexts.h" + +static int vfs_preopen_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_preopen_debug_level + +#define PREOPEN_MAX_DIGITS 19 +#define PREOPEN_MAX_NUMBER (uint64_t)9999999999999999999ULL + +struct preopen_state; + +struct preopen_helper { + struct preopen_state *state; + struct tevent_fd *fde; + pid_t pid; + int fd; + bool busy; +}; + +struct preopen_state { + int num_helpers; + struct preopen_helper *helpers; + + size_t to_read; /* How many bytes to read in children? */ + int queue_max; + + int queue_dbglvl; /* DBGLVL_DEBUG by default */ + int nomatch_dbglvl; /* DBGLVL_INFO by default */ + int match_dbglvl; /* DBGLVL_INFO by default */ + int reset_dbglvl; /* DBGLVL_INFO by default */ + int nodigits_dbglvl; /* DBGLVL_WARNING by default */ + int founddigits_dbglvl; /* DBGLVL_NOTICE by default */ + int push_dbglvl; /* DBGLVL_NOTICE by default */ + + char *template_fname; /* Filename to be sent to children */ + size_t number_start; /* start offset into "template_fname" */ + int num_digits; /* How many digits is the number long? */ + + uint64_t fnum_sent; /* last fname sent to children */ + + uint64_t fnum_queue_end;/* last fname to be sent, based on + * last open call + preopen:queuelen + */ + + struct samba_path_matching *preopen_names; + ssize_t last_match_idx; /* remember the last match */ +}; + +static void preopen_helper_destroy(struct preopen_helper *c) +{ + int status; + TALLOC_FREE(c->fde); + close(c->fd); + c->fd = -1; + kill(c->pid, SIGKILL); + waitpid(c->pid, &status, 0); + c->busy = true; +} + +static void preopen_queue_run(struct preopen_state *state) +{ + char *pdelimiter; + char delimiter; + + DBG_PREFIX(state->queue_dbglvl, ("START: " + "last_fname[%s] start_offset=%zu num_digits=%d " + "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64" num_helpers=%d\n", + state->template_fname, + state->number_start, + state->num_digits, + state->fnum_sent, + state->fnum_queue_end, + state->num_helpers)); + + pdelimiter = state->template_fname + state->number_start + + state->num_digits; + delimiter = *pdelimiter; + + while (state->fnum_sent < state->fnum_queue_end) { + + ssize_t written; + size_t to_write; + int helper; + + for (helper=0; helpernum_helpers; helper++) { + if (state->helpers[helper].busy) { + continue; + } + break; + } + if (helper == state->num_helpers) { + /* everyone is busy */ + DBG_PREFIX(state->queue_dbglvl, ("BUSY: " + "template_fname[%s] start_offset=%zu num_digits=%d " + "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64"\n", + state->template_fname, + state->number_start, + state->num_digits, + state->fnum_sent, + state->fnum_queue_end)); + return; + } + + snprintf(state->template_fname + state->number_start, + state->num_digits + 1, + "%.*llu", state->num_digits, + (long long unsigned int)(state->fnum_sent + 1)); + *pdelimiter = delimiter; + + DBG_PREFIX(state->push_dbglvl, ( + "PUSH: fullpath[%s] to helper(idx=%d)\n", + state->template_fname, helper)); + + to_write = talloc_get_size(state->template_fname); + written = write_data(state->helpers[helper].fd, + state->template_fname, to_write); + state->helpers[helper].busy = true; + + if (written != to_write) { + preopen_helper_destroy(&state->helpers[helper]); + } + state->fnum_sent += 1; + } + DBG_PREFIX(state->queue_dbglvl, ("END: " + "template_fname[%s] start_offset=%zu num_digits=%d " + "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64"\n", + state->template_fname, + state->number_start, + state->num_digits, + state->fnum_sent, + state->fnum_queue_end)); +} + +static void preopen_helper_readable(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct preopen_helper *helper = (struct preopen_helper *)priv; + struct preopen_state *state = helper->state; + ssize_t nread; + char c; + + if ((flags & TEVENT_FD_READ) == 0) { + return; + } + + nread = read(helper->fd, &c, 1); + if (nread <= 0) { + preopen_helper_destroy(helper); + return; + } + + helper->busy = false; + + DBG_PREFIX(state->queue_dbglvl, ("BEFORE: preopen_queue_run\n")); + preopen_queue_run(state); + DBG_PREFIX(state->queue_dbglvl, ("AFTER: preopen_queue_run\n")); +} + +static int preopen_helpers_destructor(struct preopen_state *c) +{ + int i; + + for (i=0; inum_helpers; i++) { + if (c->helpers[i].fd == -1) { + continue; + } + preopen_helper_destroy(&c->helpers[i]); + } + + return 0; +} + +static bool preopen_helper_open_one(int sock_fd, char **pnamebuf, + size_t to_read, void *filebuf) +{ + char *namebuf = *pnamebuf; + ssize_t nread; + char c = 0; + int fd; + + nread = 0; + + do { + ssize_t thistime; + + thistime = read(sock_fd, namebuf + nread, + talloc_get_size(namebuf) - nread); + if (thistime <= 0) { + return false; + } + + nread += thistime; + + if (nread == talloc_get_size(namebuf)) { + namebuf = talloc_realloc( + NULL, namebuf, char, + talloc_get_size(namebuf) * 2); + if (namebuf == NULL) { + return false; + } + *pnamebuf = namebuf; + } + } while (namebuf[nread - 1] != '\0'); + + fd = open(namebuf, O_RDONLY); + if (fd == -1) { + goto done; + } + nread = read(fd, filebuf, to_read); + close(fd); + + done: + sys_write_v(sock_fd, &c, 1); + return true; +} + +static bool preopen_helper(int fd, size_t to_read) +{ + char *namebuf; + void *readbuf; + + namebuf = talloc_array(NULL, char, 1024); + if (namebuf == NULL) { + return false; + } + + readbuf = talloc_size(NULL, to_read); + if (readbuf == NULL) { + TALLOC_FREE(namebuf); + return false; + } + + while (preopen_helper_open_one(fd, &namebuf, to_read, readbuf)) { + ; + } + + TALLOC_FREE(readbuf); + TALLOC_FREE(namebuf); + return false; +} + +static NTSTATUS preopen_init_helper(struct preopen_helper *h) +{ + int fdpair[2]; + NTSTATUS status; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) { + status = map_nt_error_from_unix(errno); + DEBUG(10, ("socketpair() failed: %s\n", strerror(errno))); + return status; + } + + h->pid = fork(); + + if (h->pid == -1) { + return map_nt_error_from_unix(errno); + } + + if (h->pid == 0) { + close(fdpair[0]); + preopen_helper(fdpair[1], h->state->to_read); + exit(0); + } + close(fdpair[1]); + h->fd = fdpair[0]; + h->fde = tevent_add_fd(global_event_context(), h->state, h->fd, + TEVENT_FD_READ, preopen_helper_readable, h); + if (h->fde == NULL) { + close(h->fd); + h->fd = -1; + return NT_STATUS_NO_MEMORY; + } + h->busy = false; + return NT_STATUS_OK; +} + +static NTSTATUS preopen_init_helpers(TALLOC_CTX *mem_ctx, size_t to_read, + int num_helpers, int queue_max, + struct preopen_state **presult) +{ + struct preopen_state *result; + int i; + + result = talloc(mem_ctx, struct preopen_state); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + result->num_helpers = num_helpers; + result->helpers = talloc_array(result, struct preopen_helper, + num_helpers); + if (result->helpers == NULL) { + TALLOC_FREE(result); + return NT_STATUS_NO_MEMORY; + } + + result->to_read = to_read; + result->queue_max = queue_max; + result->template_fname = NULL; + result->fnum_sent = 0; + result->fnum_queue_end = 0; + + for (i=0; ihelpers[i].state = result; + result->helpers[i].fd = -1; + } + + talloc_set_destructor(result, preopen_helpers_destructor); + + for (i=0; ihelpers[i]); + } + + *presult = result; + return NT_STATUS_OK; +} + +static void preopen_free_helpers(void **ptr) +{ + TALLOC_FREE(*ptr); +} + +static struct preopen_state *preopen_state_get(vfs_handle_struct *handle) +{ + struct preopen_state *state; + NTSTATUS status; + const char *namelist; + + if (SMB_VFS_HANDLE_TEST_DATA(handle)) { + SMB_VFS_HANDLE_GET_DATA(handle, state, struct preopen_state, + return NULL); + return state; + } + + namelist = lp_parm_const_string(SNUM(handle->conn), "preopen", "names", + NULL); + + if (namelist == NULL) { + return NULL; + } + + status = preopen_init_helpers( + NULL, + lp_parm_int(SNUM(handle->conn), "preopen", "num_bytes", 1), + lp_parm_int(SNUM(handle->conn), "preopen", "helpers", 1), + lp_parm_int(SNUM(handle->conn), "preopen", "queuelen", 10), + &state); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + state->queue_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "queue_log_level", DBGLVL_DEBUG); + state->nomatch_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "nomatch_log_level", DBGLVL_INFO); + state->match_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "match_log_level", DBGLVL_INFO); + state->reset_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "reset_log_level", DBGLVL_INFO); + state->nodigits_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "nodigits_log_level", DBGLVL_WARNING); + state->founddigits_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "founddigits_log_level", DBGLVL_NOTICE); + state->push_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "push_log_level", DBGLVL_NOTICE); + + if (lp_parm_bool(SNUM(handle->conn), "preopen", "posix-basic-regex", false)) { + status = samba_path_matching_regex_sub1_create(state, + namelist, + &state->preopen_names); + } else { + status = samba_path_matching_mswild_create(state, + true, /* case_sensitive */ + namelist, + &state->preopen_names); + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state); + return NULL; + } + state->last_match_idx = -1; + + if (!SMB_VFS_HANDLE_TEST_DATA(handle)) { + SMB_VFS_HANDLE_SET_DATA(handle, state, preopen_free_helpers, + struct preopen_state, return NULL); + } + + return state; +} + +static bool preopen_parse_fname(const char *fname, uint64_t *pnum, + size_t *pstart_idx, int *pnum_digits) +{ + char digits[PREOPEN_MAX_DIGITS+1] = { 0, }; + const char *p; + char *q = NULL; + unsigned long long num; + size_t start_idx = 0; + int num_digits = -1; + int error = 0; + + if (*pstart_idx > 0 && *pnum_digits > 0) { + /* + * If the caller knowns + * how many digits are expected + * and on what position, + * we should copy the exact + * subset before we start + * parsing the string into a number + */ + + if (*pnum_digits > PREOPEN_MAX_DIGITS) { + /* + * a string with as much digits as + * PREOPEN_MAX_DIGITS is the longest + * string that would make any sense for us. + * + * The rest will be checked via + * smb_strtoull(). + */ + return false; + } + p = fname + *pstart_idx; + memcpy(digits, p, *pnum_digits); + p = digits; + start_idx = *pstart_idx; + goto parse; + } + + p = strrchr_m(fname, '/'); + if (p == NULL) { + p = fname; + } + + p += 1; + while (p[0] != '\0') { + if (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])) { + break; + } + p += 1; + } + if (*p == '\0') { + /* no digits around */ + return false; + } + + start_idx = (p - fname); + +parse: + num = smb_strtoull(p, (char **)&q, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + return false; + } + + if (num >= PREOPEN_MAX_NUMBER) { + /* overflow */ + return false; + } + + num_digits = (q - p); + + if (*pnum_digits != -1 && *pnum_digits != num_digits) { + /* + * If the caller knowns how many digits + * it expects we should fail if we got something + * different. + */ + return false; + } + + *pnum = num; + *pstart_idx = start_idx; + *pnum_digits = num_digits; + return true; +} + +static uint64_t num_digits_max_value(int num_digits) +{ + uint64_t num_max = 1; + int i; + + if (num_digits < 1) { + return 0; + } + if (num_digits >= PREOPEN_MAX_DIGITS) { + return PREOPEN_MAX_NUMBER; + } + + for (i = 0; i < num_digits; i++) { + num_max *= 10; + } + + /* + * We actually want + * 9 instead of 10 + * 99 instead of 100 + * 999 instead of 1000 + */ + return num_max - 1; +} + +static int preopen_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + const char *dirname = dirfsp->fsp_name->base_name; + struct preopen_state *state; + int res; + uint64_t num; + uint64_t num_max; + NTSTATUS status; + char *new_template = NULL; + size_t new_start = 0; + int new_digits = -1; + size_t new_end = 0; + ssize_t match_idx = -1; + ssize_t replace_start = -1; + ssize_t replace_end = -1; + bool need_reset = false; + + DBG_DEBUG("called on %s\n", smb_fname_str_dbg(smb_fname)); + + state = preopen_state_get(handle); + if (state == NULL) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + res = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + if (res == -1) { + return -1; + } + + if ((how->flags & O_ACCMODE) != O_RDONLY) { + return res; + } + + /* + * Make sure we can later construct an absolute pathname + */ + if (dirname[0] != '/') { + return res; + } + /* + * There's no point in preopen the directory itself. + */ + if (ISDOT(smb_fname->base_name)) { + return res; + } + /* + * If we got an absolute path in + * smb_fname it's most likely the + * reopen via /proc/self/fd/$fd + */ + if (smb_fname->base_name[0] == '/') { + return res; + } + + status = samba_path_matching_check_last_component(state->preopen_names, + smb_fname->base_name, + &match_idx, + &replace_start, + &replace_end); + if (!NT_STATUS_IS_OK(status)) { + match_idx = -1; + } + if (match_idx < 0) { + DBG_PREFIX(state->nomatch_dbglvl, ( + "No match with the preopen:names list by name[%s]\n", + smb_fname_str_dbg(smb_fname))); + return res; + } + + if (replace_start != -1 && replace_end != -1) { + DBG_PREFIX(state->match_dbglvl, ( + "Pattern(idx=%zd) from preopen:names list matched name[%s] hints(start=%zd,end=%zd)\n", + match_idx, smb_fname_str_dbg(smb_fname), replace_start, replace_end)); + } else { + DBG_PREFIX(state->match_dbglvl, ( + "Pattern(idx=%zd) from preopen:names list matched name[%s]\n", + match_idx, smb_fname_str_dbg(smb_fname))); + } + + new_template = talloc_asprintf( + state, "%s/%s", + dirname, smb_fname->base_name); + if (new_template == NULL) { + DBG_ERR("talloc_asprintf(%s/%s) failed\n", + dirname, smb_fname_str_dbg(smb_fname)); + return res; + } + + if (replace_start != -1 && replace_end != -1) { + size_t dirofs = strlen(dirname) + 1; + new_start = dirofs + replace_start; + new_digits = replace_end - replace_start; + } + + if (!preopen_parse_fname(new_template, &num, + &new_start, &new_digits)) { + DBG_PREFIX(state->nodigits_dbglvl, ( + "Pattern(idx=%zd) no valid digits found on fullpath[%s]\n", + match_idx, new_template)); + TALLOC_FREE(new_template); + return res; + } + new_end = new_start + new_digits; + + DBG_PREFIX(state->founddigits_dbglvl, ( + "Pattern(idx=%zd) found num_digits[%d] start_offset[%zd] parsed_num[%"PRIu64"] fullpath[%s]\n", + match_idx, new_digits, new_start, num, new_template)); + + if (state->last_match_idx != match_idx) { + /* + * If a different pattern caused the match + * we better reset the queue + */ + if (state->last_match_idx != -1) { + DBG_PREFIX(state->reset_dbglvl, ("RESET: " + "pattern changed from idx=%zd to idx=%zd by fullpath[%s]\n", + state->last_match_idx, match_idx, new_template)); + } + need_reset = true; + } else if (state->number_start != new_start) { + /* + * If the digits started at a different position + * we better reset the queue + */ + DBG_PREFIX(state->reset_dbglvl, ("RESET: " + "start_offset changed from byte=%zd to byte=%zd by fullpath[%s]\n", + state->number_start, new_start, new_template)); + need_reset = true; + } else if (state->num_digits != new_digits) { + /* + * If number of digits changed + * we better reset the queue + */ + DBG_PREFIX(state->reset_dbglvl, ("RESET: " + "num_digits changed %d to %d by fullpath[%s]\n", + state->num_digits, new_digits, new_template)); + need_reset = true; + } else if (strncmp(state->template_fname, new_template, new_start) != 0) { + /* + * If name before the digits changed + * we better reset the queue + */ + DBG_PREFIX(state->reset_dbglvl, ("RESET: " + "leading pathprefix[%.*s] changed by fullpath[%s]\n", + (int)state->number_start, state->template_fname, new_template)); + need_reset = true; + } else if (strcmp(state->template_fname + new_end, new_template + new_end) != 0) { + /* + * If name after the digits changed + * we better reset the queue + */ + DBG_PREFIX(state->reset_dbglvl, ("RESET: " + "trailing suffix[%s] changed by fullpath[%s]\n", + state->template_fname + new_end, new_template)); + need_reset = true; + } + + if (need_reset) { + /* + * Reset the queue + */ + state->fnum_sent = 0; + state->fnum_queue_end = 0; + state->last_match_idx = match_idx; + } + + TALLOC_FREE(state->template_fname); + state->template_fname = new_template; + state->number_start = new_start; + state->num_digits = new_digits; + + if (num > state->fnum_sent) { + /* + * Helpers were too slow, there's no point in reading + * files in helpers that we already read in the + * parent. + */ + state->fnum_sent = num; + } + + if ((state->fnum_queue_end != 0) /* Something was started earlier */ + && (num < (state->fnum_queue_end - state->queue_max))) { + /* + * "num" is before the queue we announced. This means + * a new run is started. + */ + state->fnum_sent = num; + } + + num_max = num_digits_max_value(state->num_digits); + state->fnum_queue_end = MIN(num_max, num + state->queue_max); + + DBG_PREFIX(state->queue_dbglvl, ("BEFORE: preopen_queue_run\n")); + preopen_queue_run(state); + DBG_PREFIX(state->queue_dbglvl, ("AFTER: preopen_queue_run\n")); + + return res; +} + +static struct vfs_fn_pointers vfs_preopen_fns = { + .openat_fn = preopen_openat, +}; + +static_decl_vfs; +NTSTATUS vfs_preopen_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "preopen", + &vfs_preopen_fns); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + vfs_preopen_debug_level = debug_add_class("preopen"); + if (vfs_preopen_debug_level == -1) { + vfs_preopen_debug_level = DBGC_VFS; + DBG_ERR("Couldn't register custom debugging class!\n"); + } else { + DBG_DEBUG("Debug class number of 'preopen': %d\n", + vfs_preopen_debug_level); + } + + return NT_STATUS_OK; +} diff --git a/source3/modules/vfs_readahead.c b/source3/modules/vfs_readahead.c new file mode 100644 index 0000000..bb31b57 --- /dev/null +++ b/source3/modules/vfs_readahead.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) Jeremy Allison 2007. + * + * 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/filesys.h" +#include "smbd/smbd.h" + +#if defined(HAVE_LINUX_READAHEAD) && ! defined(HAVE_READAHEAD_DECL) +ssize_t readahead(int fd, off_t offset, size_t count); +#endif + +struct readahead_data { + off_t off_bound; + off_t len; + bool didmsg; +}; + +/* + * This module copes with Vista AIO read requests on Linux + * by detecting the initial 0x80000 boundary reads and causing + * the buffer cache to be filled in advance. + */ + +/******************************************************************* + sendfile wrapper that does readahead/posix_fadvise. +*******************************************************************/ + +static ssize_t readahead_sendfile(struct vfs_handle_struct *handle, + int tofd, + files_struct *fromfsp, + const DATA_BLOB *header, + off_t offset, + size_t count) +{ + struct readahead_data *rhd = (struct readahead_data *)handle->data; + + if ( offset % rhd->off_bound == 0) { +#if defined(HAVE_LINUX_READAHEAD) + int err = readahead(fsp_get_io_fd(fromfsp), offset, (size_t)rhd->len); + DEBUG(10,("readahead_sendfile: readahead on fd %u, offset %llu, len %u returned %d\n", + (unsigned int)fsp_get_io_fd(fromfsp), + (unsigned long long)offset, + (unsigned int)rhd->len, + err )); +#elif defined(HAVE_POSIX_FADVISE) + int err = posix_fadvise(fsp_get_io_fd(fromfsp), offset, (off_t)rhd->len, POSIX_FADV_WILLNEED); + DEBUG(10,("readahead_sendfile: posix_fadvise on fd %u, offset %llu, len %u returned %d\n", + (unsigned int)fsp_get_io_fd(fromfsp), + (unsigned long long)offset, + (unsigned int)rhd->len, + err )); +#else + if (!rhd->didmsg) { + DEBUG(0,("readahead_sendfile: no readahead on this platform\n")); + rhd->didmsg = True; + } +#endif + } + return SMB_VFS_NEXT_SENDFILE(handle, + tofd, + fromfsp, + header, + offset, + count); +} + +/******************************************************************* + pread wrapper that does readahead/posix_fadvise. +*******************************************************************/ + +static ssize_t readahead_pread(vfs_handle_struct *handle, + files_struct *fsp, + void *data, + size_t count, + off_t offset) +{ + struct readahead_data *rhd = (struct readahead_data *)handle->data; + + if ( offset % rhd->off_bound == 0) { +#if defined(HAVE_LINUX_READAHEAD) + int err = readahead(fsp_get_io_fd(fsp), offset, (size_t)rhd->len); + DEBUG(10,("readahead_pread: readahead on fd %u, offset %llu, len %u returned %d\n", + (unsigned int)fsp_get_io_fd(fsp), + (unsigned long long)offset, + (unsigned int)rhd->len, + err )); +#elif defined(HAVE_POSIX_FADVISE) + int err = posix_fadvise(fsp_get_io_fd(fsp), offset, (off_t)rhd->len, POSIX_FADV_WILLNEED); + DEBUG(10,("readahead_pread: posix_fadvise on fd %u, offset %llu, len %u returned %d\n", + (unsigned int)fsp_get_io_fd(fsp), + (unsigned long long)offset, + (unsigned int)rhd->len, + err )); +#else + if (!rhd->didmsg) { + DEBUG(0,("readahead_pread: no readahead on this platform\n")); + rhd->didmsg = True; + } +#endif + } + return SMB_VFS_NEXT_PREAD(handle, fsp, data, count, offset); +} + +/******************************************************************* + Directly called from main smbd when freeing handle. +*******************************************************************/ + +static void free_readahead_data(void **pptr) +{ + SAFE_FREE(*pptr); +} + +/******************************************************************* + Allocate the handle specific data so we don't call the expensive + conv_str_size function for each sendfile/pread. +*******************************************************************/ + +static int readahead_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + struct readahead_data *rhd; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + rhd = SMB_MALLOC_P(struct readahead_data); + if (!rhd) { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0,("readahead_connect: out of memory\n")); + return -1; + } + ZERO_STRUCTP(rhd); + + rhd->didmsg = False; + rhd->off_bound = conv_str_size(lp_parm_const_string(SNUM(handle->conn), + "readahead", + "offset", + NULL)); + if (rhd->off_bound == 0) { + rhd->off_bound = 0x80000; + } + rhd->len = conv_str_size(lp_parm_const_string(SNUM(handle->conn), + "readahead", + "length", + NULL)); + if (rhd->len == 0) { + rhd->len = rhd->off_bound; + } + + handle->data = (void *)rhd; + handle->free_data = free_readahead_data; + return 0; +} + +static struct vfs_fn_pointers vfs_readahead_fns = { + .sendfile_fn = readahead_sendfile, + .pread_fn = readahead_pread, + .connect_fn = readahead_connect +}; + +/******************************************************************* + Module initialization boilerplate. +*******************************************************************/ + +static_decl_vfs; +NTSTATUS vfs_readahead_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "readahead", + &vfs_readahead_fns); +} diff --git a/source3/modules/vfs_readonly.c b/source3/modules/vfs_readonly.c new file mode 100644 index 0000000..cde8ef9 --- /dev/null +++ b/source3/modules/vfs_readonly.c @@ -0,0 +1,113 @@ +/* + Unix SMB/Netbios implementation. + Version 1.9. + VFS module to perform read-only limitation based on a time period + Copyright (C) Alexander Bokovoy 2003 + + 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 . + + This work was sponsored by Optifacio Software Services, Inc. +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "getdate.h" + +/* + This module performs a read-only limitation for specified share + (or all of them if it is loaded in a [global] section) based on period + definition in smb.conf. You can stack this module multiple times under + different names to get multiple limit intervals. + + The module uses get_date() function from coreutils' date utility to parse + specified dates according to date(1) rules. Look into info page for date(1) + to understand the syntax. + + The module accepts one parameter: + + readonly: period = "begin date","end date" + + where "begin date" and "end date" are mandatory and should comply with date(1) + syntax for date strings. + + Example: + + readonly: period = "today 14:00","today 15:00" + + Default: + + readonly: period = "today 0:0:0","tomorrow 0:0:0" + + The default covers whole day thus making the share readonly + + */ + +#define MODULE_NAME "readonly" +static int readonly_connect(vfs_handle_struct *handle, + const char *service, + const char *user) +{ + const char *period_def[] = {"today 0:0:0", "tomorrow 0:0:0"}; + + const char **period = lp_parm_string_list(SNUM(handle->conn), + (handle->param ? handle->param : MODULE_NAME), + "period", period_def); + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + if (period && period[0] && period[1]) { + int i; + time_t current_time = time(NULL); + time_t begin_period = get_date(period[0], ¤t_time); + time_t end_period = get_date(period[1], ¤t_time); + + if ((current_time >= begin_period) && (current_time <= end_period)) { + connection_struct *conn = handle->conn; + + handle->conn->read_only = True; + + /* Wipe out the VUID cache. */ + for (i=0; i< VUID_CACHE_SIZE; i++) { + struct vuid_cache_entry *ent = &conn->vuid_cache->array[i]; + ent->vuid = UID_FIELD_INVALID; + TALLOC_FREE(ent->session_info); + ent->read_only = false; + ent->share_access = 0; + } + conn->vuid_cache->next_entry = 0; + } + + return 0; + + } else { + + return 0; + + } +} + + +static struct vfs_fn_pointers vfs_readonly_fns = { + .connect_fn = readonly_connect +}; + +NTSTATUS vfs_readonly_init(TALLOC_CTX *); +NTSTATUS vfs_readonly_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE_NAME, + &vfs_readonly_fns); +} diff --git a/source3/modules/vfs_recycle.c b/source3/modules/vfs_recycle.c new file mode 100644 index 0000000..327a7ee --- /dev/null +++ b/source3/modules/vfs_recycle.c @@ -0,0 +1,737 @@ +/* + * Recycle bin VFS module for Samba. + * + * Copyright (C) 2001, Brandon Stone, Amherst College, . + * Copyright (C) 2002, Jeremy Allison - modified to make a VFS module. + * Copyright (C) 2002, Alexander Bokovoy - cascaded VFS adoption, + * Copyright (C) 2002, Juergen Hasch - added some options. + * Copyright (C) 2002, Simo Sorce + * Copyright (C) 2002, Stefan (metze) Metzmacher + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "auth.h" +#include "source3/lib/substitute.h" + +#define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0) + +static int vfs_recycle_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_recycle_debug_level + +struct recycle_config_data { + const char *repository; + bool keeptree; + bool versions; + bool touch; + bool touch_mtime; + const char **exclude; + const char **exclude_dir; + const char **noversions; + mode_t directory_mode; + mode_t subdir_mode; + off_t minsize; + off_t maxsize; +}; + +static int vfs_recycle_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + struct recycle_config_data *config = NULL; + int ret; + int t; + const char *buff; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) { + return 0; + } + + config = talloc_zero(handle->conn, struct recycle_config_data); + if (config == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + config->repository = lp_parm_const_string(SNUM(handle->conn), + "recycle", + "repository", + ".recycle"); + config->keeptree = lp_parm_bool(SNUM(handle->conn), + "recycle", + "keeptree", + False); + config->versions = lp_parm_bool(SNUM(handle->conn), + "recycle", + "versions", + False); + config->touch = lp_parm_bool(SNUM(handle->conn), + "recycle", + "touch", + False); + config->touch_mtime = lp_parm_bool(SNUM(handle->conn), + "recycle", + "touch_mtime", + False); + config->exclude = lp_parm_string_list(SNUM(handle->conn), + "recycle", + "exclude", + NULL); + config->exclude_dir = lp_parm_string_list(SNUM(handle->conn), + "recycle", + "exclude_dir", + NULL); + config->noversions = lp_parm_string_list(SNUM(handle->conn), + "recycle", + "noversions", + NULL); + config->minsize = conv_str_size(lp_parm_const_string( + SNUM(handle->conn), "recycle", "minsize", NULL)); + config->maxsize = conv_str_size(lp_parm_const_string( + SNUM(handle->conn), "recycle", "maxsize", NULL)); + + buff = lp_parm_const_string(SNUM(handle->conn), + "recycle", + "directory_mode", + NULL); + if (buff != NULL ) { + sscanf(buff, "%o", &t); + } else { + t = S_IRUSR | S_IWUSR | S_IXUSR; + } + config->directory_mode = (mode_t)t; + + buff = lp_parm_const_string(SNUM(handle->conn), + "recycle", + "subdir_mode", + NULL); + if (buff != NULL ) { + sscanf(buff, "%o", &t); + } else { + t = config->directory_mode; + } + config->subdir_mode = (mode_t)t; + + SMB_VFS_HANDLE_SET_DATA( + handle, config, NULL, struct recycle_config_data, return -1); + return 0; +} + +static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname) +{ + struct smb_filename smb_fname = { + .base_name = discard_const_p(char, dname) + }; + + if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) { + if (S_ISDIR(smb_fname.st.st_ex_mode)) { + return True; + } + } + + return False; +} + +static bool recycle_file_exist(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct smb_filename *smb_fname_tmp = NULL; + bool ret = false; + + smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname); + if (smb_fname_tmp == NULL) { + return false; + } + + if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) == 0) { + if (S_ISREG(smb_fname_tmp->st.st_ex_mode)) { + ret = true; + } + } + + TALLOC_FREE(smb_fname_tmp); + return ret; +} + +/** + * Return file size + * @param conn connection + * @param fname file name + * @return size in bytes + **/ +static off_t recycle_get_file_size(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct smb_filename *smb_fname_tmp = NULL; + off_t size; + + smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname); + if (smb_fname_tmp == NULL) { + size = (off_t)0; + goto out; + } + + if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) != 0) { + DBG_DEBUG("stat for %s returned %s\n", + smb_fname_str_dbg(smb_fname_tmp), strerror(errno)); + size = (off_t)0; + goto out; + } + + size = smb_fname_tmp->st.st_ex_size; + out: + TALLOC_FREE(smb_fname_tmp); + return size; +} + +/** + * Create directory tree + * @param conn connection + * @param dname Directory tree to be created + * @param directory mode + * @param subdirectory mode + * @return Returns True for success + **/ +static bool recycle_create_dir(vfs_handle_struct *handle, + const char *dname, + mode_t dir_mode, + mode_t subdir_mode) +{ + size_t len; + mode_t mode = dir_mode; + char *new_dir = NULL; + char *tmp_str = NULL; + char *token; + char *tok_str; + bool ret = False; + char *saveptr; + + tmp_str = SMB_STRDUP(dname); + ALLOC_CHECK(tmp_str, done); + tok_str = tmp_str; + + len = strlen(dname)+1; + new_dir = (char *)SMB_MALLOC(len + 1); + ALLOC_CHECK(new_dir, done); + *new_dir = '\0'; + if (dname[0] == '/') { + /* Absolute path. */ + if (strlcat(new_dir,"/",len+1) >= len+1) { + goto done; + } + } + + /* Create directory tree if necessary */ + for(token = strtok_r(tok_str, "/", &saveptr); token; + token = strtok_r(NULL, "/", &saveptr)) { + if (strlcat(new_dir, token, len+1) >= len+1) { + goto done; + } + if (recycle_directory_exist(handle, new_dir)) + DEBUG(10, ("recycle: dir %s already exists\n", new_dir)); + else { + struct smb_filename *smb_fname = NULL; + int retval; + + DEBUG(5, ("recycle: creating new dir %s\n", new_dir)); + + smb_fname = synthetic_smb_fname(talloc_tos(), + new_dir, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + goto done; + } + + retval = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + smb_fname, + mode); + if (retval != 0) { + DBG_WARNING("recycle: mkdirat failed " + "for %s with error: %s\n", + new_dir, + strerror(errno)); + TALLOC_FREE(smb_fname); + ret = False; + goto done; + } + TALLOC_FREE(smb_fname); + } + if (strlcat(new_dir, "/", len+1) >= len+1) { + goto done; + } + mode = subdir_mode; + } + + ret = True; +done: + SAFE_FREE(tmp_str); + SAFE_FREE(new_dir); + return ret; +} + +/** + * Check if any of the components of "exclude_list" are contained in path. + * Return True if found + **/ + +static bool matchdirparam(const char **dir_exclude_list, char *path) +{ + char *startp = NULL, *endp = NULL; + + if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL || + *dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') { + return False; + } + + /* + * Walk the components of path, looking for matches with the + * exclude list on each component. + */ + + for (startp = path; startp; startp = endp) { + int i; + + while (*startp == '/') { + startp++; + } + endp = strchr(startp, '/'); + if (endp) { + *endp = '\0'; + } + + for(i=0; dir_exclude_list[i] ; i++) { + if(unix_wild_match(dir_exclude_list[i], startp)) { + /* Repair path. */ + if (endp) { + *endp = '/'; + } + return True; + } + } + + /* Repair path. */ + if (endp) { + *endp = '/'; + } + } + + return False; +} + +/** + * Check if needle is contained in haystack, * and ? patterns are resolved + * @param haystack list of parameters separated by delimimiter character + * @param needle string to be matched exectly to haystack including pattern matching + * @return True if found + **/ +static bool matchparam(const char **haystack_list, const char *needle) +{ + int i; + + if (haystack_list == NULL || haystack_list[0] == NULL || + *haystack_list[0] == '\0' || needle == NULL || *needle == '\0') { + return False; + } + + for(i=0; haystack_list[i] ; i++) { + if(unix_wild_match(haystack_list[i], needle)) { + return True; + } + } + + return False; +} + +/** + * Touch access or modify date + **/ +static void recycle_do_touch(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + bool touch_mtime) +{ + struct smb_filename *smb_fname_tmp = NULL; + struct smb_file_time ft; + int ret, err; + NTSTATUS status; + + init_smb_file_time(&ft); + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + smb_fname->stream_name, + NULL, + smb_fname->twrp, + smb_fname->flags, + &smb_fname_tmp); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("synthetic_pathref for '%s' failed: %s\n", + smb_fname_str_dbg(smb_fname), nt_errstr(status)); + return; + } + + /* atime */ + ft.atime = timespec_current(); + /* mtime */ + ft.mtime = touch_mtime ? ft.atime : smb_fname_tmp->st.st_ex_mtime; + + become_root(); + ret = SMB_VFS_NEXT_FNTIMES(handle, smb_fname_tmp->fsp, &ft); + err = errno; + unbecome_root(); + if (ret == -1 ) { + DEBUG(0, ("recycle: touching %s failed, reason = %s\n", + smb_fname_str_dbg(smb_fname_tmp), strerror(err))); + } + + TALLOC_FREE(smb_fname_tmp); +} + +/** + * Check if file should be recycled + **/ +static int recycle_unlink_internal(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + connection_struct *conn = handle->conn; + struct smb_filename *full_fname = NULL; + char *path_name = NULL; + char *temp_name = NULL; + char *final_name = NULL; + struct smb_filename *smb_fname_final = NULL; + const char *base; + char *repository = NULL; + int i = 1; + off_t file_size; /* space_avail; */ + bool exist; + int rc = -1; + struct recycle_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct recycle_config_data, + return true); + + repository = talloc_sub_full( + NULL, + lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name, + conn->connectpath, + conn->session_info->unix_token->gid, + conn->session_info->unix_info->sanitized_username, + conn->session_info->info->domain_name, + config->repository); + ALLOC_CHECK(repository, done); + /* shouldn't we allow absolute path names here? --metze */ + /* Yes :-). JRA. */ + trim_char(repository, '\0', '/'); + + if(!repository || *(repository) == '\0') { + DEBUG(3, ("recycle: repository path not set, purging %s...\n", + smb_fname_str_dbg(smb_fname))); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + /* we don't recycle the recycle bin... */ + if (strncmp(full_fname->base_name, repository, + strlen(repository)) == 0) { + DEBUG(3, ("recycle: File is within recycling bin, unlinking ...\n")); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + file_size = recycle_get_file_size(handle, full_fname); + /* it is wrong to purge filenames only because they are empty imho + * --- simo + * + if(fsize == 0) { + DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + file_name, + flags); + goto done; + } + */ + + /* FIXME: this is wrong, we should check the whole size of the recycle bin is + * not greater then maxsize, not the size of the single file, also it is better + * to remove older files + */ + if (config->maxsize > 0 && file_size > config->maxsize) { + DBG_NOTICE("File %s exceeds maximum recycle size, " + "purging... \n", + smb_fname_str_dbg(full_fname)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + if (config->minsize > 0 && file_size < config->minsize) { + DBG_NOTICE("File %s lowers minimum recycle size, " + "purging... \n", + smb_fname_str_dbg(full_fname)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + /* FIXME: this is wrong: moving files with rename does not change the disk space + * allocation + * + space_avail = SMB_VFS_NEXT_DISK_FREE(handle, ".", True, &bsize, &dfree, &dsize) * 1024L; + DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size)); + if(space_avail < file_size) { + DEBUG(3, ("recycle: Not enough diskspace, purging file %s\n", file_name)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + file_name, + flags); + goto done; + } + */ + + /* extract filename and path */ + if (!parent_dirname(talloc_tos(), full_fname->base_name, &path_name, &base)) { + rc = -1; + errno = ENOMEM; + goto done; + } + + /* original filename with path */ + DEBUG(10, ("recycle: fname = %s\n", smb_fname_str_dbg(full_fname))); + /* original path */ + DEBUG(10, ("recycle: fpath = %s\n", path_name)); + /* filename without path */ + DEBUG(10, ("recycle: base = %s\n", base)); + + if (matchparam(config->exclude, base)) { + DEBUG(3, ("recycle: file %s is excluded \n", base)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + if (matchdirparam(config->exclude_dir, path_name)) { + DEBUG(3, ("recycle: directory %s is excluded \n", path_name)); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + if (config->keeptree) { + if (asprintf(&temp_name, "%s/%s", repository, path_name) == -1) { + ALLOC_CHECK(temp_name, done); + } + } else { + temp_name = SMB_STRDUP(repository); + } + ALLOC_CHECK(temp_name, done); + + exist = recycle_directory_exist(handle, temp_name); + if (exist) { + DEBUG(10, ("recycle: Directory already exists\n")); + } else { + DEBUG(10, ("recycle: Creating directory %s\n", temp_name)); + if (recycle_create_dir(handle, + temp_name, + config->directory_mode, + config->subdir_mode) == False) + { + DEBUG(3, ("recycle: Could not create directory, " + "purging %s...\n", + smb_fname_str_dbg(full_fname))); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + } + + if (asprintf(&final_name, "%s/%s", temp_name, base) == -1) { + ALLOC_CHECK(final_name, done); + } + + /* Create smb_fname with final base name and orig stream name. */ + smb_fname_final = synthetic_smb_fname(talloc_tos(), + final_name, + full_fname->stream_name, + NULL, + full_fname->twrp, + full_fname->flags); + if (smb_fname_final == NULL) { + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + /* new filename with path */ + DEBUG(10, ("recycle: recycled file name: %s\n", + smb_fname_str_dbg(smb_fname_final))); + + /* check if we should delete file from recycle bin */ + if (recycle_file_exist(handle, smb_fname_final)) { + if (config->versions == False || + matchparam(config->noversions, base) == True) { + DEBUG(3, ("recycle: Removing old file %s from recycle " + "bin\n", smb_fname_str_dbg(smb_fname_final))); + if (SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + smb_fname_final, + flags) != 0) { + DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno))); + } + } + } + + /* rename file we move to recycle bin */ + i = 1; + while (recycle_file_exist(handle, smb_fname_final)) { + SAFE_FREE(final_name); + if (asprintf(&final_name, "%s/Copy #%d of %s", temp_name, i++, base) == -1) { + ALLOC_CHECK(final_name, done); + } + TALLOC_FREE(smb_fname_final->base_name); + smb_fname_final->base_name = talloc_strdup(smb_fname_final, + final_name); + if (smb_fname_final->base_name == NULL) { + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + } + + DEBUG(10, ("recycle: Moving %s to %s\n", smb_fname_str_dbg(full_fname), + smb_fname_str_dbg(smb_fname_final))); + rc = SMB_VFS_NEXT_RENAMEAT(handle, + dirfsp, + smb_fname, + handle->conn->cwd_fsp, + smb_fname_final); + if (rc != 0) { + DEBUG(3, ("recycle: Move error %d (%s), purging file %s " + "(%s)\n", errno, strerror(errno), + smb_fname_str_dbg(full_fname), + smb_fname_str_dbg(smb_fname_final))); + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + goto done; + } + + /* touch access date of moved file */ + if (config->touch || config->touch_mtime) + recycle_do_touch(handle, smb_fname_final, config->touch_mtime); + +done: + TALLOC_FREE(path_name); + SAFE_FREE(temp_name); + SAFE_FREE(final_name); + TALLOC_FREE(full_fname); + TALLOC_FREE(smb_fname_final); + TALLOC_FREE(repository); + return rc; +} + +static int recycle_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + + if (flags & AT_REMOVEDIR) { + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + } else { + ret = recycle_unlink_internal(handle, + dirfsp, + smb_fname, + flags); + } + return ret; +} + +static struct vfs_fn_pointers vfs_recycle_fns = { + .connect_fn = vfs_recycle_connect, + .unlinkat_fn = recycle_unlinkat, +}; + +static_decl_vfs; +NTSTATUS vfs_recycle_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle", + &vfs_recycle_fns); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_recycle_debug_level = debug_add_class("recycle"); + if (vfs_recycle_debug_level == -1) { + vfs_recycle_debug_level = DBGC_VFS; + DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n")); + } else { + DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_shadow_copy.c b/source3/modules/vfs_shadow_copy.c new file mode 100644 index 0000000..62b9291 --- /dev/null +++ b/source3/modules/vfs_shadow_copy.c @@ -0,0 +1,274 @@ +/* + * implementation of an Shadow Copy module + * + * Copyright (C) Stefan Metzmacher 2003-2004 + * + * 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 "smbd/smbd.h" +#include "ntioctl.h" +#include "source3/smbd/dir.h" + +/* + Please read the VFS module Samba-HowTo-Collection. + there's a chapter about this module + + For this share + Z:\ + + the ShadowCopies are in this directories + + Z:\@GMT-2003.08.05-12.00.00\ + Z:\@GMT-2003.08.05-12.01.00\ + Z:\@GMT-2003.08.05-12.02.00\ + + e.g. + + Z:\testfile.txt + Z:\@GMT-2003.08.05-12.02.00\testfile.txt + + or: + + Z:\testdir\testfile.txt + Z:\@GMT-2003.08.05-12.02.00\testdir\testfile.txt + + + Note: Files must differ to be displayed via Windows Explorer! + Directories are always displayed... +*/ + +static int vfs_shadow_copy_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_shadow_copy_debug_level + +#define SHADOW_COPY_PREFIX "@GMT-" +#define SHADOW_COPY_SAMPLE "@GMT-2004.02.18-15.44.00" + +typedef struct { + int pos; + int num; + struct dirent *dirs; +} shadow_copy_Dir; + +static bool shadow_copy_match_name(const char *name) +{ + if (strncmp(SHADOW_COPY_PREFIX,name, sizeof(SHADOW_COPY_PREFIX)-1)==0 && + (strlen(SHADOW_COPY_SAMPLE) == strlen(name))) { + return True; + } + + return False; +} + +static DIR *shadow_copy_fdopendir(vfs_handle_struct *handle, files_struct *fsp, const char *mask, uint32_t attr) +{ + shadow_copy_Dir *dirp; + DIR *p = SMB_VFS_NEXT_FDOPENDIR(handle,fsp,mask,attr); + + if (!p) { + DEBUG(10,("shadow_copy_opendir: SMB_VFS_NEXT_FDOPENDIR() failed for [%s]\n", + smb_fname_str_dbg(fsp->fsp_name))); + return NULL; + } + + dirp = SMB_MALLOC_P(shadow_copy_Dir); + if (!dirp) { + DEBUG(0,("shadow_copy_fdopendir: Out of memory\n")); + SMB_VFS_NEXT_CLOSEDIR(handle,p); + /* We have now closed the fd in fsp. */ + fsp_set_fd(fsp, -1); + return NULL; + } + + ZERO_STRUCTP(dirp); + + while (True) { + struct dirent *d; + + d = SMB_VFS_NEXT_READDIR(handle, fsp, p); + if (d == NULL) { + break; + } + + if (shadow_copy_match_name(d->d_name)) { + DEBUG(8,("shadow_copy_fdopendir: hide [%s]\n",d->d_name)); + continue; + } + + DEBUG(10,("shadow_copy_fdopendir: not hide [%s]\n",d->d_name)); + + dirp->dirs = SMB_REALLOC_ARRAY(dirp->dirs,struct dirent, dirp->num+1); + if (!dirp->dirs) { + DEBUG(0,("shadow_copy_fdopendir: Out of memory\n")); + break; + } + + dirp->dirs[dirp->num++] = *d; + } + + SMB_VFS_NEXT_CLOSEDIR(handle,p); + /* We have now closed the fd in fsp. */ + fsp_set_fd(fsp, -1); + return((DIR *)dirp); +} + +static struct dirent *shadow_copy_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *_dirp) +{ + shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp; + + if (dirp->pos < dirp->num) { + return &(dirp->dirs[dirp->pos++]); + } + + return NULL; +} + +static void shadow_copy_rewinddir(struct vfs_handle_struct *handle, DIR *_dirp) +{ + shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp; + dirp->pos = 0 ; +} + +static int shadow_copy_closedir(vfs_handle_struct *handle, DIR *_dirp) +{ + shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp; + + SAFE_FREE(dirp->dirs); + SAFE_FREE(dirp); + + return 0; +} + +static int shadow_copy_get_shadow_copy_data(vfs_handle_struct *handle, + files_struct *fsp, + struct shadow_copy_data *shadow_copy_data, + bool labels) +{ + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + NTSTATUS status; + struct smb_filename *smb_fname = synthetic_smb_fname(talloc_tos(), + fsp->conn->connectpath, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + errno = ENOMEM; + return -1; + } + + status = OpenDir(talloc_tos(), + handle->conn, + smb_fname, + NULL, + 0, + &dir_hnd); + TALLOC_FREE(smb_fname); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("OpenDir() failed for [%s]\n", fsp->conn->connectpath); + errno = map_errno_from_nt_status(status); + return -1; + } + + shadow_copy_data->num_volumes = 0; + shadow_copy_data->labels = NULL; + + while (True) { + SHADOW_COPY_LABEL *tlabels; + int ret; + + dname = ReadDirName(dir_hnd, &talloced); + if (dname == NULL) { + break; + } + + /* */ + if (!shadow_copy_match_name(dname)) { + DBG_DEBUG("ignore [%s]\n", dname); + TALLOC_FREE(talloced); + continue; + } + + DBG_DEBUG("not ignore [%s]\n", dname); + + if (!labels) { + shadow_copy_data->num_volumes++; + TALLOC_FREE(talloced); + continue; + } + + tlabels = (SHADOW_COPY_LABEL *)TALLOC_REALLOC(shadow_copy_data, + shadow_copy_data->labels, + (shadow_copy_data->num_volumes+1)*sizeof(SHADOW_COPY_LABEL)); + if (tlabels == NULL) { + DEBUG(0,("shadow_copy_get_shadow_copy_data: Out of memory\n")); + TALLOC_FREE(talloced); + TALLOC_FREE(dir_hnd); + return -1; + } + + ret = strlcpy(tlabels[shadow_copy_data->num_volumes], dname, + sizeof(tlabels[shadow_copy_data->num_volumes])); + if (ret != sizeof(tlabels[shadow_copy_data->num_volumes]) - 1) { + DBG_ERR("malformed label %s\n", dname); + TALLOC_FREE(talloced); + TALLOC_FREE(dir_hnd); + return -1; + } + shadow_copy_data->num_volumes++; + + shadow_copy_data->labels = tlabels; + TALLOC_FREE(talloced); + } + + TALLOC_FREE(dir_hnd); + return 0; +} + +static struct vfs_fn_pointers vfs_shadow_copy_fns = { + .fdopendir_fn = shadow_copy_fdopendir, + .readdir_fn = shadow_copy_readdir, + .rewind_dir_fn = shadow_copy_rewinddir, + .closedir_fn = shadow_copy_closedir, + .get_shadow_copy_data_fn = shadow_copy_get_shadow_copy_data, +}; + +static_decl_vfs; +NTSTATUS vfs_shadow_copy_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "shadow_copy", &vfs_shadow_copy_fns); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_shadow_copy_debug_level = debug_add_class("shadow_copy"); + if (vfs_shadow_copy_debug_level == -1) { + vfs_shadow_copy_debug_level = DBGC_VFS; + DEBUG(0, ("%s: Couldn't register custom debugging class!\n", + "vfs_shadow_copy_init")); + } else { + DEBUG(10, ("%s: Debug class number of '%s': %d\n", + "vfs_shadow_copy_init","shadow_copy",vfs_shadow_copy_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c new file mode 100644 index 0000000..c6e6ecd --- /dev/null +++ b/source3/modules/vfs_shadow_copy2.c @@ -0,0 +1,3303 @@ +/* + * shadow_copy2: a shadow copy module (second implementation) + * + * Copyright (C) Andrew Tridgell 2007 (portions taken from shadow_copy2) + * Copyright (C) Ed Plese 2009 + * Copyright (C) Volker Lendecke 2011 + * Copyright (C) Christian Ambach 2011 + * Copyright (C) Michael Adam 2013 + * Copyright (C) Rajesh Joseph 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This is a second implementation of a shadow copy module for exposing + * file system snapshots to windows clients as shadow copies. + * + * See the manual page for documentation. + */ + +#include "includes.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "include/ntioctl.h" +#include "util_tdb.h" +#include "lib/util_path.h" +#include "libcli/security/security.h" +#include "lib/util/tevent_unix.h" + +struct shadow_copy2_config { + char *gmt_format; + bool use_sscanf; + bool use_localtime; + char *snapdir; + char *delimiter; + bool snapdirseverywhere; + bool crossmountpoints; + bool fixinodes; + char *sort_order; + bool snapdir_absolute; + char *mount_point; + char *rel_connectpath; /* share root, relative to a snapshot root */ + char *snapshot_basepath; /* the absolute version of snapdir */ +}; + +/* Data-structure to hold the list of snap entries */ +struct shadow_copy2_snapentry { + char *snapname; + char *time_fmt; + struct shadow_copy2_snapentry *next; + struct shadow_copy2_snapentry *prev; +}; + +struct shadow_copy2_snaplist_info { + struct shadow_copy2_snapentry *snaplist; /* snapshot list */ + regex_t *regex; /* Regex to filter snaps */ + time_t fetch_time; /* snaplist update time */ +}; + +/* + * shadow_copy2 private structure. This structure will be + * used to keep module specific information + */ +struct shadow_copy2_private { + struct shadow_copy2_config *config; + struct shadow_copy2_snaplist_info *snaps; + char *shadow_cwd; /* Absolute $cwd path. */ + /* Absolute connectpath - can vary depending on $cwd. */ + char *shadow_connectpath; + /* talloc'ed realpath return. */ + struct smb_filename *shadow_realpath; +}; + +static int shadow_copy2_get_shadow_copy_data( + vfs_handle_struct *handle, files_struct *fsp, + struct shadow_copy_data *shadow_copy2_data, + bool labels); + +/** + * This function will create a new snapshot list entry and + * return to the caller. This entry will also be added to + * the global snapshot list. + * + * @param[in] priv shadow_copy2 specific data structure + * @return Newly created snapshot entry or NULL on failure + */ +static struct shadow_copy2_snapentry *shadow_copy2_create_snapentry( + struct shadow_copy2_private *priv) +{ + struct shadow_copy2_snapentry *tmpentry = NULL; + + tmpentry = talloc_zero(priv->snaps, struct shadow_copy2_snapentry); + if (tmpentry == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return NULL; + } + + DLIST_ADD(priv->snaps->snaplist, tmpentry); + + return tmpentry; +} + +/** + * This function will delete the entire snaplist and reset + * priv->snaps->snaplist to NULL. + * + * @param[in] priv shadow_copye specific data structure + */ +static void shadow_copy2_delete_snaplist(struct shadow_copy2_private *priv) +{ + struct shadow_copy2_snapentry *tmp = NULL; + + while ((tmp = priv->snaps->snaplist) != NULL) { + DLIST_REMOVE(priv->snaps->snaplist, tmp); + talloc_free(tmp); + } +} + +/** + * Given a timestamp this function searches the global snapshot list + * and returns the complete snapshot directory name saved in the entry. + * + * @param[in] priv shadow_copy2 specific structure + * @param[in] timestamp timestamp corresponding to one of the snapshot + * @param[out] snap_str buffer to copy the actual snapshot name + * @param[in] len length of snap_str buffer + * + * @return Length of actual snapshot name, and -1 on failure + */ +static ssize_t shadow_copy2_saved_snapname(struct shadow_copy2_private *priv, + struct tm *timestamp, + char *snap_str, size_t len) +{ + ssize_t snaptime_len = -1; + struct shadow_copy2_snapentry *entry = NULL; + + snaptime_len = strftime(snap_str, len, GMT_FORMAT, timestamp); + if (snaptime_len == 0) { + DBG_ERR("strftime failed\n"); + return -1; + } + + snaptime_len = -1; + + for (entry = priv->snaps->snaplist; entry; entry = entry->next) { + if (strcmp(entry->time_fmt, snap_str) == 0) { + snaptime_len = snprintf(snap_str, len, "%s", + entry->snapname); + return snaptime_len; + } + } + + snap_str[0] = 0; + return snaptime_len; +} + + +/** + * This function will check if snaplist is updated or not. If snaplist + * is empty then it will create a new list. Each time snaplist is updated + * the time is recorded. If the snapshot time is greater than the snaplist + * update time then chances are we are working on an older list. Then discard + * the old list and fetch a new snaplist. + * + * @param[in] handle VFS handle struct + * @param[in] snap_time time of snapshot + * + * @return true if the list is updated else false + */ +static bool shadow_copy2_update_snaplist(struct vfs_handle_struct *handle, + time_t snap_time) +{ + int ret = -1; + bool snaplist_updated = false; + struct files_struct fsp = {0}; + struct smb_filename smb_fname = {0}; + double seconds = 0.0; + struct shadow_copy2_private *priv = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return false); + + seconds = difftime(snap_time, priv->snaps->fetch_time); + + /* + * Fetch the snapshot list if either the snaplist is empty or the + * required snapshot time is greater than the last fetched snaplist + * time. + */ + if (seconds > 0 || (priv->snaps->snaplist == NULL)) { + smb_fname.base_name = discard_const_p(char, "."); + fsp.fsp_name = &smb_fname; + + ret = shadow_copy2_get_shadow_copy_data(handle, &fsp, + NULL, false); + if (ret == 0) { + snaplist_updated = true; + } else { + DBG_ERR("Failed to get shadow copy data\n"); + } + + } + + return snaplist_updated; +} + +static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str, + size_t **poffsets, + unsigned *pnum_offsets) +{ + unsigned num_offsets; + size_t *offsets; + const char *p; + + num_offsets = 0; + + p = str; + while ((p = strchr(p, '/')) != NULL) { + num_offsets += 1; + p += 1; + } + + offsets = talloc_array(mem_ctx, size_t, num_offsets); + if (offsets == NULL) { + return false; + } + + p = str; + num_offsets = 0; + while ((p = strchr(p, '/')) != NULL) { + offsets[num_offsets] = p-str; + num_offsets += 1; + p += 1; + } + + *poffsets = offsets; + *pnum_offsets = num_offsets; + return true; +} + +/** + * Given a timestamp, build the posix level GMT-tag string + * based on the configurable format. + */ +static ssize_t shadow_copy2_posix_gmt_string(struct vfs_handle_struct *handle, + time_t snapshot, + char *snaptime_string, + size_t len) +{ + struct tm snap_tm; + ssize_t snaptime_len; + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return 0); + + config = priv->config; + + if (config->use_sscanf) { + snaptime_len = snprintf(snaptime_string, + len, + config->gmt_format, + (unsigned long)snapshot); + if (snaptime_len <= 0) { + DEBUG(10, ("snprintf failed\n")); + return -1; + } + } else { + if (config->use_localtime) { + if (localtime_r(&snapshot, &snap_tm) == 0) { + DEBUG(10, ("gmtime_r failed\n")); + return -1; + } + } else { + if (gmtime_r(&snapshot, &snap_tm) == 0) { + DEBUG(10, ("gmtime_r failed\n")); + return -1; + } + } + + if (priv->snaps->regex != NULL) { + snaptime_len = shadow_copy2_saved_snapname(priv, + &snap_tm, snaptime_string, len); + if (snaptime_len >= 0) + return snaptime_len; + + /* + * If we fail to find the snapshot name, chances are + * that we have not updated our snaplist. Make sure the + * snaplist is updated. + */ + if (!shadow_copy2_update_snaplist(handle, snapshot)) { + DBG_DEBUG("shadow_copy2_update_snaplist " + "failed\n"); + return -1; + } + + return shadow_copy2_saved_snapname(priv, + &snap_tm, snaptime_string, len); + } + + snaptime_len = strftime(snaptime_string, + len, + config->gmt_format, + &snap_tm); + if (snaptime_len == 0) { + DEBUG(10, ("strftime failed\n")); + return -1; + } + } + + return snaptime_len; +} + +/** + * Given a timestamp, build the string to insert into a path + * as a path component for creating the local path to the + * snapshot at the given timestamp of the input path. + * + * In the case of a parallel snapdir (specified with an + * absolute path), this is the initial portion of the + * local path of any snapshot file. The complete path is + * obtained by appending the portion of the file's path + * below the share root's mountpoint. + */ +static char *shadow_copy2_insert_string(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + time_t snapshot) +{ + fstring snaptime_string; + ssize_t snaptime_len = 0; + char *result = NULL; + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + config = priv->config; + + snaptime_len = shadow_copy2_posix_gmt_string(handle, + snapshot, + snaptime_string, + sizeof(snaptime_string)); + if (snaptime_len <= 0) { + return NULL; + } + + if (config->snapdir_absolute) { + result = talloc_asprintf(mem_ctx, "%s/%s", + config->snapdir, snaptime_string); + } else { + result = talloc_asprintf(mem_ctx, "/%s/%s", + config->snapdir, snaptime_string); + } + if (result == NULL) { + DBG_WARNING("talloc_asprintf failed\n"); + } + + return result; +} + +/** + * Build the posix snapshot path for the connection + * at the given timestamp, i.e. the absolute posix path + * that contains the snapshot for this file system. + * + * This only applies to classical case, i.e. not + * to the "snapdirseverywhere" mode. + */ +static char *shadow_copy2_snapshot_path(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + time_t snapshot) +{ + fstring snaptime_string; + ssize_t snaptime_len = 0; + char *result = NULL; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + snaptime_len = shadow_copy2_posix_gmt_string(handle, + snapshot, + snaptime_string, + sizeof(snaptime_string)); + if (snaptime_len <= 0) { + return NULL; + } + + result = talloc_asprintf(mem_ctx, "%s/%s", + priv->config->snapshot_basepath, snaptime_string); + if (result == NULL) { + DBG_WARNING("talloc_asprintf failed\n"); + } + + return result; +} + +static char *make_path_absolute(TALLOC_CTX *mem_ctx, + struct shadow_copy2_private *priv, + const char *name) +{ + char *newpath = NULL; + char *abs_path = NULL; + + if (name[0] != '/') { + newpath = talloc_asprintf(mem_ctx, + "%s/%s", + priv->shadow_cwd, + name); + if (newpath == NULL) { + return NULL; + } + name = newpath; + } + abs_path = canonicalize_absolute_path(mem_ctx, name); + TALLOC_FREE(newpath); + return abs_path; +} + +/* Return a $cwd-relative path. */ +static bool make_relative_path(const char *cwd, char *abs_path) +{ + size_t cwd_len = strlen(cwd); + size_t abs_len = strlen(abs_path); + + if (abs_len < cwd_len) { + return false; + } + if (memcmp(abs_path, cwd, cwd_len) != 0) { + return false; + } + /* The cwd_len != 1 case is for $cwd == '/' */ + if (cwd_len != 1 && + abs_path[cwd_len] != '/' && + abs_path[cwd_len] != '\0') + { + return false; + } + if (abs_path[cwd_len] == '/') { + cwd_len++; + } + memmove(abs_path, &abs_path[cwd_len], abs_len + 1 - cwd_len); + return true; +} + +static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, + const char *name, + char *gmt, size_t gmt_len); + +/* + * Check if an incoming filename is already a snapshot converted pathname. + * + * If so, it returns the pathname truncated at the snapshot point which + * will be used as the connectpath. + */ + +static int check_for_converted_path(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + struct shadow_copy2_private *priv, + char *abs_path, + bool *ppath_already_converted, + char **pconnectpath) +{ + size_t snapdirlen = 0; + char *p = strstr_m(abs_path, priv->config->snapdir); + char *q = NULL; + char *connect_path = NULL; + char snapshot[GMT_NAME_LEN+1]; + + *ppath_already_converted = false; + + if (p == NULL) { + /* Must at least contain shadow:snapdir. */ + return 0; + } + + if (priv->config->snapdir[0] == '/' && + p != abs_path) { + /* Absolute shadow:snapdir must be at the start. */ + return 0; + } + + snapdirlen = strlen(priv->config->snapdir); + if (p[snapdirlen] != '/') { + /* shadow:snapdir must end as a separate component. */ + return 0; + } + + if (p > abs_path && p[-1] != '/') { + /* shadow:snapdir must start as a separate component. */ + return 0; + } + + p += snapdirlen; + p++; /* Move past the / */ + + /* + * Need to return up to the next path + * component after the time. + * This will be used as the connectpath. + */ + q = strchr(p, '/'); + if (q == NULL) { + /* + * No next path component. + * Use entire string. + */ + connect_path = talloc_strdup(mem_ctx, + abs_path); + } else { + connect_path = talloc_strndup(mem_ctx, + abs_path, + q - abs_path); + } + if (connect_path == NULL) { + return ENOMEM; + } + + /* + * Point p at the same offset in connect_path as + * it is in abs_path. + */ + + p = &connect_path[p - abs_path]; + + /* + * Now ensure there is a time string at p. + * The SMB-format @GMT-token string is returned + * in snapshot. + */ + + if (!shadow_copy2_snapshot_to_gmt(handle, + p, + snapshot, + sizeof(snapshot))) { + TALLOC_FREE(connect_path); + return 0; + } + + if (pconnectpath != NULL) { + *pconnectpath = connect_path; + } + + *ppath_already_converted = true; + + DBG_DEBUG("path |%s| is already converted. " + "connect path = |%s|\n", + abs_path, + connect_path); + + return 0; +} + +/** + * This function does two things. + * + * 1). Checks if an incoming filename is already a + * snapshot converted pathname. + * If so, it returns the pathname truncated + * at the snapshot point which will be used + * as the connectpath, and then does an early return. + * + * 2). Checks if an incoming filename contains an + * SMB-layer @GMT- style timestamp. + * If so, it strips the timestamp, and returns + * both the timestamp and the stripped path + * (making it cwd-relative). + */ + +static bool _shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + time_t *ptimestamp, + char **pstripped, + char **psnappath, + bool *_already_converted, + const char *function) +{ + char *stripped = NULL; + struct shadow_copy2_private *priv; + char *abs_path = NULL; + bool ret = true; + bool already_converted = false; + int err = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return false); + + DBG_DEBUG("[from %s()] Path '%s'\n", + function, smb_fname_str_dbg(smb_fname)); + + if (_already_converted != NULL) { + *_already_converted = false; + } + + abs_path = make_path_absolute(mem_ctx, priv, smb_fname->base_name); + if (abs_path == NULL) { + ret = false; + goto out; + } + + DBG_DEBUG("abs path '%s'\n", abs_path); + + err = check_for_converted_path(mem_ctx, + handle, + priv, + abs_path, + &already_converted, + psnappath); + if (err != 0) { + /* error in conversion. */ + ret = false; + goto out; + } + + if (already_converted) { + if (_already_converted != NULL) { + *_already_converted = true; + } + goto out; + } + + if (smb_fname->twrp == 0) { + goto out; + } + + if (ptimestamp != NULL) { + *ptimestamp = nt_time_to_unix(smb_fname->twrp); + } + + if (pstripped != NULL) { + stripped = talloc_strdup(mem_ctx, abs_path); + if (stripped == NULL) { + ret = false; + goto out; + } + + if (smb_fname->base_name[0] != '/') { + ret = make_relative_path(priv->shadow_cwd, stripped); + if (!ret) { + DBG_DEBUG("Path '%s' " + "doesn't start with cwd '%s'\n", + stripped, priv->shadow_cwd); + ret = false; + errno = ENOENT; + goto out; + } + } + *pstripped = stripped; + } + + ret = true; + + out: + TALLOC_FREE(abs_path); + return ret; +} + +#define shadow_copy2_strip_snapshot_internal(mem_ctx, handle, orig_name, \ + ptimestamp, pstripped, psnappath, _already_converted) \ + _shadow_copy2_strip_snapshot_internal((mem_ctx), (handle), (orig_name), \ + (ptimestamp), (pstripped), (psnappath), (_already_converted), \ + __FUNCTION__) + +static bool _shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const struct smb_filename *orig_name, + time_t *ptimestamp, + char **pstripped, + const char *function) +{ + return _shadow_copy2_strip_snapshot_internal(mem_ctx, + handle, + orig_name, + ptimestamp, + pstripped, + NULL, + NULL, + function); +} + +#define shadow_copy2_strip_snapshot(mem_ctx, handle, orig_name, \ + ptimestamp, pstripped) \ + _shadow_copy2_strip_snapshot((mem_ctx), (handle), (orig_name), \ + (ptimestamp), (pstripped), __FUNCTION__) + +static bool _shadow_copy2_strip_snapshot_converted(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const struct smb_filename *orig_name, + time_t *ptimestamp, + char **pstripped, + bool *is_converted, + const char *function) +{ + return _shadow_copy2_strip_snapshot_internal(mem_ctx, + handle, + orig_name, + ptimestamp, + pstripped, + NULL, + is_converted, + function); +} + +#define shadow_copy2_strip_snapshot_converted(mem_ctx, handle, orig_name, \ + ptimestamp, pstripped, is_converted) \ + _shadow_copy2_strip_snapshot_converted((mem_ctx), (handle), (orig_name), \ + (ptimestamp), (pstripped), (is_converted), __FUNCTION__) + +static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx, + vfs_handle_struct *handle) +{ + char *path = talloc_strdup(mem_ctx, handle->conn->connectpath); + dev_t dev; + struct stat st; + char *p; + + if (stat(path, &st) != 0) { + talloc_free(path); + return NULL; + } + + dev = st.st_dev; + + while ((p = strrchr(path, '/')) && p > path) { + *p = 0; + if (stat(path, &st) != 0) { + talloc_free(path); + return NULL; + } + if (st.st_dev != dev) { + *p = '/'; + break; + } + } + + return path; +} + +/** + * Convert from a name as handed in via the SMB layer + * and a timestamp into the local path of the snapshot + * of the provided file at the provided time. + * Also return the path in the snapshot corresponding + * to the file's share root. + */ +static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp, + size_t *snaproot_len) +{ + struct smb_filename converted_fname; + char *result = NULL; + size_t *slashes = NULL; + unsigned num_slashes; + char *path = NULL; + size_t pathlen; + char *insert = NULL; + char *converted = NULL; + size_t insertlen, connectlen = 0; + int saved_errno = 0; + int i; + size_t min_offset; + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + size_t in_share_offset = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + config = priv->config; + + DEBUG(10, ("converting '%s'\n", name)); + + if (!config->snapdirseverywhere) { + int ret; + char *snapshot_path; + + snapshot_path = shadow_copy2_snapshot_path(talloc_tos(), + handle, + timestamp); + if (snapshot_path == NULL) { + goto fail; + } + + if (config->rel_connectpath == NULL) { + converted = talloc_asprintf(mem_ctx, "%s/%s", + snapshot_path, name); + } else { + converted = talloc_asprintf(mem_ctx, "%s/%s/%s", + snapshot_path, + config->rel_connectpath, + name); + } + if (converted == NULL) { + goto fail; + } + + converted_fname = (struct smb_filename) { + .base_name = converted, + }; + + ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname); + DEBUG(10, ("Trying[not snapdirseverywhere] %s: %d (%s)\n", + converted, + ret, ret == 0 ? "ok" : strerror(errno))); + if (ret == 0) { + DEBUG(10, ("Found %s\n", converted)); + result = converted; + converted = NULL; + if (snaproot_len != NULL) { + *snaproot_len = strlen(snapshot_path); + if (config->rel_connectpath != NULL) { + *snaproot_len += + strlen(config->rel_connectpath) + 1; + } + } + goto fail; + } else { + errno = ENOENT; + goto fail; + } + /* never reached ... */ + } + + connectlen = strlen(handle->conn->connectpath); + if (name[0] == 0) { + path = talloc_strdup(mem_ctx, handle->conn->connectpath); + } else { + path = talloc_asprintf( + mem_ctx, "%s/%s", handle->conn->connectpath, name); + } + if (path == NULL) { + errno = ENOMEM; + goto fail; + } + pathlen = talloc_get_size(path)-1; + + if (!shadow_copy2_find_slashes(talloc_tos(), path, + &slashes, &num_slashes)) { + goto fail; + } + + insert = shadow_copy2_insert_string(talloc_tos(), handle, timestamp); + if (insert == NULL) { + goto fail; + } + insertlen = talloc_get_size(insert)-1; + + /* + * Note: We deliberately don't expensively initialize the + * array with talloc_zero here: Putting zero into + * converted[pathlen+insertlen] below is sufficient, because + * in the following for loop, the insert string is inserted + * at various slash places. So the memory up to position + * pathlen+insertlen will always be initialized when the + * converted string is used. + */ + converted = talloc_array(mem_ctx, char, pathlen + insertlen + 1); + if (converted == NULL) { + goto fail; + } + + if (path[pathlen-1] != '/') { + /* + * Append a fake slash to find the snapshot root + */ + size_t *tmp; + tmp = talloc_realloc(talloc_tos(), slashes, + size_t, num_slashes+1); + if (tmp == NULL) { + goto fail; + } + slashes = tmp; + slashes[num_slashes] = pathlen; + num_slashes += 1; + } + + min_offset = 0; + + if (!config->crossmountpoints) { + min_offset = strlen(config->mount_point); + } + + memcpy(converted, path, pathlen+1); + converted[pathlen+insertlen] = '\0'; + + converted_fname = (struct smb_filename) { + .base_name = converted, + }; + + for (i = num_slashes-1; i>=0; i--) { + int ret; + size_t offset; + + offset = slashes[i]; + + if (offset < min_offset) { + errno = ENOENT; + goto fail; + } + + if (offset >= connectlen) { + in_share_offset = offset; + } + + memcpy(converted+offset, insert, insertlen); + + offset += insertlen; + memcpy(converted+offset, path + slashes[i], + pathlen - slashes[i]); + + ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname); + + DEBUG(10, ("Trying[snapdirseverywhere] %s: %d (%s)\n", + converted, + ret, ret == 0 ? "ok" : strerror(errno))); + if (ret == 0) { + /* success */ + if (snaproot_len != NULL) { + *snaproot_len = in_share_offset + insertlen; + } + break; + } + if (errno == ENOTDIR) { + /* + * This is a valid condition: We appended the + * .snapshots/@GMT.. to a file name. Just try + * with the upper levels. + */ + continue; + } + if (errno != ENOENT) { + /* Other problem than "not found" */ + goto fail; + } + } + + if (i >= 0) { + /* + * Found something + */ + DEBUG(10, ("Found %s\n", converted)); + result = converted; + converted = NULL; + } else { + errno = ENOENT; + } +fail: + if (result == NULL) { + saved_errno = errno; + } + TALLOC_FREE(converted); + TALLOC_FREE(insert); + TALLOC_FREE(slashes); + TALLOC_FREE(path); + if (saved_errno != 0) { + errno = saved_errno; + } + return result; +} + +/** + * Convert from a name as handed in via the SMB layer + * and a timestamp into the local path of the snapshot + * of the provided file at the provided time. + */ +static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp) +{ + return shadow_copy2_do_convert(mem_ctx, handle, name, timestamp, NULL); +} + +/* + modify a sbuf return to ensure that inodes in the shadow directory + are different from those in the main directory + */ +static void convert_sbuf(vfs_handle_struct *handle, const char *fname, + SMB_STRUCT_STAT *sbuf) +{ + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return); + + if (priv->config->fixinodes) { + /* some snapshot systems, like GPFS, return the same + device:inode for the snapshot files as the current + files. That breaks the 'restore' button in the shadow copy + GUI, as the client gets a sharing violation. + + This is a crude way of allowing both files to be + open at once. It has a slight chance of inode + number collision, but I can't see a better approach + without significant VFS changes + */ + TDB_DATA key = { .dptr = discard_const_p(uint8_t, fname), + .dsize = strlen(fname) }; + uint32_t shash; + + shash = tdb_jenkins_hash(&key) & 0xFF000000; + if (shash == 0) { + shash = 1; + } + sbuf->st_ex_ino ^= shash; + } +} + +static int shadow_copy2_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + time_t timestamp_src = 0; + time_t timestamp_dst = 0; + char *snappath_src = NULL; + char *snappath_dst = NULL; + + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, + smb_fname_src, + ×tamp_src, NULL, &snappath_src, + NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, + smb_fname_dst, + ×tamp_dst, NULL, &snappath_dst, + NULL)) { + return -1; + } + if (timestamp_src != 0) { + errno = EXDEV; + return -1; + } + if (timestamp_dst != 0) { + errno = EROFS; + return -1; + } + /* + * Don't allow rename on already converted paths. + */ + if (snappath_src != NULL) { + errno = EXDEV; + return -1; + } + if (snappath_dst != NULL) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); +} + +static int shadow_copy2_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + time_t timestamp_old = 0; + time_t timestamp_new = 0; + char *snappath_old = NULL; + char *snappath_new = NULL; + + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), + handle, + link_contents, + ×tamp_old, + NULL, + &snappath_old, + NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), + handle, + new_smb_fname, + ×tamp_new, + NULL, + &snappath_new, + NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + /* + * Don't allow symlinks on already converted paths. + */ + if ((snappath_old != NULL) || (snappath_new != NULL)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); +} + +static int shadow_copy2_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + time_t timestamp_old = 0; + time_t timestamp_new = 0; + char *snappath_old = NULL; + char *snappath_new = NULL; + + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), + handle, + old_smb_fname, + ×tamp_old, + NULL, + &snappath_old, + NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), + handle, + new_smb_fname, + ×tamp_new, + NULL, + &snappath_new, + NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + /* + * Don't allow links on already converted paths. + */ + if ((snappath_old != NULL) || (snappath_new != NULL)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); +} + +static int shadow_copy2_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct shadow_copy2_private *priv = NULL; + time_t timestamp = 0; + char *stripped = NULL; + bool converted = false; + char *abspath = NULL; + char *tmp; + int ret = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return -1); + + if (!shadow_copy2_strip_snapshot_converted(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped, + &converted)) { + return -1; + } + if (timestamp == 0) { + TALLOC_FREE(stripped); + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret != 0) { + return ret; + } + if (!converted) { + return 0; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + smb_fname->base_name); + if (abspath == NULL) { + return -1; + } + + convert_sbuf(handle, abspath, &smb_fname->st); + TALLOC_FREE(abspath); + return 0; + } + + tmp = smb_fname->base_name; + smb_fname->base_name = shadow_copy2_convert( + talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; + return -1; + } + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (ret != 0) { + goto out; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + smb_fname->base_name); + if (abspath == NULL) { + ret = -1; + goto out; + } + + convert_sbuf(handle, abspath, &smb_fname->st); + TALLOC_FREE(abspath); + +out: + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; + + return ret; +} + +static int shadow_copy2_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct shadow_copy2_private *priv = NULL; + time_t timestamp = 0; + char *stripped = NULL; + bool converted = false; + char *abspath = NULL; + char *tmp; + int ret = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return -1); + + if (!shadow_copy2_strip_snapshot_converted(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped, + &converted)) { + return -1; + } + if (timestamp == 0) { + TALLOC_FREE(stripped); + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret != 0) { + return ret; + } + if (!converted) { + return 0; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + smb_fname->base_name); + if (abspath == NULL) { + return -1; + } + + convert_sbuf(handle, abspath, &smb_fname->st); + TALLOC_FREE(abspath); + return 0; + } + + tmp = smb_fname->base_name; + smb_fname->base_name = shadow_copy2_convert( + talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; + return -1; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret != 0) { + goto out; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + smb_fname->base_name); + if (abspath == NULL) { + ret = -1; + goto out; + } + + convert_sbuf(handle, abspath, &smb_fname->st); + TALLOC_FREE(abspath); + +out: + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; + + return ret; +} + +static int shadow_copy2_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct shadow_copy2_private *priv = NULL; + time_t timestamp = 0; + struct smb_filename *orig_smb_fname = NULL; + struct smb_filename vss_smb_fname; + struct smb_filename *orig_base_smb_fname = NULL; + struct smb_filename vss_base_smb_fname; + char *stripped = NULL; + char *abspath = NULL; + bool converted = false; + bool ok; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return -1); + + ok = shadow_copy2_strip_snapshot_converted(talloc_tos(), + handle, + fsp->fsp_name, + ×tamp, + &stripped, + &converted); + if (!ok) { + return -1; + } + + if (timestamp == 0) { + TALLOC_FREE(stripped); + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret != 0) { + return ret; + } + if (!converted) { + return 0; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + fsp->fsp_name->base_name); + if (abspath == NULL) { + return -1; + } + + convert_sbuf(handle, abspath, sbuf); + TALLOC_FREE(abspath); + return 0; + } + + vss_smb_fname = *fsp->fsp_name; + vss_smb_fname.base_name = shadow_copy2_convert(talloc_tos(), + handle, + stripped, + timestamp); + TALLOC_FREE(stripped); + if (vss_smb_fname.base_name == NULL) { + return -1; + } + + orig_smb_fname = fsp->fsp_name; + fsp->fsp_name = &vss_smb_fname; + + if (fsp_is_alternate_stream(fsp)) { + vss_base_smb_fname = *fsp->base_fsp->fsp_name; + vss_base_smb_fname.base_name = vss_smb_fname.base_name; + orig_base_smb_fname = fsp->base_fsp->fsp_name; + fsp->base_fsp->fsp_name = &vss_base_smb_fname; + } + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret != 0) { + goto out; + } + + abspath = make_path_absolute(talloc_tos(), + priv, + fsp->fsp_name->base_name); + if (abspath == NULL) { + ret = -1; + goto out; + } + + convert_sbuf(handle, abspath, sbuf); + TALLOC_FREE(abspath); + +out: + fsp->fsp_name = orig_smb_fname; + if (fsp_is_alternate_stream(fsp)) { + fsp->base_fsp->fsp_name = orig_base_smb_fname; + } + + return ret; +} + +static int shadow_copy2_fstatat( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + struct shadow_copy2_private *priv = NULL; + struct smb_filename *smb_fname = NULL; + time_t timestamp = 0; + char *stripped = NULL; + char *abspath = NULL; + bool converted = false; + int ret; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return -1); + + smb_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname_in); + if (smb_fname == NULL) { + errno = ENOMEM; + return -1; + } + + ok = shadow_copy2_strip_snapshot_converted(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped, + &converted); + if (!ok) { + return -1; + } + if (timestamp == 0) { + TALLOC_FREE(stripped); + ret = SMB_VFS_NEXT_FSTATAT( + handle, dirfsp, smb_fname_in, sbuf, flags); + if (ret != 0) { + return ret; + } + if (!converted) { + return 0; + } + + abspath = make_path_absolute( + talloc_tos(), priv, smb_fname->base_name); + if (abspath == NULL) { + errno = ENOMEM; + return -1; + } + + convert_sbuf(handle, abspath, sbuf); + TALLOC_FREE(abspath); + return 0; + } + + smb_fname->base_name = shadow_copy2_convert( + smb_fname, handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (smb_fname->base_name == NULL) { + TALLOC_FREE(smb_fname); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_FSTATAT(handle, + dirfsp, + smb_fname, + sbuf, + flags); + if (ret != 0) { + int saved_errno = errno; + TALLOC_FREE(smb_fname); + errno = saved_errno; + return -1; + } + + abspath = make_path_absolute( + talloc_tos(), priv, smb_fname->base_name); + if (abspath == NULL) { + TALLOC_FREE(smb_fname); + errno = ENOMEM; + return -1; + } + + convert_sbuf(handle, abspath, sbuf); + TALLOC_FREE(abspath); + + TALLOC_FREE(smb_fname); + + return 0; +} + +static struct smb_filename *shadow_copy2_openat_name( + TALLOC_CTX *mem_ctx, + const struct files_struct *dirfsp, + const struct files_struct *fsp, + const struct smb_filename *smb_fname_in) +{ + struct smb_filename *result = NULL; + + if (fsp->base_fsp != NULL) { + struct smb_filename *base_fname = fsp->base_fsp->fsp_name; + + if (smb_fname_in->base_name[0] == '/') { + /* + * Special-case stream names from streams_depot + */ + result = cp_smb_filename(mem_ctx, smb_fname_in); + } else { + + SMB_ASSERT(is_named_stream(smb_fname_in)); + + result = synthetic_smb_fname(mem_ctx, + base_fname->base_name, + smb_fname_in->stream_name, + &smb_fname_in->st, + smb_fname_in->twrp, + smb_fname_in->flags); + } + } else { + result = full_path_from_dirfsp_atname( + mem_ctx, dirfsp, smb_fname_in); + } + + return result; +} + +static int shadow_copy2_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + struct files_struct *fsp, + const struct vfs_open_how *_how) +{ + struct vfs_open_how how = *_how; + struct smb_filename *smb_fname = NULL; + time_t timestamp = 0; + char *stripped = NULL; + int saved_errno = 0; + int ret; + bool ok; + + if (how.resolve != 0) { + errno = ENOSYS; + return -1; + } + + smb_fname = shadow_copy2_openat_name( + talloc_tos(), dirfsp, fsp, smb_fname_in); + if (smb_fname == NULL) { + errno = ENOMEM; + return -1; + } + + ok = shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped); + if (!ok) { + TALLOC_FREE(smb_fname); + return -1; + } + if (timestamp == 0) { + TALLOC_FREE(stripped); + TALLOC_FREE(smb_fname); + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname_in, + fsp, + &how); + } + + smb_fname->base_name = shadow_copy2_convert(smb_fname, + handle, + stripped, + timestamp); + if (smb_fname->base_name == NULL) { + int err = errno; + TALLOC_FREE(stripped); + TALLOC_FREE(smb_fname); + errno = err; + return -1; + } + TALLOC_FREE(stripped); + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + if (ret == -1) { + saved_errno = errno; + } + + TALLOC_FREE(smb_fname); + + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int shadow_copy2_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + time_t timestamp = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); +} + +static int shadow_copy2_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + time_t timestamp = 0; + const struct smb_filename *smb_fname = NULL; + + smb_fname = fsp->fsp_name; + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); +} + +static void store_cwd_data(vfs_handle_struct *handle, + const char *connectpath) +{ + struct shadow_copy2_private *priv = NULL; + struct smb_filename *cwd_fname = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return); + + TALLOC_FREE(priv->shadow_cwd); + cwd_fname = SMB_VFS_NEXT_GETWD(handle, talloc_tos()); + if (cwd_fname == NULL) { + smb_panic("getwd failed\n"); + } + DBG_DEBUG("shadow cwd = %s\n", cwd_fname->base_name); + priv->shadow_cwd = talloc_strdup(priv, cwd_fname->base_name); + TALLOC_FREE(cwd_fname); + if (priv->shadow_cwd == NULL) { + smb_panic("talloc failed\n"); + } + TALLOC_FREE(priv->shadow_connectpath); + if (connectpath) { + DBG_DEBUG("shadow connectpath = %s\n", connectpath); + priv->shadow_connectpath = talloc_strdup(priv, connectpath); + if (priv->shadow_connectpath == NULL) { + smb_panic("talloc failed\n"); + } + } +} + +static int shadow_copy2_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char *stripped = NULL; + char *snappath = NULL; + int ret = -1; + int saved_errno = 0; + char *conv = NULL; + size_t rootpath_len = 0; + struct smb_filename *conv_smb_fname = NULL; + + if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped, + &snappath, + NULL)) { + return -1; + } + if (stripped != NULL) { + conv = shadow_copy2_do_convert(talloc_tos(), + handle, + stripped, + timestamp, + &rootpath_len); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + } else { + conv_smb_fname = cp_smb_filename(talloc_tos(), smb_fname); + } + + if (conv_smb_fname == NULL) { + TALLOC_FREE(conv); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_CHDIR(handle, conv_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + + if (ret == 0) { + if (conv != NULL && rootpath_len != 0) { + conv[rootpath_len] = '\0'; + } else if (snappath != 0) { + TALLOC_FREE(conv); + conv = snappath; + } + store_cwd_data(handle, conv); + } + + TALLOC_FREE(stripped); + TALLOC_FREE(conv); + TALLOC_FREE(conv_smb_fname); + + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int shadow_copy2_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + time_t timestamp = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + fsp->fsp_name, + ×tamp, + NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int shadow_copy2_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + time_t timestamp = 0; + char *stripped = NULL; + int saved_errno = 0; + int ret; + struct smb_filename *full_fname = NULL; + struct smb_filename *conv = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + full_fname, + ×tamp, + &stripped)) { + TALLOC_FREE(full_fname); + return -1; + } + + if (timestamp == 0) { + TALLOC_FREE(full_fname); + TALLOC_FREE(stripped); + return SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + } + conv = cp_smb_filename(talloc_tos(), full_fname); + if (conv == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(stripped); + errno = ENOMEM; + return -1; + } + TALLOC_FREE(full_fname); + conv->base_name = shadow_copy2_convert( + conv, handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv->base_name == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + conv, + buf, + bufsiz); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(conv); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int shadow_copy2_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + time_t timestamp = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); +} + +static struct smb_filename *shadow_copy2_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char *stripped = NULL; + struct smb_filename *result_fname = NULL; + struct smb_filename *conv_fname = NULL; + int saved_errno = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, &stripped)) { + goto done; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + } + + conv_fname = cp_smb_filename(talloc_tos(), smb_fname); + if (conv_fname == NULL) { + goto done; + } + conv_fname->base_name = shadow_copy2_convert( + conv_fname, handle, stripped, timestamp); + if (conv_fname->base_name == NULL) { + goto done; + } + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, conv_fname); + +done: + if (result_fname == NULL) { + saved_errno = errno; + } + TALLOC_FREE(conv_fname); + TALLOC_FREE(stripped); + if (saved_errno != 0) { + errno = saved_errno; + } + return result_fname; +} + +/** + * Check whether a given directory contains a + * snapshot directory as direct subdirectory. + * If yes, return the path of the snapshot-subdir, + * otherwise return NULL. + */ +static char *have_snapdir(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *path) +{ + struct smb_filename smb_fname; + int ret; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + smb_fname = (struct smb_filename) { + .base_name = talloc_asprintf( + mem_ctx, "%s/%s", path, priv->config->snapdir), + }; + if (smb_fname.base_name == NULL) { + return NULL; + } + + ret = SMB_VFS_NEXT_STAT(handle, &smb_fname); + if ((ret == 0) && (S_ISDIR(smb_fname.st.st_ex_mode))) { + return smb_fname.base_name; + } + TALLOC_FREE(smb_fname.base_name); + return NULL; +} + +/** + * Find the snapshot directory (if any) for the given + * filename (which is relative to the share). + */ +static const char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + char *path, *p; + const char *snapdir; + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + config = priv->config; + + /* + * If the non-snapdisrseverywhere mode, we should not search! + */ + if (!config->snapdirseverywhere) { + return config->snapshot_basepath; + } + + path = talloc_asprintf(mem_ctx, "%s/%s", + handle->conn->connectpath, + smb_fname->base_name); + if (path == NULL) { + return NULL; + } + + snapdir = have_snapdir(handle, talloc_tos(), path); + if (snapdir != NULL) { + TALLOC_FREE(path); + return snapdir; + } + + while ((p = strrchr(path, '/')) && (p > path)) { + + p[0] = '\0'; + + snapdir = have_snapdir(handle, talloc_tos(), path); + if (snapdir != NULL) { + TALLOC_FREE(path); + return snapdir; + } + } + TALLOC_FREE(path); + return NULL; +} + +static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, + const char *name, + char *gmt, size_t gmt_len) +{ + struct tm timestamp = { .tm_sec = 0, }; + time_t timestamp_t; + unsigned long int timestamp_long; + const char *fmt; + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + char *tmpstr = NULL; + char *tmp = NULL; + bool converted = false; + int ret = -1; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + config = priv->config; + + fmt = config->gmt_format; + + /* + * If regex is provided, then we will have to parse the + * filename which will contain both the prefix and the time format. + * e.g. + */ + if (priv->snaps->regex != NULL) { + tmpstr = talloc_strdup(talloc_tos(), name); + /* point "name" to the time format */ + name = strstr(name, priv->config->delimiter); + if (name == NULL) { + goto done; + } + /* Extract the prefix */ + tmp = strstr(tmpstr, priv->config->delimiter); + if (tmp == NULL) { + goto done; + } + *tmp = '\0'; + + /* Parse regex */ + ret = regexec(priv->snaps->regex, tmpstr, 0, NULL, 0); + if (ret) { + DBG_DEBUG("shadow_copy2_snapshot_to_gmt: " + "no regex match for %s\n", tmpstr); + goto done; + } + } + + if (config->use_sscanf) { + if (sscanf(name, fmt, ×tamp_long) != 1) { + DEBUG(10, ("shadow_copy2_snapshot_to_gmt: " + "no sscanf match %s: %s\n", + fmt, name)); + goto done; + } + timestamp_t = timestamp_long; + gmtime_r(×tamp_t, ×tamp); + } else { + if (strptime(name, fmt, ×tamp) == NULL) { + DEBUG(10, ("shadow_copy2_snapshot_to_gmt: " + "no match %s: %s\n", + fmt, name)); + goto done; + } + DEBUG(10, ("shadow_copy2_snapshot_to_gmt: match %s: %s\n", + fmt, name)); + + if (config->use_localtime) { + timestamp.tm_isdst = -1; + timestamp_t = mktime(×tamp); + gmtime_r(×tamp_t, ×tamp); + } + } + + strftime(gmt, gmt_len, GMT_FORMAT, ×tamp); + converted = true; + +done: + TALLOC_FREE(tmpstr); + return converted; +} + +static int shadow_copy2_label_cmp_asc(const void *x, const void *y) +{ + return strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL)); +} + +static int shadow_copy2_label_cmp_desc(const void *x, const void *y) +{ + return -strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL)); +} + +/* + sort the shadow copy data in ascending or descending order + */ +static void shadow_copy2_sort_data(vfs_handle_struct *handle, + struct shadow_copy_data *shadow_copy2_data) +{ + int (*cmpfunc)(const void *, const void *); + const char *sort; + struct shadow_copy2_private *priv; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return); + + sort = priv->config->sort_order; + if (sort == NULL) { + return; + } + + if (strcmp(sort, "asc") == 0) { + cmpfunc = shadow_copy2_label_cmp_asc; + } else if (strcmp(sort, "desc") == 0) { + cmpfunc = shadow_copy2_label_cmp_desc; + } else { + return; + } + + if (shadow_copy2_data && shadow_copy2_data->num_volumes > 0 && + shadow_copy2_data->labels) + { + TYPESAFE_QSORT(shadow_copy2_data->labels, + shadow_copy2_data->num_volumes, + cmpfunc); + } +} + +static int shadow_copy2_get_shadow_copy_data( + vfs_handle_struct *handle, files_struct *fsp, + struct shadow_copy_data *shadow_copy2_data, + bool labels) +{ + DIR *p = NULL; + const char *snapdir; + struct smb_filename *snapdir_smb_fname = NULL; + struct files_struct *dirfsp = NULL; + struct files_struct *fspcwd = NULL; + struct dirent *d; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + struct shadow_copy2_private *priv = NULL; + struct shadow_copy2_snapentry *tmpentry = NULL; + bool get_snaplist = false; + struct vfs_open_how how = { + .flags = O_RDONLY, .mode = 0, + }; + int fd; + int ret = -1; + NTSTATUS status; + int saved_errno = 0; + + snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle, fsp->fsp_name); + if (snapdir == NULL) { + DEBUG(0,("shadow:snapdir not found for %s in get_shadow_copy_data\n", + handle->conn->connectpath)); + errno = EINVAL; + goto done; + } + + snapdir_smb_fname = synthetic_smb_fname(talloc_tos(), + snapdir, + NULL, + NULL, + 0, + fsp->fsp_name->flags); + if (snapdir_smb_fname == NULL) { + errno = ENOMEM; + goto done; + } + + status = create_internal_dirfsp(handle->conn, + snapdir_smb_fname, + &dirfsp); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("create_internal_dir_fsp() failed for '%s'" + " - %s\n", snapdir, nt_errstr(status)); + errno = ENOSYS; + goto done; + } + + status = vfs_at_fspcwd(talloc_tos(), handle->conn, &fspcwd); + if (!NT_STATUS_IS_OK(status)) { + errno = ENOMEM; + goto done; + } + +#ifdef O_DIRECTORY + how.flags |= O_DIRECTORY; +#endif + + fd = SMB_VFS_NEXT_OPENAT(handle, + fspcwd, + snapdir_smb_fname, + dirfsp, + &how); + if (fd == -1) { + DBG_WARNING("SMB_VFS_NEXT_OPEN failed for '%s'" + " - %s\n", snapdir, strerror(errno)); + errno = ENOSYS; + goto done; + } + fsp_set_fd(dirfsp, fd); + + /* Now we have the handle, check access here. */ + status = smbd_check_access_rights_fsp(fspcwd, + dirfsp, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("user does not have list permission " + "on snapdir %s\n", + fsp_str_dbg(dirfsp)); + errno = EACCES; + goto done; + } + + p = SMB_VFS_NEXT_FDOPENDIR(handle, dirfsp, NULL, 0); + if (!p) { + DBG_NOTICE("shadow_copy2: SMB_VFS_NEXT_FDOPENDIR() failed for '%s'" + " - %s\n", snapdir, strerror(errno)); + errno = ENOSYS; + goto done; + } + + if (shadow_copy2_data != NULL) { + shadow_copy2_data->num_volumes = 0; + shadow_copy2_data->labels = NULL; + } + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + goto done); + + /* + * Normally this function is called twice once with labels = false and + * then with labels = true. When labels is false it will return the + * number of volumes so that the caller can allocate memory for that + * many labels. Therefore to eliminate snaplist both the times it is + * good to check if labels is set or not. + * + * shadow_copy2_data is NULL when we only want to update the list and + * don't want any labels. + */ + if ((priv->snaps->regex != NULL) && (labels || shadow_copy2_data == NULL)) { + get_snaplist = true; + /* Reset the global snaplist */ + shadow_copy2_delete_snaplist(priv); + + /* Set the current time as snaplist update time */ + time(&(priv->snaps->fetch_time)); + } + + while ((d = SMB_VFS_NEXT_READDIR(handle, dirfsp, p))) { + char snapshot[GMT_NAME_LEN+1]; + SHADOW_COPY_LABEL *tlabels; + + /* + * ignore names not of the right form in the snapshot + * directory + */ + if (!shadow_copy2_snapshot_to_gmt( + handle, d->d_name, + snapshot, sizeof(snapshot))) { + + DEBUG(6, ("shadow_copy2_get_shadow_copy_data: " + "ignoring %s\n", d->d_name)); + continue; + } + DEBUG(6,("shadow_copy2_get_shadow_copy_data: %s -> %s\n", + d->d_name, snapshot)); + + if (get_snaplist) { + /* + * Create a snap entry for each successful + * pattern match. + */ + tmpentry = shadow_copy2_create_snapentry(priv); + if (tmpentry == NULL) { + DBG_ERR("talloc_zero() failed\n"); + goto done; + } + tmpentry->snapname = talloc_strdup(tmpentry, d->d_name); + tmpentry->time_fmt = talloc_strdup(tmpentry, snapshot); + } + + if (shadow_copy2_data == NULL) { + continue; + } + + if (!labels) { + /* the caller doesn't want the labels */ + shadow_copy2_data->num_volumes++; + continue; + } + + tlabels = talloc_realloc(shadow_copy2_data, + shadow_copy2_data->labels, + SHADOW_COPY_LABEL, + shadow_copy2_data->num_volumes+1); + if (tlabels == NULL) { + DEBUG(0,("shadow_copy2: out of memory\n")); + goto done; + } + + strlcpy(tlabels[shadow_copy2_data->num_volumes], snapshot, + sizeof(*tlabels)); + + shadow_copy2_data->num_volumes++; + shadow_copy2_data->labels = tlabels; + } + + shadow_copy2_sort_data(handle, shadow_copy2_data); + ret = 0; + +done: + if (ret != 0) { + saved_errno = errno; + } + TALLOC_FREE(fspcwd ); + if (p != NULL) { + SMB_VFS_NEXT_CLOSEDIR(handle, p); + p = NULL; + if (dirfsp != NULL) { + /* + * VFS_CLOSEDIR implicitly + * closed the associated fd. + */ + fsp_set_fd(dirfsp, -1); + } + } + if (dirfsp != NULL) { + fd_close(dirfsp); + file_free(NULL, dirfsp); + } + TALLOC_FREE(tmp_ctx); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int shadow_copy2_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *full_fname = NULL; + time_t timestamp = 0; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + full_fname, + ×tamp, + NULL)) { + TALLOC_FREE(full_fname); + return -1; + } + TALLOC_FREE(full_fname); + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); +} + +static int shadow_copy2_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + time_t timestamp = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + fsp->fsp_name, + ×tamp, + NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); +} + +static int shadow_copy2_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *aname, const void *value, + size_t size, int flags) +{ + time_t timestamp = 0; + const struct smb_filename *smb_fname = NULL; + + smb_fname = fsp->fsp_name; + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, + aname, value, size, flags); +} + +static NTSTATUS shadow_copy2_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + time_t timestamp = 0; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return NT_STATUS_NO_MEMORY; + } + if (timestamp != 0) { + return NT_STATUS_MEDIA_WRITE_PROTECTED; + } + return SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + smb_fname, + reflist, + referral_count); +} + +static NTSTATUS shadow_copy2_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + time_t timestamp = 0; + char *stripped = NULL; + struct smb_filename *full_fname = NULL; + struct smb_filename *conv = NULL; + NTSTATUS status; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (!shadow_copy2_strip_snapshot(mem_ctx, + handle, + full_fname, + ×tamp, + &stripped)) { + TALLOC_FREE(full_fname); + return NT_STATUS_NO_MEMORY; + } + if (timestamp == 0) { + TALLOC_FREE(full_fname); + TALLOC_FREE(stripped); + return SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + dirfsp, + smb_fname, + ppreflist, + preferral_count); + } + + conv = cp_smb_filename(mem_ctx, full_fname); + if (conv == NULL) { + TALLOC_FREE(full_fname); + TALLOC_FREE(stripped); + return NT_STATUS_NO_MEMORY; + } + TALLOC_FREE(full_fname); + conv->base_name = shadow_copy2_convert(conv, + handle, + stripped, + timestamp); + TALLOC_FREE(stripped); + if (conv->base_name == NULL) { + TALLOC_FREE(conv); + return NT_STATUS_NO_MEMORY; + } + + status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + handle->conn->cwd_fsp, + conv, + ppreflist, + preferral_count); + + if (NT_STATUS_IS_OK(status)) { + /* Return any stat(2) info. */ + smb_fname->st = conv->st; + } + + TALLOC_FREE(conv); + return status; +} + +static const char *shadow_copy2_connectpath( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in) +{ + time_t timestamp = 0; + char *stripped = NULL; + char *tmp = NULL; + const char *fname = smb_fname_in->base_name; + const struct smb_filename *full = NULL; + struct smb_filename smb_fname = {0}; + struct smb_filename *result_fname = NULL; + char *result = NULL; + char *parent_dir = NULL; + int saved_errno = 0; + size_t rootpath_len = 0; + struct shadow_copy2_private *priv = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return NULL); + + DBG_DEBUG("Calc connect path for [%s]\n", fname); + + if (priv->shadow_connectpath != NULL) { + DBG_DEBUG("cached connect path is [%s]\n", + priv->shadow_connectpath); + return priv->shadow_connectpath; + } + + full = full_path_from_dirfsp_atname( + talloc_tos(), dirfsp, smb_fname_in); + if (full == NULL) { + return NULL; + } + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, full, + ×tamp, &stripped)) { + goto done; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname_in); + } + + tmp = shadow_copy2_do_convert(talloc_tos(), handle, stripped, timestamp, + &rootpath_len); + if (tmp == NULL) { + if (errno != ENOENT) { + goto done; + } + + /* + * If the converted path does not exist, and converting + * the parent yields something that does exist, then + * this path refers to something that has not been + * created yet, relative to the parent path. + * The snapshot finding is relative to the parent. + * (usually snapshots are read/only but this is not + * necessarily true). + * This code also covers getting a wildcard in the + * last component, because this function is called + * prior to sanitizing the path, and in SMB1 we may + * get wildcards in path names. + */ + if (!parent_dirname(talloc_tos(), stripped, &parent_dir, + NULL)) { + errno = ENOMEM; + goto done; + } + + tmp = shadow_copy2_do_convert(talloc_tos(), handle, parent_dir, + timestamp, &rootpath_len); + if (tmp == NULL) { + goto done; + } + } + + DBG_DEBUG("converted path is [%s] root path is [%.*s]\n", tmp, + (int)rootpath_len, tmp); + + tmp[rootpath_len] = '\0'; + smb_fname = (struct smb_filename) { .base_name = tmp }; + + result_fname = SMB_VFS_NEXT_REALPATH(handle, priv, &smb_fname); + if (result_fname == NULL) { + goto done; + } + + /* + * SMB_VFS_NEXT_REALPATH returns a talloc'ed string. + * Don't leak memory. + */ + TALLOC_FREE(priv->shadow_realpath); + priv->shadow_realpath = result_fname; + result = priv->shadow_realpath->base_name; + + DBG_DEBUG("connect path is [%s]\n", result); + +done: + if (result == NULL) { + saved_errno = errno; + } + TALLOC_FREE(tmp); + TALLOC_FREE(stripped); + TALLOC_FREE(parent_dir); + if (saved_errno != 0) { + errno = saved_errno; + } + return result; +} + +static NTSTATUS shadow_copy2_parent_pathname(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname_in, + struct smb_filename **parent_dir_out, + struct smb_filename **atname_out) +{ + time_t timestamp = 0; + char *stripped = NULL; + char *converted_name = NULL; + struct smb_filename *smb_fname = NULL; + struct smb_filename *parent = NULL; + struct smb_filename *atname = NULL; + struct shadow_copy2_private *priv = NULL; + bool ok = false; + bool is_converted = false; + NTSTATUS status = NT_STATUS_OK; + TALLOC_CTX *frame = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + priv, + struct shadow_copy2_private, + return NT_STATUS_INTERNAL_ERROR); + + frame = talloc_stackframe(); + + smb_fname = cp_smb_filename(frame, smb_fname_in); + if (smb_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* First, call the default PARENT_PATHNAME. */ + status = SMB_VFS_NEXT_PARENT_PATHNAME(handle, + frame, + smb_fname, + &parent, + &atname); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + if (parent->twrp == 0) { + /* + * Parent is not a snapshot path, return + * the regular result. + */ + status = NT_STATUS_OK; + goto out; + } + + /* See if we can find a snapshot for the parent. */ + ok = shadow_copy2_strip_snapshot_converted(frame, + handle, + parent, + ×tamp, + &stripped, + &is_converted); + if (!ok) { + status = map_nt_error_from_unix(errno); + goto fail; + } + + if (is_converted) { + /* + * Already found snapshot for parent so wipe + * out the twrp. + */ + parent->twrp = 0; + goto out; + } + + converted_name = shadow_copy2_convert(frame, + handle, + stripped, + timestamp); + + if (converted_name == NULL) { + /* + * Can't find snapshot for parent so wipe + * out the twrp. + */ + parent->twrp = 0; + } + + out: + + *parent_dir_out = talloc_move(ctx, &parent); + if (atname_out != NULL) { + *atname_out = talloc_move(*parent_dir_out, &atname); + } + + fail: + + TALLOC_FREE(frame); + return status; +} + +static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + time_t timestamp = 0; + char *stripped = NULL; + int saved_errno = 0; + char *conv = NULL; + struct smb_filename *conv_smb_fname = NULL; + uint64_t ret = (uint64_t)-1; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped)) { + return (uint64_t)-1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return (uint64_t)-1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + if (conv_smb_fname == NULL) { + TALLOC_FREE(conv); + return (uint64_t)-1; + } + ret = SMB_VFS_NEXT_DISK_FREE(handle, conv_smb_fname, + bsize, dfree, dsize); + if (ret == (uint64_t)-1) { + saved_errno = errno; + } + TALLOC_FREE(conv); + TALLOC_FREE(conv_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int shadow_copy2_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + time_t timestamp = 0; + char *stripped = NULL; + int ret; + int saved_errno = 0; + char *conv; + struct smb_filename *conv_smb_fname = NULL; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, dq); + } + + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + if (conv_smb_fname == NULL) { + TALLOC_FREE(conv); + return -1; + } + ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv_smb_fname, qtype, id, dq); + + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(conv); + TALLOC_FREE(conv_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + + return ret; +} + +static ssize_t shadow_copy2_pwrite(vfs_handle_struct *handle, + files_struct *fsp, + const void *data, + size_t n, + off_t offset) +{ + ssize_t nwritten; + + nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + if (nwritten == -1) { + if (errno == EBADF && fsp->fsp_flags.can_write) { + errno = EROFS; + } + } + + return nwritten; +} + +struct shadow_copy2_pwrite_state { + vfs_handle_struct *handle; + files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void shadow_copy2_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *shadow_copy2_pwrite_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct shadow_copy2_pwrite_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct shadow_copy2_pwrite_state); + if (req == NULL) { + return NULL; + } + state->handle = handle; + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, + ev, + handle, + fsp, + data, + n, + offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, shadow_copy2_pwrite_done, req); + + return req; +} + +static void shadow_copy2_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct shadow_copy2_pwrite_state *state = tevent_req_data( + req, struct shadow_copy2_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + if (state->ret == -1) { + tevent_req_error(req, state->vfs_aio_state.error); + return; + } + + tevent_req_done(req); +} + +static ssize_t shadow_copy2_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct shadow_copy2_pwrite_state *state = tevent_req_data( + req, struct shadow_copy2_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + if ((vfs_aio_state->error == EBADF) && + state->fsp->fsp_flags.can_write) + { + vfs_aio_state->error = EROFS; + errno = EROFS; + } + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int shadow_copy2_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct shadow_copy2_config *config; + struct shadow_copy2_private *priv; + int ret; + const char *snapdir; + const char *snapprefix = NULL; + const char *delimiter; + const char *gmt_format; + const char *sort_order; + const char *basedir = NULL; + const char *snapsharepath = NULL; + const char *mount_point; + + DBG_DEBUG("cnum[%" PRIu32 "], connectpath[%s]\n", + handle->conn->cnum, + handle->conn->connectpath); + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + priv = talloc_zero(handle->conn, struct shadow_copy2_private); + if (priv == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + priv->snaps = talloc_zero(priv, struct shadow_copy2_snaplist_info); + if (priv->snaps == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + config = talloc_zero(priv, struct shadow_copy2_config); + if (config == NULL) { + DEBUG(0, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + priv->config = config; + + gmt_format = lp_parm_const_string(SNUM(handle->conn), + "shadow", "format", + GMT_FORMAT); + config->gmt_format = talloc_strdup(config, gmt_format); + if (config->gmt_format == NULL) { + DEBUG(0, ("talloc_strdup() failed\n")); + errno = ENOMEM; + return -1; + } + + /* config->gmt_format must not contain a path separator. */ + if (strchr(config->gmt_format, '/') != NULL) { + DEBUG(0, ("shadow:format %s must not contain a /" + "character. Unable to initialize module.\n", + config->gmt_format)); + errno = EINVAL; + return -1; + } + + config->use_sscanf = lp_parm_bool(SNUM(handle->conn), + "shadow", "sscanf", false); + + config->use_localtime = lp_parm_bool(SNUM(handle->conn), + "shadow", "localtime", + false); + + snapdir = lp_parm_const_string(SNUM(handle->conn), + "shadow", "snapdir", + ".snapshots"); + config->snapdir = talloc_strdup(config, snapdir); + if (config->snapdir == NULL) { + DEBUG(0, ("talloc_strdup() failed\n")); + errno = ENOMEM; + return -1; + } + + snapprefix = lp_parm_const_string(SNUM(handle->conn), + "shadow", "snapprefix", + NULL); + if (snapprefix != NULL) { + priv->snaps->regex = talloc_zero(priv->snaps, regex_t); + if (priv->snaps->regex == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + /* pre-compute regex rule for matching pattern later */ + ret = regcomp(priv->snaps->regex, snapprefix, 0); + if (ret) { + DBG_ERR("Failed to create regex object\n"); + return -1; + } + } + + delimiter = lp_parm_const_string(SNUM(handle->conn), + "shadow", "delimiter", + "_GMT"); + if (delimiter != NULL) { + priv->config->delimiter = talloc_strdup(priv->config, delimiter); + if (priv->config->delimiter == NULL) { + DBG_ERR("talloc_strdup() failed\n"); + errno = ENOMEM; + return -1; + } + } + + config->snapdirseverywhere = lp_parm_bool(SNUM(handle->conn), + "shadow", + "snapdirseverywhere", + false); + + config->crossmountpoints = lp_parm_bool(SNUM(handle->conn), + "shadow", "crossmountpoints", + false); + + if (config->crossmountpoints && !config->snapdirseverywhere) { + DBG_WARNING("Warning: 'crossmountpoints' depends on " + "'snapdirseverywhere'. Disabling crossmountpoints.\n"); + } + + config->fixinodes = lp_parm_bool(SNUM(handle->conn), + "shadow", "fixinodes", + false); + + sort_order = lp_parm_const_string(SNUM(handle->conn), + "shadow", "sort", "desc"); + config->sort_order = talloc_strdup(config, sort_order); + if (config->sort_order == NULL) { + DEBUG(0, ("talloc_strdup() failed\n")); + errno = ENOMEM; + return -1; + } + + mount_point = lp_parm_const_string(SNUM(handle->conn), + "shadow", "mountpoint", NULL); + if (mount_point != NULL) { + if (mount_point[0] != '/') { + DBG_WARNING("Warning: 'mountpoint' is relative " + "('%s'), but it has to be an absolute " + "path. Ignoring provided value.\n", + mount_point); + mount_point = NULL; + } else { + char *p; + p = strstr(handle->conn->connectpath, mount_point); + if (p != handle->conn->connectpath) { + DBG_WARNING("Warning: the share root (%s) is " + "not a subdirectory of the " + "specified mountpoint (%s). " + "Ignoring provided value.\n", + handle->conn->connectpath, + mount_point); + mount_point = NULL; + } + } + } + + if (mount_point != NULL) { + config->mount_point = talloc_strdup(config, mount_point); + if (config->mount_point == NULL) { + DBG_ERR("talloc_strdup() failed\n"); + return -1; + } + } else { + config->mount_point = shadow_copy2_find_mount_point(config, + handle); + if (config->mount_point == NULL) { + DBG_WARNING("shadow_copy2_find_mount_point " + "of the share root '%s' failed: %s\n", + handle->conn->connectpath, strerror(errno)); + return -1; + } + } + + basedir = lp_parm_const_string(SNUM(handle->conn), + "shadow", "basedir", NULL); + + if (basedir != NULL) { + if (basedir[0] != '/') { + DBG_WARNING("Warning: 'basedir' is " + "relative ('%s'), but it has to be an " + "absolute path. Disabling basedir.\n", + basedir); + basedir = NULL; + } else { + char *p; + p = strstr(basedir, config->mount_point); + if (p != basedir) { + DEBUG(1, ("Warning: basedir (%s) is not a " + "subdirectory of the share root's " + "mount point (%s). " + "Disabling basedir\n", + basedir, config->mount_point)); + basedir = NULL; + } + } + } + + if (config->snapdirseverywhere && basedir != NULL) { + DBG_WARNING("Warning: 'basedir' is incompatible " + "with 'snapdirseverywhere'. Disabling basedir.\n"); + basedir = NULL; + } + + snapsharepath = lp_parm_const_string(SNUM(handle->conn), "shadow", + "snapsharepath", NULL); + if (snapsharepath != NULL) { + if (snapsharepath[0] == '/') { + DBG_WARNING("Warning: 'snapsharepath' is " + "absolute ('%s'), but it has to be a " + "relative path. Disabling snapsharepath.\n", + snapsharepath); + snapsharepath = NULL; + } + if (config->snapdirseverywhere && snapsharepath != NULL) { + DBG_WARNING("Warning: 'snapsharepath' is incompatible " + "with 'snapdirseverywhere'. Disabling " + "snapsharepath.\n"); + snapsharepath = NULL; + } + } + + if (basedir != NULL && snapsharepath != NULL) { + DBG_WARNING("Warning: 'snapsharepath' is incompatible with " + "'basedir'. Disabling snapsharepath\n"); + snapsharepath = NULL; + } + + if (snapsharepath != NULL) { + config->rel_connectpath = talloc_strdup(config, snapsharepath); + if (config->rel_connectpath == NULL) { + DBG_ERR("talloc_strdup() failed\n"); + errno = ENOMEM; + return -1; + } + } + + if (basedir == NULL) { + basedir = config->mount_point; + } + + if (config->rel_connectpath == NULL && + strlen(basedir) < strlen(handle->conn->connectpath)) { + config->rel_connectpath = talloc_strdup(config, + handle->conn->connectpath + strlen(basedir)); + if (config->rel_connectpath == NULL) { + DEBUG(0, ("talloc_strdup() failed\n")); + errno = ENOMEM; + return -1; + } + } + + if (config->snapdir[0] == '/') { + config->snapdir_absolute = true; + + if (config->snapdirseverywhere) { + DBG_WARNING("Warning: An absolute snapdir is " + "incompatible with 'snapdirseverywhere', " + "setting 'snapdirseverywhere' to " + "false.\n"); + config->snapdirseverywhere = false; + } + + if (config->crossmountpoints) { + DBG_WARNING("Warning: 'crossmountpoints' is not " + "supported with an absolute snapdir. " + "Disabling it.\n"); + config->crossmountpoints = false; + } + + config->snapshot_basepath = config->snapdir; + } else { + config->snapshot_basepath = talloc_asprintf(config, "%s/%s", + config->mount_point, config->snapdir); + if (config->snapshot_basepath == NULL) { + DEBUG(0, ("talloc_asprintf() failed\n")); + errno = ENOMEM; + return -1; + } + } + + trim_string(config->mount_point, NULL, "/"); + trim_string(config->rel_connectpath, "/", "/"); + trim_string(config->snapdir, NULL, "/"); + trim_string(config->snapshot_basepath, NULL, "/"); + + DEBUG(10, ("shadow_copy2_connect: configuration:\n" + " share root: '%s'\n" + " mountpoint: '%s'\n" + " rel share root: '%s'\n" + " snapdir: '%s'\n" + " snapprefix: '%s'\n" + " delimiter: '%s'\n" + " snapshot base path: '%s'\n" + " format: '%s'\n" + " use sscanf: %s\n" + " snapdirs everywhere: %s\n" + " cross mountpoints: %s\n" + " fix inodes: %s\n" + " sort order: %s\n" + "", + handle->conn->connectpath, + config->mount_point, + config->rel_connectpath, + config->snapdir, + snapprefix, + config->delimiter, + config->snapshot_basepath, + config->gmt_format, + config->use_sscanf ? "yes" : "no", + config->snapdirseverywhere ? "yes" : "no", + config->crossmountpoints ? "yes" : "no", + config->fixinodes ? "yes" : "no", + config->sort_order + )); + + + SMB_VFS_HANDLE_SET_DATA(handle, priv, + NULL, struct shadow_copy2_private, + return -1); + + return 0; +} + +static struct vfs_fn_pointers vfs_shadow_copy2_fns = { + .connect_fn = shadow_copy2_connect, + .disk_free_fn = shadow_copy2_disk_free, + .get_quota_fn = shadow_copy2_get_quota, + .create_dfs_pathat_fn = shadow_copy2_create_dfs_pathat, + .read_dfs_pathat_fn = shadow_copy2_read_dfs_pathat, + .renameat_fn = shadow_copy2_renameat, + .linkat_fn = shadow_copy2_linkat, + .symlinkat_fn = shadow_copy2_symlinkat, + .stat_fn = shadow_copy2_stat, + .lstat_fn = shadow_copy2_lstat, + .fstat_fn = shadow_copy2_fstat, + .fstatat_fn = shadow_copy2_fstatat, + .openat_fn = shadow_copy2_openat, + .unlinkat_fn = shadow_copy2_unlinkat, + .fchmod_fn = shadow_copy2_fchmod, + .chdir_fn = shadow_copy2_chdir, + .fntimes_fn = shadow_copy2_fntimes, + .readlinkat_fn = shadow_copy2_readlinkat, + .mknodat_fn = shadow_copy2_mknodat, + .realpath_fn = shadow_copy2_realpath, + .get_shadow_copy_data_fn = shadow_copy2_get_shadow_copy_data, + .mkdirat_fn = shadow_copy2_mkdirat, + .fsetxattr_fn = shadow_copy2_fsetxattr, + .fchflags_fn = shadow_copy2_fchflags, + .pwrite_fn = shadow_copy2_pwrite, + .pwrite_send_fn = shadow_copy2_pwrite_send, + .pwrite_recv_fn = shadow_copy2_pwrite_recv, + .connectpath_fn = shadow_copy2_connectpath, + .parent_pathname_fn = shadow_copy2_parent_pathname, +}; + +static_decl_vfs; +NTSTATUS vfs_shadow_copy2_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "shadow_copy2", &vfs_shadow_copy2_fns); +} diff --git a/source3/modules/vfs_shell_snap.c b/source3/modules/vfs_shell_snap.c new file mode 100644 index 0000000..d1b7b8c --- /dev/null +++ b/source3/modules/vfs_shell_snap.c @@ -0,0 +1,201 @@ +/* + * Module for snapshot management using shell callouts + * + * Copyright (C) David Disseldorp 2013-2015 + * + * 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 "include/ntioctl.h" +#include "system/filesys.h" +#include "smbd/smbd.h" + +/* + * Check whether a path can be shadow copied. Return the base volume, allowing + * the caller to determine if multiple paths lie on the same base volume. + */ +static NTSTATUS shell_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + NTSTATUS status; + const char *cmd; + char *cmd_run; + int ret; + TALLOC_CTX *tmp_ctx; + + cmd = lp_parm_const_string(handle->conn->params->service, + "shell_snap", "check path command", ""); + if ((cmd == NULL) || (strlen(cmd) == 0)) { + DEBUG(0, + ("\"shell_snap:check path command\" not configured\n")); + status = NT_STATUS_NOT_SUPPORTED; + goto err_out; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + /* add service path argument */ + cmd_run = talloc_asprintf(tmp_ctx, "%s %s", cmd, service_path); + if (cmd_run == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_tmp_free; + } + + ret = smbrun(cmd_run, NULL, NULL); + if (ret != 0) { + DEBUG(0, ("%s failed with %d\n", cmd_run, ret)); + status = NT_STATUS_NOT_SUPPORTED; + goto err_tmp_free; + } + + /* assume the service path is the base volume */ + *base_volume = talloc_strdup(mem_ctx, service_path); + if (*base_volume == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_tmp_free; + } + status = NT_STATUS_OK; +err_tmp_free: + talloc_free(tmp_ctx); +err_out: + return status; +} + +static NTSTATUS shell_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **base_path, + char **snap_path) +{ + const char *cmd; + char *cmd_run; + char **qlines; + int numlines, ret; + int fd = -1; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + cmd = lp_parm_const_string(handle->conn->params->service, + "shell_snap", "create command", ""); + if ((cmd == NULL) || (strlen(cmd) == 0)) { + DEBUG(1, ("\"shell_snap:create command\" not configured\n")); + status = NT_STATUS_NOT_SUPPORTED; + goto err_out; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + /* add base vol argument */ + cmd_run = talloc_asprintf(tmp_ctx, "%s %s", cmd, base_volume); + if (cmd_run == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_tmp_free; + } + + ret = smbrun(cmd_run, &fd, NULL); + talloc_free(cmd_run); + if (ret != 0) { + if (fd != -1) { + close(fd); + } + status = NT_STATUS_UNSUCCESSFUL; + goto err_tmp_free; + } + + numlines = 0; + qlines = fd_lines_load(fd, &numlines, PATH_MAX + 1, tmp_ctx); + close(fd); + + /* script must return the snapshot path as a single line */ + if ((numlines == 0) || (qlines == NULL) || (qlines[0] == NULL)) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_tmp_free; + } + + *base_path = talloc_strdup(mem_ctx, base_volume); + if (*base_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_tmp_free; + } + *snap_path = talloc_strdup(mem_ctx, qlines[0]); + if (*snap_path == NULL) { + status = NT_STATUS_NO_MEMORY; + talloc_free(*base_path); + goto err_tmp_free; + } + + status = NT_STATUS_OK; +err_tmp_free: + talloc_free(tmp_ctx); +err_out: + return status; +} + +static NTSTATUS shell_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + const char *cmd; + char *cmd_run; + int ret; + + cmd = lp_parm_const_string(handle->conn->params->service, + "shell_snap", "delete command", ""); + if ((cmd == NULL) || (strlen(cmd) == 0)) { + DEBUG(1, ("\"shell_snap:delete command\" not configured\n")); + return NT_STATUS_NOT_SUPPORTED; + } + + /* add base path and snap path arguments */ + cmd_run = talloc_asprintf(mem_ctx, "%s %s %s", + cmd, base_path, snap_path); + if (cmd_run == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = smbrun(cmd_run, NULL, NULL); + talloc_free(cmd_run); + if (ret != 0) { + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static struct vfs_fn_pointers shell_snap_fns = { + .snap_check_path_fn = shell_snap_check_path, + .snap_create_fn = shell_snap_create, + .snap_delete_fn = shell_snap_delete, +}; + +static_decl_vfs; +NTSTATUS vfs_shell_snap_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "shell_snap", &shell_snap_fns); +} diff --git a/source3/modules/vfs_snapper.c b/source3/modules/vfs_snapper.c new file mode 100644 index 0000000..f12a94b --- /dev/null +++ b/source3/modules/vfs_snapper.c @@ -0,0 +1,2647 @@ +/* + * Module for snapshot IO using snapper + * + * Copyright (C) David Disseldorp 2012-2014 + * + * Portions taken from vfs_shadow_copy2.c: + * Copyright (C) Andrew Tridgell 2007 + * Copyright (C) Ed Plese 2009 + * Copyright (C) Volker Lendecke 2011 + * Copyright (C) Christian Ambach 2011 + * Copyright (C) Michael Adam 2013 + * + * 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 +#ifdef HAVE_LINUX_IOCTL_H +#include +#endif +#include +#include +#include +#include "includes.h" +#include "include/ntioctl.h" +#include "include/smb.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/smb_strtox.h" + +#define SNAPPER_SIG_LIST_SNAPS_RSP "a(uquxussa{ss})" +#define SNAPPER_SIG_LIST_CONFS_RSP "a(ssa{ss})" +#define SNAPPER_SIG_CREATE_SNAP_RSP "u" +#define SNAPPER_SIG_DEL_SNAPS_RSP "" +#define SNAPPER_SIG_STRING_DICT "{ss}" + +struct snapper_dict { + char *key; + char *val; +}; + +struct snapper_snap { + uint32_t id; + uint16_t type; + uint32_t pre_id; + int64_t time; + uint32_t creator_uid; + char *desc; + char *cleanup; + uint32_t num_user_data; + struct snapper_dict *user_data; +}; + +struct snapper_conf { + char *name; + char *mnt; + uint32_t num_attrs; + struct snapper_dict *attrs; +}; + +static const struct { + const char *snapper_err_str; + NTSTATUS status; +} snapper_err_map[] = { + { "error.no_permissions", NT_STATUS_ACCESS_DENIED }, +}; + +static NTSTATUS snapper_err_ntstatus_map(const char *snapper_err_str) +{ + int i; + + if (snapper_err_str == NULL) { + return NT_STATUS_UNSUCCESSFUL; + } + for (i = 0; i < ARRAY_SIZE(snapper_err_map); i++) { + if (!strcmp(snapper_err_map[i].snapper_err_str, + snapper_err_str)) { + return snapper_err_map[i].status; + } + } + DEBUG(2, ("no explicit mapping for dbus error: %s\n", snapper_err_str)); + + return NT_STATUS_UNSUCCESSFUL; +} + +/* + * Strings are UTF-8. Other characters must be encoded hexadecimal as "\x??". + * As a consequence "\" must be encoded as "\\". + */ +static NTSTATUS snapper_dbus_str_encode(TALLOC_CTX *mem_ctx, const char *in_str, + char **_out_str) +{ + size_t in_len; + char *out_str; + int i; + int out_off; + int out_len; + + if (in_str == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + in_len = strlen(in_str); + + /* output can be max 4 times the length of @in_str, +1 for terminator */ + out_len = (in_len * 4) + 1; + + out_str = talloc_array(mem_ctx, char, out_len); + if (out_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + out_off = 0; + for (i = 0; i < in_len; i++) { + size_t pushed; + + if (in_str[i] == '\\') { + pushed = snprintf(out_str + out_off, out_len - out_off, + "\\\\"); + } else if ((unsigned char)in_str[i] > 127) { + pushed = snprintf(out_str + out_off, out_len - out_off, + "\\x%02x", (unsigned char)in_str[i]); + } else { + /* regular character */ + *(out_str + out_off) = in_str[i]; + pushed = sizeof(char); + } + if (pushed >= out_len - out_off) { + /* truncated, should never happen */ + talloc_free(out_str); + return NT_STATUS_INTERNAL_ERROR; + } + out_off += pushed; + } + + *(out_str + out_off) = '\0'; + *_out_str = out_str; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_dbus_str_decode(TALLOC_CTX *mem_ctx, const char *in_str, + char **_out_str) +{ + size_t in_len; + char *out_str; + int i; + int out_off; + int out_len; + + if (in_str == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + in_len = strlen(in_str); + + /* output cannot be larger than input, +1 for terminator */ + out_len = in_len + 1; + + out_str = talloc_array(mem_ctx, char, out_len); + if (out_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + out_off = 0; + for (i = 0; i < in_len; i++) { + int j; + char hex_buf[3]; + unsigned int non_ascii_byte; + + if (in_str[i] != '\\') { + out_str[out_off] = in_str[i]; + out_off++; + continue; + } + + i++; + if (in_str[i] == '\\') { + out_str[out_off] = '\\'; + out_off++; + continue; + } else if (in_str[i] != 'x') { + goto err_invalid_src_encoding; + } + + /* non-ASCII, encoded as two hex chars */ + for (j = 0; j < 2; j++) { + i++; + if ((in_str[i] == '\0') || !isxdigit(in_str[i])) { + goto err_invalid_src_encoding; + } + hex_buf[j] = in_str[i]; + } + hex_buf[2] = '\0'; + + sscanf(hex_buf, "%x", &non_ascii_byte); + out_str[out_off] = (unsigned char)non_ascii_byte; + out_off++; + } + + out_str[out_off] = '\0'; + *_out_str = out_str; + + return NT_STATUS_OK; +err_invalid_src_encoding: + DEBUG(0, ("invalid encoding %s\n", in_str)); + return NT_STATUS_INVALID_PARAMETER; +} + +static DBusConnection *snapper_dbus_conn_create(void) +{ + DBusError err; + DBusConnection *dconn; + + dbus_error_init(&err); + + /* + * Always create a new DBus connection, to ensure snapperd detects the + * correct client [E]UID. With dbus_bus_get() it does not! + */ + dconn = dbus_bus_get_private(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err)) { + DEBUG(0, ("dbus connection error: %s\n", err.message)); + dbus_error_free(&err); + } + if (dconn == NULL) { + return NULL; + } + + /* dbus_bus_get_private() sets exit-on-disconnect by default, undo it */ + dbus_connection_set_exit_on_disconnect(dconn, false); + + return dconn; +} + +static void snapper_dbus_conn_destroy(DBusConnection *dconn) +{ + if (dconn == NULL) { + DEBUG(2, ("attempt to destroy NULL dbus connection\n")); + return; + } + + dbus_connection_close(dconn); + dbus_connection_unref(dconn); +} + +/* + * send the message @send_msg over the dbus and wait for a response, return the + * responsee via @recv_msg_out. + * @send_msg is not freed, dbus_message_unref() must be handled by the caller. + */ +static NTSTATUS snapper_dbus_msg_xchng(DBusConnection *dconn, + DBusMessage *send_msg, + DBusMessage **recv_msg_out) +{ + DBusPendingCall *pending; + DBusMessage *recv_msg; + + /* send message and get a handle for a reply */ + if (!dbus_connection_send_with_reply(dconn, send_msg, &pending, -1)) { + return NT_STATUS_NO_MEMORY; + } + if (NULL == pending) { + DEBUG(0, ("dbus msg send failed\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + dbus_connection_flush(dconn); + + /* block until we receive a reply */ + dbus_pending_call_block(pending); + + /* get the reply message */ + recv_msg = dbus_pending_call_steal_reply(pending); + if (recv_msg == NULL) { + DEBUG(0, ("Reply Null\n")); + return NT_STATUS_UNSUCCESSFUL; + } + /* free the pending message handle */ + dbus_pending_call_unref(pending); + *recv_msg_out = recv_msg; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_type_check(DBusMessageIter *iter, + int expected_type) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != expected_type) { + DEBUG(0, ("got type %d, expecting %d\n", + type, expected_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_type_check_get(DBusMessageIter *iter, + int expected_type, + void *val) +{ + NTSTATUS status; + status = snapper_type_check(iter, expected_type); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_get_basic(iter, val); + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_dict_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct snapper_dict *dict_out) + +{ + NTSTATUS status; + DBusMessageIter dct_iter; + char *key_encoded; + char *val_encoded; + + status = snapper_type_check(iter, DBUS_TYPE_DICT_ENTRY); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &dct_iter); + + status = snapper_type_check_get(&dct_iter, DBUS_TYPE_STRING, + &key_encoded); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = snapper_dbus_str_decode(mem_ctx, key_encoded, &dict_out->key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&dct_iter); + status = snapper_type_check_get(&dct_iter, DBUS_TYPE_STRING, + &val_encoded); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dict_out->key); + return status; + } + status = snapper_dbus_str_decode(mem_ctx, val_encoded, &dict_out->val); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dict_out->key); + return status; + } + + return NT_STATUS_OK; +} + +static void snapper_dict_array_print(uint32_t num_dicts, + struct snapper_dict *dicts) +{ + int i; + + for (i = 0; i < num_dicts; i++) { + DEBUG(10, ("dict (key: %s, val: %s)\n", + dicts[i].key, dicts[i].val)); + } +} + +static NTSTATUS snapper_dict_array_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + uint32_t *num_dicts_out, + struct snapper_dict **dicts_out) +{ + NTSTATUS status; + DBusMessageIter array_iter; + uint32_t num_dicts; + struct snapper_dict *dicts = NULL; + + status = snapper_type_check(iter, DBUS_TYPE_ARRAY); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &array_iter); + + num_dicts = 0; + while (dbus_message_iter_get_arg_type(&array_iter) + != DBUS_TYPE_INVALID) { + num_dicts++; + dicts = talloc_realloc(mem_ctx, dicts, struct snapper_dict, + num_dicts); + if (dicts == NULL) + abort(); + + status = snapper_dict_unpack(mem_ctx, &array_iter, + &dicts[num_dicts - 1]); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dicts); + return status; + } + dbus_message_iter_next(&array_iter); + } + + *num_dicts_out = num_dicts; + *dicts_out = dicts; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_list_confs_pack(DBusMessage **req_msg_out) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call("org.opensuse.Snapper", + "/org/opensuse/Snapper", + "org.opensuse.Snapper", + "ListConfigs"); + if (msg == NULL) { + DEBUG(0, ("null msg\n")); + return NT_STATUS_NO_MEMORY; + } + + /* no arguments to append */ + *req_msg_out = msg; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_conf_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct snapper_conf *conf_out) +{ + NTSTATUS status; + DBusMessageIter st_iter; + char *name_encoded; + char *mnt_encoded; + + status = snapper_type_check(iter, DBUS_TYPE_STRUCT); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &st_iter); + + status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING, + &name_encoded); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = snapper_dbus_str_decode(mem_ctx, name_encoded, + &conf_out->name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING, + &mnt_encoded); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(conf_out->name); + return status; + } + + status = snapper_dbus_str_decode(mem_ctx, mnt_encoded, + &conf_out->mnt); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(conf_out->name); + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_dict_array_unpack(mem_ctx, &st_iter, + &conf_out->num_attrs, + &conf_out->attrs); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(conf_out->mnt); + talloc_free(conf_out->name); + return status; + } + + return NT_STATUS_OK; +} + +static struct snapper_conf *snapper_conf_array_base_find(int32_t num_confs, + struct snapper_conf *confs, + const char *base) +{ + int i; + + for (i = 0; i < num_confs; i++) { + if (strcmp(confs[i].mnt, base) == 0) { + DEBUG(5, ("found snapper conf %s for path %s\n", + confs[i].name, base)); + return &confs[i]; + } + } + DEBUG(5, ("config for base %s not found\n", base)); + + return NULL; +} + +static void snapper_conf_array_print(int32_t num_confs, + struct snapper_conf *confs) +{ + int i; + + for (i = 0; i < num_confs; i++) { + DEBUG(10, ("name: %s, mnt: %s\n", + confs[i].name, confs[i].mnt)); + snapper_dict_array_print(confs[i].num_attrs, confs[i].attrs); + } +} + +static NTSTATUS snapper_conf_array_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + uint32_t *num_confs_out, + struct snapper_conf **confs_out) +{ + uint32_t num_confs; + NTSTATUS status; + struct snapper_conf *confs = NULL; + DBusMessageIter array_iter; + + + status = snapper_type_check(iter, DBUS_TYPE_ARRAY); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &array_iter); + + num_confs = 0; + while (dbus_message_iter_get_arg_type(&array_iter) + != DBUS_TYPE_INVALID) { + num_confs++; + confs = talloc_realloc(mem_ctx, confs, struct snapper_conf, + num_confs); + if (confs == NULL) + abort(); + + status = snapper_conf_unpack(confs, &array_iter, + &confs[num_confs - 1]); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(confs); + return status; + } + dbus_message_iter_next(&array_iter); + } + + *num_confs_out = num_confs; + *confs_out = confs; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_list_confs_unpack(TALLOC_CTX *mem_ctx, + DBusConnection *dconn, + DBusMessage *rsp_msg, + uint32_t *num_confs_out, + struct snapper_conf **confs_out) +{ + NTSTATUS status; + DBusMessageIter iter; + int msg_type; + uint32_t num_confs; + struct snapper_conf *confs; + const char *sig; + + msg_type = dbus_message_get_type(rsp_msg); + if (msg_type == DBUS_MESSAGE_TYPE_ERROR) { + const char *err_str = dbus_message_get_error_name(rsp_msg); + DEBUG(0, ("list_confs error response: %s\n", err_str)); + return snapper_err_ntstatus_map(err_str); + } + + if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) { + DEBUG(0, ("unexpected list_confs ret type: %d\n", + msg_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sig = dbus_message_get_signature(rsp_msg); + if ((sig == NULL) + || (strcmp(sig, SNAPPER_SIG_LIST_CONFS_RSP) != 0)) { + DEBUG(0, ("bad list confs response sig: %s, expected: %s\n", + (sig ? sig : "NULL"), SNAPPER_SIG_LIST_CONFS_RSP)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!dbus_message_iter_init(rsp_msg, &iter)) { + /* FIXME return empty? */ + DEBUG(0, ("Message has no arguments!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + status = snapper_conf_array_unpack(mem_ctx, &iter, &num_confs, &confs); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to unpack conf array\n")); + return status; + } + + snapper_conf_array_print(num_confs, confs); + + *num_confs_out = num_confs; + *confs_out = confs; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_list_snaps_pack(TALLOC_CTX *mem_ctx, + char *snapper_conf, + DBusMessage **req_msg_out) +{ + DBusMessage *msg; + DBusMessageIter args; + char *conf_encoded; + NTSTATUS status; + + msg = dbus_message_new_method_call("org.opensuse.Snapper", /* target for the method call */ + "/org/opensuse/Snapper", /* object to call on */ + "org.opensuse.Snapper", /* interface to call on */ + "ListSnapshots"); /* method name */ + if (msg == NULL) { + DEBUG(0, ("failed to create list snaps message\n")); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + return status; + } + + /* append arguments */ + dbus_message_iter_init_append(msg, &args); + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &conf_encoded)) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + *req_msg_out = msg; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_snap_struct_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct snapper_snap *snap_out) +{ + NTSTATUS status; + DBusMessageIter st_iter; + char *desc_encoded; + char *cleanup_encoded; + + status = snapper_type_check(iter, DBUS_TYPE_STRUCT); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &st_iter); + + status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32, + &snap_out->id); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT16, + &snap_out->type); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32, + &snap_out->pre_id); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_INT64, + &snap_out->time); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32, + &snap_out->creator_uid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING, + &desc_encoded); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = snapper_dbus_str_decode(mem_ctx, desc_encoded, + &snap_out->desc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING, + &cleanup_encoded); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(snap_out->desc); + return status; + } + + status = snapper_dbus_str_decode(mem_ctx, cleanup_encoded, + &snap_out->cleanup); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(snap_out->desc); + return status; + } + + dbus_message_iter_next(&st_iter); + status = snapper_dict_array_unpack(mem_ctx, &st_iter, + &snap_out->num_user_data, + &snap_out->user_data); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(snap_out->cleanup); + talloc_free(snap_out->desc); + return status; + } + + return NT_STATUS_OK; +} + +static void snapper_snap_array_print(int32_t num_snaps, + struct snapper_snap *snaps) +{ + int i; + + for (i = 0; i < num_snaps; i++) { + DEBUG(10, ("id: %u, " + "type: %u, " + "pre_id: %u, " + "time: %ld, " + "creator_uid: %u, " + "desc: %s, " + "cleanup: %s\n", + (unsigned int)snaps[i].id, + (unsigned int)snaps[i].type, + (unsigned int)snaps[i].pre_id, + (long int)snaps[i].time, + (unsigned int)snaps[i].creator_uid, + snaps[i].desc, + snaps[i].cleanup)); + snapper_dict_array_print(snaps[i].num_user_data, + snaps[i].user_data); + } +} + +static NTSTATUS snapper_snap_array_unpack(TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + uint32_t *num_snaps_out, + struct snapper_snap **snaps_out) +{ + uint32_t num_snaps; + NTSTATUS status; + struct snapper_snap *snaps = NULL; + DBusMessageIter array_iter; + + + status = snapper_type_check(iter, DBUS_TYPE_ARRAY); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dbus_message_iter_recurse(iter, &array_iter); + + num_snaps = 0; + while (dbus_message_iter_get_arg_type(&array_iter) + != DBUS_TYPE_INVALID) { + num_snaps++; + snaps = talloc_realloc(mem_ctx, snaps, struct snapper_snap, + num_snaps); + if (snaps == NULL) + abort(); + + status = snapper_snap_struct_unpack(snaps, &array_iter, + &snaps[num_snaps - 1]); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(snaps); + return status; + } + dbus_message_iter_next(&array_iter); + } + + *num_snaps_out = num_snaps; + *snaps_out = snaps; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_list_snaps_unpack(TALLOC_CTX *mem_ctx, + DBusMessage *rsp_msg, + uint32_t *num_snaps_out, + struct snapper_snap **snaps_out) +{ + NTSTATUS status; + DBusMessageIter iter; + int msg_type; + uint32_t num_snaps; + struct snapper_snap *snaps; + const char *sig; + + msg_type = dbus_message_get_type(rsp_msg); + if (msg_type == DBUS_MESSAGE_TYPE_ERROR) { + const char *err_str = dbus_message_get_error_name(rsp_msg); + DEBUG(0, ("list_snaps error response: %s\n", err_str)); + return snapper_err_ntstatus_map(err_str); + } + + if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) { + DEBUG(0,("unexpected list_snaps ret type: %d\n", + msg_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sig = dbus_message_get_signature(rsp_msg); + if ((sig == NULL) + || (strcmp(sig, SNAPPER_SIG_LIST_SNAPS_RSP) != 0)) { + DEBUG(0, ("bad list snaps response sig: %s, " + "expected: %s\n", + (sig ? sig : "NULL"), + SNAPPER_SIG_LIST_SNAPS_RSP)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* read the parameters */ + if (!dbus_message_iter_init(rsp_msg, &iter)) { + DEBUG(0, ("response has no arguments!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + status = snapper_snap_array_unpack(mem_ctx, &iter, &num_snaps, &snaps); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to unpack snap array\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + snapper_snap_array_print(num_snaps, snaps); + + *num_snaps_out = num_snaps; + *snaps_out = snaps; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_create_snap_pack(TALLOC_CTX *mem_ctx, + const char *snapper_conf, + const char *desc, + uint32_t num_user_data, + struct snapper_dict *user_data, + DBusMessage **req_msg_out) +{ + DBusMessage *msg; + DBusMessageIter args; + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + const char *empty = ""; + char *str_encoded; + uint32_t i; + bool ok; + TALLOC_CTX *enc_ctx; + NTSTATUS status; + + DEBUG(10, ("CreateSingleSnapshot: %s, %s, %s, num user %u\n", + snapper_conf, desc, empty, num_user_data)); + + enc_ctx = talloc_new(mem_ctx); + if (enc_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg = dbus_message_new_method_call("org.opensuse.Snapper", + "/org/opensuse/Snapper", + "org.opensuse.Snapper", + "CreateSingleSnapshot"); + if (msg == NULL) { + DEBUG(0, ("failed to create req msg\n")); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(enc_ctx, snapper_conf, &str_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return status; + } + + /* append arguments */ + dbus_message_iter_init_append(msg, &args); + ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &str_encoded); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(enc_ctx, desc, &str_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return status; + } + + ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &str_encoded); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* cleanup - no need to encode empty string */ + ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &empty); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, + SNAPPER_SIG_STRING_DICT, + &array_iter); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_user_data; i++) { + ok = dbus_message_iter_open_container(&array_iter, + DBUS_TYPE_DICT_ENTRY, + NULL, &struct_iter); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(enc_ctx, user_data[i].key, + &str_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return status; + } + + ok = dbus_message_iter_append_basic(&struct_iter, + DBUS_TYPE_STRING, + &str_encoded); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(enc_ctx, user_data[i].val, + &str_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return status; + } + + ok = dbus_message_iter_append_basic(&struct_iter, + DBUS_TYPE_STRING, + &str_encoded); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + ok = dbus_message_iter_close_container(&array_iter, &struct_iter); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + } + + ok = dbus_message_iter_close_container(&args, &array_iter); + if (!ok) { + dbus_message_unref(msg); + talloc_free(enc_ctx); + return NT_STATUS_NO_MEMORY; + } + + *req_msg_out = msg; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_create_snap_unpack(DBusConnection *conn, + DBusMessage *rsp_msg, + uint32_t *snap_id_out) +{ + NTSTATUS status; + DBusMessageIter iter; + int msg_type; + const char *sig; + uint32_t snap_id; + + msg_type = dbus_message_get_type(rsp_msg); + if (msg_type == DBUS_MESSAGE_TYPE_ERROR) { + const char *err_str = dbus_message_get_error_name(rsp_msg); + DEBUG(0, ("create snap error response: %s, euid %d egid %d\n", + err_str, geteuid(), getegid())); + return snapper_err_ntstatus_map(err_str); + } + + if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) { + DEBUG(0, ("unexpected create snap ret type: %d\n", + msg_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sig = dbus_message_get_signature(rsp_msg); + if ((sig == NULL) + || (strcmp(sig, SNAPPER_SIG_CREATE_SNAP_RSP) != 0)) { + DEBUG(0, ("bad create snap response sig: %s, expected: %s\n", + (sig ? sig : "NULL"), SNAPPER_SIG_CREATE_SNAP_RSP)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* read the parameters */ + if (!dbus_message_iter_init(rsp_msg, &iter)) { + DEBUG(0, ("response has no arguments!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + status = snapper_type_check_get(&iter, DBUS_TYPE_UINT32, &snap_id); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *snap_id_out = snap_id; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_del_snap_pack(TALLOC_CTX *mem_ctx, + const char *snapper_conf, + uint32_t snap_id, + DBusMessage **req_msg_out) +{ + DBusMessage *msg; + DBusMessageIter args; + DBusMessageIter array_iter; + char *conf_encoded; + bool ok; + NTSTATUS status; + + msg = dbus_message_new_method_call("org.opensuse.Snapper", + "/org/opensuse/Snapper", + "org.opensuse.Snapper", + "DeleteSnapshots"); + if (msg == NULL) { + DEBUG(0, ("failed to create req msg\n")); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + return status; + } + + /* append arguments */ + dbus_message_iter_init_append(msg, &args); + ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &conf_encoded); + if (!ok) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, + DBUS_TYPE_UINT32_AS_STRING, + &array_iter); + if (!ok) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + ok = dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_UINT32, + &snap_id); + if (!ok) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + dbus_message_iter_close_container(&args, &array_iter); + *req_msg_out = msg; + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_del_snap_unpack(DBusConnection *conn, + DBusMessage *rsp_msg) +{ + int msg_type; + const char *sig; + + msg_type = dbus_message_get_type(rsp_msg); + if (msg_type == DBUS_MESSAGE_TYPE_ERROR) { + const char *err_str = dbus_message_get_error_name(rsp_msg); + DEBUG(0, ("del snap error response: %s\n", err_str)); + return snapper_err_ntstatus_map(err_str); + } + + if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) { + DEBUG(0, ("unexpected del snap ret type: %d\n", + msg_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sig = dbus_message_get_signature(rsp_msg); + if ((sig == NULL) + || (strcmp(sig, SNAPPER_SIG_DEL_SNAPS_RSP) != 0)) { + DEBUG(0, ("bad create snap response sig: %s, expected: %s\n", + (sig ? sig : "NULL"), SNAPPER_SIG_DEL_SNAPS_RSP)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* no parameters in response */ + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_list_snaps_at_time_pack(TALLOC_CTX *mem_ctx, + const char *snapper_conf, + time_t time_lower, + time_t time_upper, + DBusMessage **req_msg_out) +{ + DBusMessage *msg; + DBusMessageIter args; + char *conf_encoded; + NTSTATUS status; + + msg = dbus_message_new_method_call("org.opensuse.Snapper", + "/org/opensuse/Snapper", + "org.opensuse.Snapper", + "ListSnapshotsAtTime"); + if (msg == NULL) { + DEBUG(0, ("failed to create list snaps message\n")); + return NT_STATUS_NO_MEMORY; + } + + status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded); + if (!NT_STATUS_IS_OK(status)) { + dbus_message_unref(msg); + return status; + } + + dbus_message_iter_init_append(msg, &args); + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, + &conf_encoded)) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_INT64, + &time_lower)) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_INT64, + &time_upper)) { + talloc_free(conf_encoded); + dbus_message_unref(msg); + return NT_STATUS_NO_MEMORY; + } + + *req_msg_out = msg; + + return NT_STATUS_OK; +} +/* no snapper_list_snaps_at_time_unpack, use snapper_list_snaps_unpack */ + +/* + * Determine the snapper snapshot id given a path. + * Ideally this should be determined via a lookup. + */ +static NTSTATUS snapper_snap_path_to_id(TALLOC_CTX *mem_ctx, + const char *snap_path, + uint32_t *snap_id_out) +{ + char *path_dup; + char *str_idx; + uint32_t snap_id; + int error = 0; + + path_dup = talloc_strdup(mem_ctx, snap_path); + if (path_dup == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* trim trailing '/' */ + str_idx = path_dup + strlen(path_dup) - 1; + while (*str_idx == '/') { + *str_idx = '\0'; + str_idx--; + } + + str_idx = strrchr(path_dup, '/'); + if ((str_idx == NULL) + || (strcmp(str_idx + 1, "snapshot") != 0)) { + talloc_free(path_dup); + return NT_STATUS_INVALID_PARAMETER; + } + + while (*str_idx == '/') { + *str_idx = '\0'; + str_idx--; + } + + str_idx = strrchr(path_dup, '/'); + if (str_idx == NULL) { + talloc_free(path_dup); + return NT_STATUS_INVALID_PARAMETER; + } + + str_idx++; + snap_id = smb_strtoul(str_idx, NULL, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + talloc_free(path_dup); + return NT_STATUS_INVALID_PARAMETER; + } + + talloc_free(path_dup); + *snap_id_out = snap_id; + return NT_STATUS_OK; +} + +/* + * Determine the snapper snapshot path given an id and base. + * Ideally this should be determined via a lookup. + */ +static NTSTATUS snapper_snap_id_to_path(TALLOC_CTX *mem_ctx, + const char *base_path, + uint32_t snap_id, + char **snap_path_out) +{ + char *snap_path; + + snap_path = talloc_asprintf(mem_ctx, "%s/.snapshots/%u/snapshot", + base_path, snap_id); + if (snap_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *snap_path_out = snap_path; + return NT_STATUS_OK; +} + +static NTSTATUS snapper_get_conf_call(TALLOC_CTX *mem_ctx, + DBusConnection *dconn, + const char *path, + char **conf_name_out, + char **base_path_out) +{ + NTSTATUS status; + DBusMessage *req_msg; + DBusMessage *rsp_msg; + uint32_t num_confs = 0; + struct snapper_conf *confs = NULL; + struct snapper_conf *conf; + char *conf_name; + char *base_path; + + status = snapper_list_confs_pack(&req_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_req_free; + } + + status = snapper_list_confs_unpack(mem_ctx, dconn, rsp_msg, + &num_confs, &confs); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + + /* + * for now we only support shares where the path directly corresponds + * to a snapper configuration. + */ + conf = snapper_conf_array_base_find(num_confs, confs, + path); + if (conf == NULL) { + status = NT_STATUS_NOT_SUPPORTED; + goto err_array_free; + } + + conf_name = talloc_strdup(mem_ctx, conf->name); + if (conf_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_array_free; + } + base_path = talloc_strdup(mem_ctx, conf->mnt); + if (base_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_conf_name_free; + } + + talloc_free(confs); + dbus_message_unref(rsp_msg); + dbus_message_unref(req_msg); + + *conf_name_out = conf_name; + *base_path_out = base_path; + + return NT_STATUS_OK; + +err_conf_name_free: + talloc_free(conf_name); +err_array_free: + talloc_free(confs); +err_rsp_free: + dbus_message_unref(rsp_msg); +err_req_free: + dbus_message_unref(req_msg); +err_out: + return status; +} + +/* + * Check whether a path can be shadow copied. Return the base volume, allowing + * the caller to determine if multiple paths lie on the same base volume. + */ +static NTSTATUS snapper_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + NTSTATUS status; + DBusConnection *dconn; + char *conf_name; + char *base_path; + + dconn = snapper_dbus_conn_create(); + if (dconn == NULL) { + return NT_STATUS_UNSUCCESSFUL; + } + + status = snapper_get_conf_call(mem_ctx, dconn, service_path, + &conf_name, &base_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_conn_close; + } + + talloc_free(conf_name); + *base_volume = base_path; + snapper_dbus_conn_destroy(dconn); + + return NT_STATUS_OK; + +err_conn_close: + snapper_dbus_conn_destroy(dconn); + return status; +} + +static NTSTATUS snapper_create_snap_call(TALLOC_CTX *mem_ctx, + DBusConnection *dconn, + const char *conf_name, + const char *base_path, + const char *snap_desc, + uint32_t num_user_data, + struct snapper_dict *user_data, + char **snap_path_out) +{ + NTSTATUS status; + DBusMessage *req_msg; + DBusMessage *rsp_msg; + uint32_t snap_id = 0; + char *snap_path; + + status = snapper_create_snap_pack(mem_ctx, + conf_name, + snap_desc, + num_user_data, + user_data, + &req_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_req_free; + } + + status = snapper_create_snap_unpack(dconn, rsp_msg, &snap_id); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + + status = snapper_snap_id_to_path(mem_ctx, base_path, snap_id, + &snap_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + + dbus_message_unref(rsp_msg); + dbus_message_unref(req_msg); + + DEBUG(6, ("created new snapshot %u at %s\n", snap_id, snap_path)); + *snap_path_out = snap_path; + + return NT_STATUS_OK; + +err_rsp_free: + dbus_message_unref(rsp_msg); +err_req_free: + dbus_message_unref(req_msg); +err_out: + return status; +} + +static NTSTATUS snapper_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **_base_path, + char **_snap_path) +{ + DBusConnection *dconn; + NTSTATUS status; + char *conf_name; + char *base_path; + char *snap_path = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dconn = snapper_dbus_conn_create(); + if (dconn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + status = snapper_get_conf_call(tmp_ctx, dconn, base_volume, + &conf_name, &base_path); + if (!NT_STATUS_IS_OK(status)) { + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + return status; + } + + status = snapper_create_snap_call(tmp_ctx, dconn, + conf_name, base_path, + "Snapshot created by Samba", + 0, NULL, + &snap_path); + if (!NT_STATUS_IS_OK(status)) { + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + return status; + } + + snapper_dbus_conn_destroy(dconn); + *_base_path = talloc_steal(mem_ctx, base_path); + *_snap_path = talloc_steal(mem_ctx, snap_path); + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +static NTSTATUS snapper_delete_snap_call(TALLOC_CTX *mem_ctx, + DBusConnection *dconn, + const char *conf_name, + uint32_t snap_id) +{ + NTSTATUS status; + DBusMessage *req_msg = NULL; + DBusMessage *rsp_msg; + + status = snapper_del_snap_pack(mem_ctx, conf_name, snap_id, &req_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_req_free; + } + + status = snapper_del_snap_unpack(dconn, rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + + dbus_message_unref(rsp_msg); + dbus_message_unref(req_msg); + + DEBUG(6, ("deleted snapshot %u\n", snap_id)); + + return NT_STATUS_OK; + +err_rsp_free: + dbus_message_unref(rsp_msg); +err_req_free: + dbus_message_unref(req_msg); +err_out: + return status; +} + +static NTSTATUS snapper_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + DBusConnection *dconn; + NTSTATUS status; + char *conf_name; + char *snap_base_path; + uint32_t snap_id; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dconn = snapper_dbus_conn_create(); + if (dconn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + status = snapper_get_conf_call(tmp_ctx, dconn, base_path, + &conf_name, &snap_base_path); + if (!NT_STATUS_IS_OK(status)) { + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + return status; + } + + status = snapper_snap_path_to_id(tmp_ctx, snap_path, &snap_id); + if (!NT_STATUS_IS_OK(status)) { + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + return status; + } + + status = snapper_delete_snap_call(tmp_ctx, dconn, conf_name, snap_id); + if (!NT_STATUS_IS_OK(status)) { + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + return status; + } + + snapper_dbus_conn_destroy(dconn); + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +/* sc_data used as parent talloc context for all labels */ +static int snapper_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *sc_data, + bool labels) +{ + DBusConnection *dconn; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + char *conf_name; + char *base_path; + DBusMessage *req_msg = NULL; + DBusMessage *rsp_msg; + uint32_t num_snaps; + struct snapper_snap *snaps; + uint32_t i; + uint32_t lbl_off; + + tmp_ctx = talloc_new(sc_data); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + dconn = snapper_dbus_conn_create(); + if (dconn == NULL) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_mem_ctx_free; + } + + if (fsp->conn->connectpath == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_conn_free; + } + + status = snapper_get_conf_call(tmp_ctx, dconn, + fsp->conn->connectpath, + &conf_name, + &base_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_conn_free; + } + + status = snapper_list_snaps_pack(tmp_ctx, conf_name, &req_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_conn_free; + } + + status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_req_free; + } + + status = snapper_list_snaps_unpack(tmp_ctx, rsp_msg, + &num_snaps, &snaps); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + /* we should always get at least one snapshot (current) */ + if (num_snaps == 0) { + DEBUG(1, ("zero snapshots in snap list response\n")); + status = NT_STATUS_UNSUCCESSFUL; + goto err_rsp_free; + } + + /* subtract 1, (current) snapshot is not returned */ + sc_data->num_volumes = num_snaps - 1; + sc_data->labels = NULL; + + if ((labels == false) || (sc_data->num_volumes == 0)) { + /* tokens need not be added to the labels array */ + goto done; + } + + sc_data->labels = talloc_array(sc_data, SHADOW_COPY_LABEL, + sc_data->num_volumes); + if (sc_data->labels == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_rsp_free; + } + + /* start at end for descending order, do not include 0 (current) */ + lbl_off = 0; + for (i = num_snaps - 1; i > 0; i--) { + char *lbl = sc_data->labels[lbl_off++]; + struct tm gmt_snap_time; + struct tm *tm_ret; + size_t str_sz; + + tm_ret = gmtime_r((time_t *)&snaps[i].time, &gmt_snap_time); + if (tm_ret == NULL) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_labels_free; + } + str_sz = strftime(lbl, sizeof(SHADOW_COPY_LABEL), + "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time); + if (str_sz == 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_labels_free; + } + } + +done: + talloc_free(tmp_ctx); + dbus_message_unref(rsp_msg); + dbus_message_unref(req_msg); + snapper_dbus_conn_destroy(dconn); + + return 0; + +err_labels_free: + TALLOC_FREE(sc_data->labels); +err_rsp_free: + dbus_message_unref(rsp_msg); +err_req_free: + dbus_message_unref(req_msg); +err_conn_free: + snapper_dbus_conn_destroy(dconn); +err_mem_ctx_free: + talloc_free(tmp_ctx); +err_out: + errno = map_errno_from_nt_status(status); + return -1; +} + +static bool snapper_gmt_strip_snapshot(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + time_t *ptimestamp, + char **pstripped) +{ + char *stripped; + + if (smb_fname->twrp == 0) { + goto no_snapshot; + } + + if (pstripped != NULL) { + stripped = talloc_strdup(mem_ctx, smb_fname->base_name); + if (stripped == NULL) { + return false; + } + *pstripped = stripped; + } + + *ptimestamp = nt_time_to_unix(smb_fname->twrp); + return true; +no_snapshot: + *ptimestamp = 0; + return true; +} + +static NTSTATUS snapper_get_snap_at_time_call(TALLOC_CTX *mem_ctx, + DBusConnection *dconn, + const char *conf_name, + const char *base_path, + time_t snaptime, + char **snap_path_out) +{ + NTSTATUS status; + DBusMessage *req_msg = NULL; + DBusMessage *rsp_msg; + uint32_t num_snaps; + struct snapper_snap *snaps; + char *snap_path; + + status = snapper_list_snaps_at_time_pack(mem_ctx, + conf_name, + snaptime, + snaptime, + &req_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg); + if (!NT_STATUS_IS_OK(status)) { + goto err_req_free; + } + + status = snapper_list_snaps_unpack(mem_ctx, rsp_msg, + &num_snaps, &snaps); + if (!NT_STATUS_IS_OK(status)) { + goto err_rsp_free; + } + + if (num_snaps == 0) { + DEBUG(4, ("no snapshots found with time: %lu\n", + (unsigned long)snaptime)); + status = NT_STATUS_INVALID_PARAMETER; + goto err_snap_array_free; + } else if (num_snaps > 0) { + DEBUG(4, ("got %u snapshots for single time %lu, using top\n", + num_snaps, (unsigned long)snaptime)); + } + + status = snapper_snap_id_to_path(mem_ctx, base_path, snaps[0].id, + &snap_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_snap_array_free; + } + + *snap_path_out = snap_path; +err_snap_array_free: + talloc_free(snaps); +err_rsp_free: + dbus_message_unref(rsp_msg); +err_req_free: + dbus_message_unref(req_msg); +err_out: + return status; +} + +static NTSTATUS snapper_snap_path_expand(struct connection_struct *conn, + TALLOC_CTX *mem_ctx, + time_t snap_time, + char **snap_dir_out) +{ + DBusConnection *dconn; + NTSTATUS status; + char *conf_name; + char *base_path; + char *snap_path = NULL; + + dconn = snapper_dbus_conn_create(); + if (dconn == NULL) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + if (conn->connectpath == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_conn_free; + } + + status = snapper_get_conf_call(mem_ctx, dconn, + conn->connectpath, + &conf_name, + &base_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_conn_free; + } + + status = snapper_get_snap_at_time_call(mem_ctx, dconn, + conf_name, base_path, snap_time, + &snap_path); + if (!NT_STATUS_IS_OK(status)) { + goto err_conf_name_free; + } + + /* confirm snapshot path is nested under base path */ + if (strncmp(snap_path, base_path, strlen(base_path)) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_snap_path_free; + } + + talloc_free(conf_name); + talloc_free(base_path); + snapper_dbus_conn_destroy(dconn); + *snap_dir_out = snap_path; + + return NT_STATUS_OK; + +err_snap_path_free: + talloc_free(snap_path); +err_conf_name_free: + talloc_free(conf_name); + talloc_free(base_path); +err_conn_free: + snapper_dbus_conn_destroy(dconn); +err_out: + return status; +} + +static char *snapper_gmt_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp) +{ + char *snap_path = NULL; + char *path = NULL; + NTSTATUS status; + int saved_errno; + + status = snapper_snap_path_expand(handle->conn, mem_ctx, timestamp, + &snap_path); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto err_out; + } + + path = talloc_asprintf(mem_ctx, "%s/%s", snap_path, name); + if (path == NULL) { + errno = ENOMEM; + goto err_snap_path_free; + } + + DEBUG(10, ("converted %s/%s @ time to %s\n", + handle->conn->connectpath, name, path)); + return path; + +err_snap_path_free: + saved_errno = errno; + talloc_free(snap_path); + errno = saved_errno; +err_out: + return NULL; +} + +static int snapper_gmt_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + time_t timestamp_src, timestamp_dst; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname_src, + ×tamp_src, NULL)) { + return -1; + } + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname_dst, + ×tamp_dst, NULL)) { + return -1; + } + if (timestamp_src != 0) { + errno = EXDEV; + return -1; + } + if (timestamp_dst != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); +} + +static int snapper_gmt_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + link_contents, + ×tamp_old, + NULL)) { + return -1; + } + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + new_smb_fname, + ×tamp_new, + NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); +} + +static int snapper_gmt_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + old_smb_fname, + ×tamp_old, + NULL)) { + return -1; + } + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + new_smb_fname, + ×tamp_new, + NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); +} + +static int snapper_gmt_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp; + char *stripped, *tmp; + int ret, saved_errno; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + tmp = smb_fname->base_name; + smb_fname->base_name = snapper_gmt_convert(talloc_tos(), handle, + stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; + return -1; + } + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + saved_errno = errno; + + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; + + errno = saved_errno; + return ret; +} + +static int snapper_gmt_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp; + char *stripped, *tmp; + int ret, saved_errno; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + tmp = smb_fname->base_name; + smb_fname->base_name = snapper_gmt_convert(talloc_tos(), handle, + stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; + return -1; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + saved_errno = errno; + + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; + + errno = saved_errno; + return ret; +} + +static int snapper_gmt_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + struct smb_filename *smb_fname = NULL; + time_t timestamp; + char *stripped = NULL; + int ret; + int saved_errno = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname_in, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname_in, + fsp, + how); + } + + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + TALLOC_FREE(stripped); + return -1; + } + + smb_fname->base_name = snapper_gmt_convert(smb_fname, handle, + stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + TALLOC_FREE(smb_fname); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int snapper_gmt_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + time_t timestamp = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); +} + +static int snapper_gmt_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + time_t timestamp = 0; + const struct smb_filename *smb_fname = NULL; + + smb_fname = fsp->fsp_name; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return -1; + } + + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); +} + +static int snapper_gmt_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char *stripped = NULL; + int ret; + int saved_errno = 0; + char *conv = NULL; + struct smb_filename *conv_smb_fname = NULL; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); + } + conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + if (conv_smb_fname == NULL) { + TALLOC_FREE(conv); + errno = ENOMEM; + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, conv_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(conv); + TALLOC_FREE(conv_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int snapper_gmt_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + time_t timestamp = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + fsp->fsp_name, + ×tamp, + NULL)) { + return -1; + } + + if (timestamp != 0) { + errno = EROFS; + return -1; + } + + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int snapper_gmt_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + time_t timestamp = 0; + int ret; + int saved_errno = 0; + struct smb_filename *full_fname = NULL; + + /* + * Now this function only looks at smb_fname->twrp + * we don't need to copy out the path. Just use + * smb_fname->base_name directly. + */ + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + } + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + /* Find the snapshot path from the full pathname. */ + full_fname->base_name = snapper_gmt_convert(full_fname, + handle, + full_fname->base_name, + timestamp); + if (full_fname->base_name == NULL) { + TALLOC_FREE(full_fname); + return -1; + } + ret = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + full_fname, + buf, + bufsiz); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(full_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int snapper_gmt_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + time_t timestamp = (time_t)0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); +} + +static struct smb_filename *snapper_gmt_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char *stripped = NULL; + struct smb_filename *result_fname = NULL; + struct smb_filename *conv_smb_fname = NULL; + int saved_errno = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, + ×tamp, &stripped)) { + goto done; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + } + + conv_smb_fname = cp_smb_filename(talloc_tos(), smb_fname); + if (conv_smb_fname == NULL) { + goto done; + } + conv_smb_fname->base_name = snapper_gmt_convert(conv_smb_fname, handle, + stripped, timestamp); + if (conv_smb_fname->base_name == NULL) { + goto done; + } + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, conv_smb_fname); + +done: + if (result_fname == NULL) { + saved_errno = errno; + } + TALLOC_FREE(conv_smb_fname); + TALLOC_FREE(stripped); + if (saved_errno != 0) { + errno = saved_errno; + } + return result_fname; +} + +static int snapper_gmt_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *fname, + mode_t mode) +{ + time_t timestamp = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + fname, + mode); +} + +static int snapper_gmt_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + time_t timestamp = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + fsp->fsp_name, ×tamp, NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); +} + +static int snapper_gmt_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *aname, const void *value, + size_t size, int flags) +{ + time_t timestamp = 0; + const struct smb_filename *smb_fname = NULL; + + smb_fname = fsp->fsp_name; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, + aname, value, size, flags); +} + +static NTSTATUS snapper_gmt_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + time_t timestamp; + char *stripped; + char *conv; + struct smb_filename *conv_fname = NULL; + NTSTATUS status; + bool ok; + + ok = snapper_gmt_strip_snapshot( + talloc_tos(), handle, dirfsp->fsp_name,×tamp, &stripped); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + if (stripped[0] == '\0') { + *found_name = talloc_strdup(mem_ctx, name); + if (*found_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } + conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return map_nt_error_from_unix(errno); + } + + status = synthetic_pathref( + talloc_tos(), + dirfsp->conn->cwd_fsp, + conv, + NULL, + NULL, + 0, + 0, + &conv_fname); + + status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, conv_fname->fsp, name, mem_ctx, found_name); + TALLOC_FREE(conv); + return status; +} + +static uint64_t snapper_gmt_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + time_t timestamp = 0; + char *stripped = NULL; + uint64_t ret; + int saved_errno = 0; + char *conv = NULL; + struct smb_filename *conv_smb_fname = NULL; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, ×tamp, &stripped)) { + return (uint64_t)-1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, + bsize, dfree, dsize); + } + + conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return (uint64_t)-1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + if (conv_smb_fname == NULL) { + TALLOC_FREE(conv); + errno = ENOMEM; + return (uint64_t)-1; + } + + ret = SMB_VFS_NEXT_DISK_FREE(handle, conv_smb_fname, + bsize, dfree, dsize); + + if (ret == (uint64_t)-1) { + saved_errno = errno; + } + TALLOC_FREE(conv_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int snapper_gmt_get_quota(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + time_t timestamp = 0; + char *stripped = NULL; + int ret; + int saved_errno = 0; + char *conv = NULL; + struct smb_filename *conv_smb_fname = NULL; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, + smb_fname, ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, dq); + } + + conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + conv_smb_fname = synthetic_smb_fname(talloc_tos(), + conv, + NULL, + NULL, + 0, + smb_fname->flags); + TALLOC_FREE(conv); + if (conv_smb_fname == NULL) { + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv_smb_fname, qtype, id, dq); + + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(conv_smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static NTSTATUS snapper_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + time_t timestamp = 0; + + if (!snapper_gmt_strip_snapshot(talloc_tos(), + handle, + smb_fname, + ×tamp, + NULL)) { + return NT_STATUS_NO_MEMORY; + } + if (timestamp != 0) { + return NT_STATUS_MEDIA_WRITE_PROTECTED; + } + return SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + smb_fname, + reflist, + referral_count); +} + +static struct vfs_fn_pointers snapper_fns = { + .snap_check_path_fn = snapper_snap_check_path, + .snap_create_fn = snapper_snap_create, + .snap_delete_fn = snapper_snap_delete, + .get_shadow_copy_data_fn = snapper_get_shadow_copy_data, + .create_dfs_pathat_fn = snapper_create_dfs_pathat, + .disk_free_fn = snapper_gmt_disk_free, + .get_quota_fn = snapper_gmt_get_quota, + .renameat_fn = snapper_gmt_renameat, + .linkat_fn = snapper_gmt_linkat, + .symlinkat_fn = snapper_gmt_symlinkat, + .stat_fn = snapper_gmt_stat, + .lstat_fn = snapper_gmt_lstat, + .openat_fn = snapper_gmt_openat, + .unlinkat_fn = snapper_gmt_unlinkat, + .fchmod_fn = snapper_gmt_fchmod, + .chdir_fn = snapper_gmt_chdir, + .fntimes_fn = snapper_gmt_fntimes, + .readlinkat_fn = snapper_gmt_readlinkat, + .mknodat_fn = snapper_gmt_mknodat, + .realpath_fn = snapper_gmt_realpath, + .mkdirat_fn = snapper_gmt_mkdirat, + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fsetxattr_fn = snapper_gmt_fsetxattr, + .fchflags_fn = snapper_gmt_fchflags, + .get_real_filename_at_fn = snapper_gmt_get_real_filename_at, +}; + +static_decl_vfs; +NTSTATUS vfs_snapper_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "snapper", &snapper_fns); +} diff --git a/source3/modules/vfs_solarisacl.c b/source3/modules/vfs_solarisacl.c new file mode 100644 index 0000000..d31bda5 --- /dev/null +++ b/source3/modules/vfs_solarisacl.c @@ -0,0 +1,699 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set Solaris ACLs + Copyright (C) Michael Adam 2006,2008 + + 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/filesys.h" +#include "smbd/smbd.h" +#include "modules/vfs_solarisacl.h" + +/* typedef struct acl SOLARIS_ACE_T; */ +typedef aclent_t SOLARIS_ACE_T; +typedef aclent_t *SOLARIS_ACL_T; +typedef int SOLARIS_ACL_TAG_T; /* the type of an ACL entry */ +typedef o_mode_t SOLARIS_PERM_T; + +/* for convenience: check if solaris acl entry is a default entry? */ +#define _IS_DEFAULT(ace) ((ace).a_type & ACL_DEFAULT) +#define _IS_OF_TYPE(ace, type) ( \ + (((type) == SMB_ACL_TYPE_ACCESS) && !_IS_DEFAULT(ace)) \ + || \ + (((type) == SMB_ACL_TYPE_DEFAULT) && _IS_DEFAULT(ace)) \ +) + + +/* prototypes for private functions */ + +static SOLARIS_ACL_T solaris_acl_init(int count); +static bool smb_acl_to_solaris_acl(SMB_ACL_T smb_acl, + SOLARIS_ACL_T *solariacl, int *count, + SMB_ACL_TYPE_T type); +static SMB_ACL_T solaris_acl_to_smb_acl(SOLARIS_ACL_T solarisacl, int count, + SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx); +static SOLARIS_ACL_TAG_T smb_tag_to_solaris_tag(SMB_ACL_TAG_T smb_tag); +static SMB_ACL_TAG_T solaris_tag_to_smb_tag(SOLARIS_ACL_TAG_T solaris_tag); +static bool solaris_add_to_acl(SOLARIS_ACL_T *solaris_acl, int *count, + SOLARIS_ACL_T add_acl, int add_count, SMB_ACL_TYPE_T type); +static bool solaris_acl_get_file(const char *name, SOLARIS_ACL_T *solarisacl, + int *count); +static bool solaris_acl_get_fd(int fd, SOLARIS_ACL_T *solarisacl, int *count); +static bool solaris_acl_sort(SOLARIS_ACL_T theacl, int count); +static SMB_ACL_PERM_T solaris_perm_to_smb_perm(const SOLARIS_PERM_T perm); +static SOLARIS_PERM_T smb_perm_to_solaris_perm(const SMB_ACL_PERM_T perm); +#if 0 +static bool solaris_acl_check(SOLARIS_ACL_T solaris_acl, int count); +#endif + +/* public functions - the api */ + +static SMB_ACL_T solarisacl_sys_acl_get_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result = NULL; + int count; + SOLARIS_ACL_T solaris_acl = NULL; + const char *path_p = smb_fname->base_name; + + DEBUG(10, ("solarisacl_sys_acl_get_file called for file '%s'.\n", + path_p)); + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type)); + errno = EINVAL; + goto done; + } + + DEBUGADD(10, ("getting %s acl\n", + ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default"))); + + if (!solaris_acl_get_file(path_p, &solaris_acl, &count)) { + goto done; + } + result = solaris_acl_to_smb_acl(solaris_acl, count, type, mem_ctx); + if (result == NULL) { + DEBUG(10, ("conversion solaris_acl -> smb_acl failed (%s).\n", + strerror(errno))); + } + + done: + DEBUG(10, ("solarisacl_sys_acl_get_file %s.\n", + ((result == NULL) ? "failed" : "succeeded" ))); + SAFE_FREE(solaris_acl); + return result; +} + + +/* + * get the access ACL of a file referred to by a fd + */ +SMB_ACL_T solarisacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result = NULL; + int count; + SOLARIS_ACL_T solaris_acl = NULL; + + DEBUG(10, ("entering solarisacl_sys_acl_get_fd.\n")); + + if (!solaris_acl_get_fd(fsp_get_io_fd(fsp), &solaris_acl, &count)) { + goto done; + } + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type)); + errno = EINVAL; + goto done; + } + /* + * The facl call returns both ACCESS and DEFAULT acls (as present). + * The posix acl_get_fd function returns only the + * access acl. So we need to filter this out here. + */ + result = solaris_acl_to_smb_acl(solaris_acl, count, + type, mem_ctx); + if (result == NULL) { + DEBUG(10, ("conversion solaris_acl -> smb_acl failed (%s).\n", + strerror(errno))); + } + + done: + DEBUG(10, ("solarisacl_sys_acl_get_fd %s.\n", + ((result == NULL) ? "failed" : "succeeded"))); + SAFE_FREE(solaris_acl); + return result; +} + +/* + * set the access ACL on the file referred to by a fd + */ +int solarisacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + SOLARIS_ACL_T solaris_acl = NULL; + int count; + SOLARIS_ACL_T other_acl = NULL; + int other_count; + SMB_ACL_TYPE_T other_type; + int ret = -1; + + DEBUG(10, ("entering solarisacl_sys_acl_set_fd\n")); + + /* + * the posix acl_set_fd call sets the access acl of the + * file referred to by fd. the solaris facl-SETACL call + * sets the access and default acl as provided, so we + * have to retrieve the default acl of the file and + * concatenate it with the access acl provided. + */ + if (!smb_acl_to_solaris_acl(theacl, &solaris_acl, &count, + type)) + { + DEBUG(10, ("conversion smb_acl -> solaris_acl failed (%s).\n", + strerror(errno))); + goto done; + } + if (!solaris_acl_get_fd(fsp_get_io_fd(fsp), &other_acl, &other_count)) { + DEBUG(10, ("error getting (default) acl from fd\n")); + goto done; + } + + other_type = (type == SMB_ACL_TYPE_ACCESS) + ? SMB_ACL_TYPE_DEFAULT + : SMB_ACL_TYPE_ACCESS; + + if (!solaris_add_to_acl(&solaris_acl, &count, + other_acl, other_count, + other_type)) + { + DEBUG(10, ("error adding default acl to solaris acl\n")); + goto done; + } + if (!solaris_acl_sort(solaris_acl, count)) { + DEBUG(10, ("resulting acl is not valid!\n")); + goto done; + } + + ret = facl(fsp_get_io_fd(fsp), SETACL, count, solaris_acl); + if (ret != 0) { + DEBUG(10, ("call of facl failed (%s).\n", strerror(errno))); + } + + done: + DEBUG(10, ("solarisacl_sys_acl_set_fd %s.\n", + ((ret == 0) ? "succeeded" : "failed" ))); + SAFE_FREE(solaris_acl); + SAFE_FREE(other_acl); + return ret; +} + +/* + * delete the default ACL of a directory + * + * This is achieved by fetching the access ACL and rewriting it + * directly, via the solaris system call: the SETACL call on + * directories writes both the access and the default ACL as provided. + * + * XXX: posix acl_delete_def_file returns an error if + * the file referred to by path is not a directory. + * this function does not complain but the actions + * have no effect on a file other than a directory. + * But sys_acl_delete_default_file is only called in + * smbd/posixacls.c after having checked that the file + * is a directory, anyways. So implementing the extra + * check is considered unnecessary. --- Agreed? XXX + */ +int solarisacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + SMB_ACL_T smb_acl; + int ret = -1; + SOLARIS_ACL_T solaris_acl = NULL; + int count; + + DBG_DEBUG("entering solarisacl_sys_acl_delete_def_fd.\n"); + + smb_acl = solarisacl_sys_acl_get_file(handle, fsp->fsp_name->base_name, + SMB_ACL_TYPE_ACCESS, talloc_tos()); + if (smb_acl == NULL) { + DBG_DEBUG("getting file acl failed!\n"); + goto done; + } + if (!smb_acl_to_solaris_acl(smb_acl, &solaris_acl, &count, + SMB_ACL_TYPE_ACCESS)) + { + DBG_DEBUG("conversion smb_acl -> solaris_acl failed.\n"); + goto done; + } + if (!solaris_acl_sort(solaris_acl, count)) { + DBG_DEBUG("resulting acl is not valid!\n"); + goto done; + } + ret = acl(fsp->fsp_name->base_name, SETACL, count, solaris_acl); + if (ret != 0) { + DBG_DEBG("settinge file acl failed!\n"); + } + + done: + DBG_DEBUG("solarisacl_sys_acl_delete_def_fd %s.\n", + ((ret != 0) ? "failed" : "succeeded" )); + TALLOC_FREE(smb_acl); + return ret; +} + +/* private functions */ + +static SOLARIS_ACL_T solaris_acl_init(int count) +{ + SOLARIS_ACL_T solaris_acl = + (SOLARIS_ACL_T)SMB_MALLOC(sizeof(aclent_t) * count); + if (solaris_acl == NULL) { + errno = ENOMEM; + } + return solaris_acl; +} + +/* + * Convert the SMB acl to the ACCESS or DEFAULT part of a + * solaris ACL, as desired. + */ +static bool smb_acl_to_solaris_acl(SMB_ACL_T smb_acl, + SOLARIS_ACL_T *solaris_acl, int *count, + SMB_ACL_TYPE_T type) +{ + bool ret = False; + int i; + + DEBUG(10, ("entering smb_acl_to_solaris_acl\n")); + + *solaris_acl = NULL; + *count = 0; + + for (i = 0; i < smb_acl->count; i++) { + const struct smb_acl_entry *smb_entry = &(smb_acl->acl[i]); + SOLARIS_ACE_T solaris_entry; + + ZERO_STRUCT(solaris_entry); + + solaris_entry.a_type = smb_tag_to_solaris_tag(smb_entry->a_type); + if (solaris_entry.a_type == 0) { + DEBUG(10, ("smb_tag to solaris_tag failed\n")); + goto fail; + } + switch(solaris_entry.a_type) { + case USER: + DEBUG(10, ("got tag type USER with uid %u\n", + (unsigned int)smb_entry->info.user.uid)); + solaris_entry.a_id = (uid_t)smb_entry->info.user.uid; + break; + case GROUP: + DEBUG(10, ("got tag type GROUP with gid %u\n", + (unsigned int)smb_entry->info.group.gid)); + solaris_entry.a_id = (uid_t)smb_entry->info.group.gid; + break; + default: + break; + } + if (type == SMB_ACL_TYPE_DEFAULT) { + DEBUG(10, ("adding default bit to solaris ace\n")); + solaris_entry.a_type |= ACL_DEFAULT; + } + + solaris_entry.a_perm = + smb_perm_to_solaris_perm(smb_entry->a_perm); + DEBUG(10, ("assembled the following solaris ace:\n")); + DEBUGADD(10, (" - type: 0x%04x\n", solaris_entry.a_type)); + DEBUGADD(10, (" - id: %u\n", (unsigned int)solaris_entry.a_id)); + DEBUGADD(10, (" - perm: o%o\n", solaris_entry.a_perm)); + if (!solaris_add_to_acl(solaris_acl, count, &solaris_entry, + 1, type)) + { + DEBUG(10, ("error adding acl entry\n")); + goto fail; + } + DEBUG(10, ("count after adding: %d (i: %d)\n", *count, i)); + DEBUG(10, ("test, if entry has been copied into acl:\n")); + DEBUGADD(10, (" - type: 0x%04x\n", + (*solaris_acl)[(*count)-1].a_type)); + DEBUGADD(10, (" - id: %u\n", + (unsigned int)(*solaris_acl)[(*count)-1].a_id)); + DEBUGADD(10, (" - perm: o%o\n", + (*solaris_acl)[(*count)-1].a_perm)); + } + + ret = True; + goto done; + + fail: + SAFE_FREE(*solaris_acl); + done: + DEBUG(10, ("smb_acl_to_solaris_acl %s\n", + ((ret == True) ? "succeeded" : "failed"))); + return ret; +} + +/* + * convert either the access or the default part of a + * soaris acl to the SMB_ACL format. + */ +static SMB_ACL_T solaris_acl_to_smb_acl(SOLARIS_ACL_T solaris_acl, int count, + SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result; + int i; + + if ((result = sys_acl_init(mem_ctx)) == NULL) { + DEBUG(10, ("error allocating memory for SMB_ACL\n")); + goto fail; + } + for (i = 0; i < count; i++) { + SMB_ACL_ENTRY_T smb_entry; + SMB_ACL_PERM_T smb_perm; + + if (!_IS_OF_TYPE(solaris_acl[i], type)) { + continue; + } + result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count + 1); + if (result->acl == NULL) { + DEBUG(10, ("error reallocating memory for SMB_ACL\n")); + goto fail; + } + smb_entry = &result->acl[result->count]; + if (sys_acl_set_tag_type(smb_entry, + solaris_tag_to_smb_tag(solaris_acl[i].a_type)) != 0) + { + DEBUG(10, ("invalid tag type given: 0x%04x\n", + solaris_acl[i].a_type)); + goto fail; + } + /* intentionally not checking return code here: */ + sys_acl_set_qualifier(smb_entry, (void *)&solaris_acl[i].a_id); + smb_perm = solaris_perm_to_smb_perm(solaris_acl[i].a_perm); + if (sys_acl_set_permset(smb_entry, &smb_perm) != 0) { + DEBUG(10, ("invalid permset given: %d\n", + solaris_acl[i].a_perm)); + goto fail; + } + result->count += 1; + } + goto done; + + fail: + TALLOC_FREE(result); + done: + DEBUG(10, ("solaris_acl_to_smb_acl %s\n", + ((result == NULL) ? "failed" : "succeeded"))); + return result; +} + + + +static SOLARIS_ACL_TAG_T smb_tag_to_solaris_tag(SMB_ACL_TAG_T smb_tag) +{ + SOLARIS_ACL_TAG_T solaris_tag = 0; + + DEBUG(10, ("smb_tag_to_solaris_tag\n")); + DEBUGADD(10, (" --> got smb tag 0x%04x\n", smb_tag)); + + switch (smb_tag) { + case SMB_ACL_USER: + solaris_tag = USER; + break; + case SMB_ACL_USER_OBJ: + solaris_tag = USER_OBJ; + break; + case SMB_ACL_GROUP: + solaris_tag = GROUP; + break; + case SMB_ACL_GROUP_OBJ: + solaris_tag = GROUP_OBJ; + break; + case SMB_ACL_OTHER: + solaris_tag = OTHER_OBJ; + break; + case SMB_ACL_MASK: + solaris_tag = CLASS_OBJ; + break; + default: + DEBUGADD(10, (" !!! unknown smb tag type 0x%04x\n", smb_tag)); + break; + } + + DEBUGADD(10, (" --> determined solaris tag 0x%04x\n", solaris_tag)); + + return solaris_tag; +} + +static SMB_ACL_TAG_T solaris_tag_to_smb_tag(SOLARIS_ACL_TAG_T solaris_tag) +{ + SMB_ACL_TAG_T smb_tag = 0; + + DEBUG(10, ("solaris_tag_to_smb_tag:\n")); + DEBUGADD(10, (" --> got solaris tag 0x%04x\n", solaris_tag)); + + solaris_tag &= ~ACL_DEFAULT; + + switch (solaris_tag) { + case USER: + smb_tag = SMB_ACL_USER; + break; + case USER_OBJ: + smb_tag = SMB_ACL_USER_OBJ; + break; + case GROUP: + smb_tag = SMB_ACL_GROUP; + break; + case GROUP_OBJ: + smb_tag = SMB_ACL_GROUP_OBJ; + break; + case OTHER_OBJ: + smb_tag = SMB_ACL_OTHER; + break; + case CLASS_OBJ: + smb_tag = SMB_ACL_MASK; + break; + default: + DEBUGADD(10, (" !!! unknown solaris tag type: 0x%04x\n", + solaris_tag)); + break; + } + + DEBUGADD(10, (" --> determined smb tag 0x%04x\n", smb_tag)); + + return smb_tag; +} + + +static SMB_ACL_PERM_T solaris_perm_to_smb_perm(const SOLARIS_PERM_T perm) +{ + SMB_ACL_PERM_T smb_perm = 0; + smb_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0); + smb_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0); + smb_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); + return smb_perm; +} + + +static SOLARIS_PERM_T smb_perm_to_solaris_perm(const SMB_ACL_PERM_T perm) +{ + SOLARIS_PERM_T solaris_perm = 0; + solaris_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0); + solaris_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0); + solaris_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0); + return solaris_perm; +} + + +static bool solaris_acl_get_file(const char *name, SOLARIS_ACL_T *solaris_acl, + int *count) +{ + bool result = False; + + DEBUG(10, ("solaris_acl_get_file called for file '%s'\n", name)); + + /* + * The original code tries some INITIAL_ACL_SIZE + * and only did the GETACLCNT call upon failure + * (for performance reasons). + * For the sake of simplicity, I skip this for now. + */ + *count = acl(name, GETACLCNT, 0, NULL); + if (*count < 0) { + DEBUG(10, ("acl GETACLCNT failed: %s\n", strerror(errno))); + goto done; + } + *solaris_acl = solaris_acl_init(*count); + if (*solaris_acl == NULL) { + DEBUG(10, ("error allocating memory for solaris acl...\n")); + goto done; + } + *count = acl(name, GETACL, *count, *solaris_acl); + if (*count < 0) { + DEBUG(10, ("acl GETACL failed: %s\n", strerror(errno))); + goto done; + } + result = True; + + done: + DEBUG(10, ("solaris_acl_get_file %s.\n", + ((result == True) ? "succeeded" : "failed" ))); + return result; +} + + +static bool solaris_acl_get_fd(int fd, SOLARIS_ACL_T *solaris_acl, int *count) +{ + bool ret = False; + + DEBUG(10, ("entering solaris_acl_get_fd\n")); + + /* + * see solaris_acl_get_file for comment about omission + * of INITIAL_ACL_SIZE... + */ + *count = facl(fd, GETACLCNT, 0, NULL); + if (*count < 0) { + DEBUG(10, ("facl GETACLCNT failed: %s\n", strerror(errno))); + goto done; + } + *solaris_acl = solaris_acl_init(*count); + if (*solaris_acl == NULL) { + DEBUG(10, ("error allocating memory for solaris acl...\n")); + goto done; + } + *count = facl(fd, GETACL, *count, *solaris_acl); + if (*count < 0) { + DEBUG(10, ("facl GETACL failed: %s\n", strerror(errno))); + goto done; + } + ret = True; + + done: + DEBUG(10, ("solaris_acl_get_fd %s\n", + ((ret == True) ? "succeeded" : "failed"))); + return ret; +} + + + +/* + * Add entries to a solaris ACL. + * + * Entries are directly added to the solarisacl parameter. + * if memory allocation fails, this may result in solarisacl + * being NULL. if the resulting acl is to be checked and is + * not valid, it is kept in solarisacl but False is returned. + * + * The type of ACEs (access/default) to be added to the ACL can + * be selected via the type parameter. + * I use the SMB_ACL_TYPE_T type here. Since SMB_ACL_TYPE_ACCESS + * is defined as "0", this means that one can only add either + * access or default ACEs, not both at the same time. If it + * should become necessary to add all of an ACL, one would have + * to replace this parameter by another type. + */ +static bool solaris_add_to_acl(SOLARIS_ACL_T *solaris_acl, int *count, + SOLARIS_ACL_T add_acl, int add_count, + SMB_ACL_TYPE_T type) +{ + int i; + + if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT)) + { + DEBUG(10, ("invalid acl type given: %d\n", type)); + errno = EINVAL; + return False; + } + for (i = 0; i < add_count; i++) { + if (!_IS_OF_TYPE(add_acl[i], type)) { + continue; + } + ADD_TO_ARRAY(NULL, SOLARIS_ACE_T, add_acl[i], + solaris_acl, count); + if (solaris_acl == NULL) { + DEBUG(10, ("error enlarging acl.\n")); + errno = ENOMEM; + return False; + } + } + return True; +} + + +/* + * sort the ACL and check it for validity + * + * [original comment from lib/sysacls.c:] + * + * if it's a minimal ACL with only 4 entries then we + * need to recalculate the mask permissions to make + * sure that they are the same as the GROUP_OBJ + * permissions as required by the UnixWare acl() system call. + * + * (note: since POSIX allows minimal ACLs which only contain + * 3 entries - ie there is no mask entry - we should, in theory, + * check for this and add a mask entry if necessary - however + * we "know" that the caller of this interface always specifies + * a mask, so in practice "this never happens" (tm) - if it *does* + * happen aclsort() will fail and return an error and someone will + * have to fix it...) + */ +static bool solaris_acl_sort(SOLARIS_ACL_T solaris_acl, int count) +{ + int fixmask = (count <= 4); + + if (aclsort(count, fixmask, solaris_acl) != 0) { + errno = EINVAL; + return False; + } + return True; +} + +#if 0 +/* + * acl check function: + * unused at the moment but could be used to get more + * concrete error messages for debugging... + * (acl sort just says that the acl is invalid...) + */ +static bool solaris_acl_check(SOLARIS_ACL_T solaris_acl, int count) +{ + int check_rc; + int check_which; + + check_rc = aclcheck(solaris_acl, count, &check_which); + if (check_rc != 0) { + DEBUG(10, ("acl is not valid:\n")); + DEBUGADD(10, (" - return code: %d\n", check_rc)); + DEBUGADD(10, (" - which: %d\n", check_which)); + if (check_which != -1) { + DEBUGADD(10, (" - invalid entry:\n")); + DEBUGADD(10, (" * type: %d:\n", + solaris_acl[check_which].a_type)); + DEBUGADD(10, (" * id: %d\n", + solaris_acl[check_which].a_id)); + DEBUGADD(10, (" * perm: 0o%o\n", + solaris_acl[check_which].a_perm)); + } + return False; + } + return True; +} +#endif + +static struct vfs_fn_pointers solarisacl_fns = { + .sys_acl_get_fd_fn = solarisacl_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = solarisacl_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = solarisacl_sys_acl_delete_def_fd, +}; + +static_decl_vfs; +NTSTATUS vfs_solarisacl_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "solarisacl", + &solarisacl_fns); +} + +/* ENTE */ diff --git a/source3/modules/vfs_solarisacl.h b/source3/modules/vfs_solarisacl.h new file mode 100644 index 0000000..54d538c --- /dev/null +++ b/source3/modules/vfs_solarisacl.h @@ -0,0 +1,44 @@ +/* + Unix SMB/Netbios implementation. + VFS module to get and set Solaris ACLs - prototype header + Copyright (C) Michael Adam 2008 + + 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 . +*/ + +#ifndef __VFS_SOLARISACL_H__ +#define __VFS_SOLARISACL_H__ + +SMB_ACL_T solarisacl_sys_acl_get_file(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx); + +SMB_ACL_T solarisacl_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx); + +int solarisacl_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl); + +int solarisacl_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp); + +NTSTATUS vfs_solarisacl_init(TALLOC_CTX *); + +#endif + diff --git a/source3/modules/vfs_streams_depot.c b/source3/modules/vfs_streams_depot.c new file mode 100644 index 0000000..f9701cc --- /dev/null +++ b/source3/modules/vfs_streams_depot.c @@ -0,0 +1,1218 @@ +/* + * Store streams in a separate subdirectory + * + * Copyright (C) Volker Lendecke, 2007 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "source3/smbd/dir.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* + * Excerpt from a mail from tridge: + * + * Volker, what I'm thinking of is this: + * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream1 + * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream2 + * + * where XX/YY is a 2 level hash based on the fsid/inode. "aaaa.bbbb" + * is the fsid/inode. "namedstreamX" is a file named after the stream + * name. + */ + +static uint32_t hash_fn(DATA_BLOB key) +{ + uint32_t value; /* Used to compute the hash value. */ + uint32_t i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + for (value = 0x238F13AF * key.length, i=0; i < key.length; i++) + value = (value + (key.data[i] << (i*5 % 24))); + + return (1103515243 * value + 12345); +} + +/* + * With the hashing scheme based on the inode we need to protect against + * streams showing up on files with re-used inodes. This can happen if we + * create a stream directory from within Samba, and a local process or NFS + * client deletes the file without deleting the streams directory. When the + * inode is re-used and the stream directory is still around, the streams in + * there would be show up as belonging to the new file. + * + * There are several workarounds for this, probably the easiest one is on + * systems which have a true birthtime stat element: When the file has a later + * birthtime than the streams directory, then we have to recreate the + * directory. + * + * The other workaround is to somehow mark the file as generated by Samba with + * something that a NFS client would not do. The closest one is a special + * xattr value being set. On systems which do not support xattrs, it might be + * an option to put in a special ACL entry for a non-existing group. + */ + +static bool file_is_valid(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + char buf; + NTSTATUS status; + struct smb_filename *pathref = NULL; + int ret; + + DEBUG(10, ("file_is_valid (%s) called\n", smb_fname->base_name)); + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags, + &pathref); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + ret = SMB_VFS_FGETXATTR(pathref->fsp, + SAMBA_XATTR_MARKER, + &buf, + sizeof(buf)); + if (ret != sizeof(buf)) { + int saved_errno = errno; + DBG_DEBUG("FGETXATTR failed: %s\n", strerror(saved_errno)); + TALLOC_FREE(pathref); + errno = saved_errno; + return false; + } + + TALLOC_FREE(pathref); + + if (buf != '1') { + DEBUG(10, ("got wrong buffer content: '%c'\n", buf)); + return false; + } + + return true; +} + +/* + * Return the root of the stream directory. Can be + * external to the share definition but by default + * is "handle->conn->connectpath/.streams". + * + * Note that this is an *absolute* path, starting + * with '/', so the dirfsp being used in the + * calls below isn't looked at. + */ + +static char *stream_rootdir(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *tmp; + + tmp = talloc_asprintf(ctx, + "%s/.streams", + handle->conn->connectpath); + if (tmp == NULL) { + errno = ENOMEM; + return NULL; + } + + return lp_parm_substituted_string(ctx, + lp_sub, + SNUM(handle->conn), + "streams_depot", + "directory", + tmp); +} + +/** + * Given an smb_filename, determine the stream directory using the file's + * base_name. + */ +static char *stream_dir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + const SMB_STRUCT_STAT *base_sbuf, bool create_it) +{ + uint32_t hash; + struct smb_filename *smb_fname_hash = NULL; + char *result = NULL; + SMB_STRUCT_STAT base_sbuf_tmp; + char *tmp = NULL; + uint8_t first, second; + char *id_hex; + struct file_id id; + uint8_t id_buf[16]; + bool check_valid; + char *rootdir = NULL; + struct smb_filename *rootdir_fname = NULL; + struct smb_filename *tmp_fname = NULL; + int ret; + + check_valid = lp_parm_bool(SNUM(handle->conn), + "streams_depot", "check_valid", true); + + rootdir = stream_rootdir(handle, + talloc_tos()); + if (rootdir == NULL) { + errno = ENOMEM; + goto fail; + } + + rootdir_fname = synthetic_smb_fname(talloc_tos(), + rootdir, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (rootdir_fname == NULL) { + errno = ENOMEM; + goto fail; + } + + /* Stat the base file if it hasn't already been done. */ + if (base_sbuf == NULL) { + struct smb_filename *smb_fname_base; + + smb_fname_base = synthetic_smb_fname( + talloc_tos(), + smb_fname->base_name, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (smb_fname_base == NULL) { + errno = ENOMEM; + goto fail; + } + if (SMB_VFS_NEXT_STAT(handle, smb_fname_base) == -1) { + TALLOC_FREE(smb_fname_base); + goto fail; + } + base_sbuf_tmp = smb_fname_base->st; + TALLOC_FREE(smb_fname_base); + } else { + base_sbuf_tmp = *base_sbuf; + } + + id = SMB_VFS_FILE_ID_CREATE(handle->conn, &base_sbuf_tmp); + + push_file_id_16((char *)id_buf, &id); + + hash = hash_fn(data_blob_const(id_buf, sizeof(id_buf))); + + first = hash & 0xff; + second = (hash >> 8) & 0xff; + + id_hex = hex_encode_talloc(talloc_tos(), id_buf, sizeof(id_buf)); + + if (id_hex == NULL) { + errno = ENOMEM; + goto fail; + } + + result = talloc_asprintf(talloc_tos(), "%s/%2.2X/%2.2X/%s", rootdir, + first, second, id_hex); + + TALLOC_FREE(id_hex); + + if (result == NULL) { + errno = ENOMEM; + return NULL; + } + + smb_fname_hash = synthetic_smb_fname(talloc_tos(), + result, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (smb_fname_hash == NULL) { + errno = ENOMEM; + goto fail; + } + + if (SMB_VFS_NEXT_STAT(handle, smb_fname_hash) == 0) { + struct smb_filename *smb_fname_new = NULL; + char *newname; + bool delete_lost; + + if (!S_ISDIR(smb_fname_hash->st.st_ex_mode)) { + errno = EINVAL; + goto fail; + } + + if (!check_valid || + file_is_valid(handle, smb_fname)) { + return result; + } + + /* + * Someone has recreated a file under an existing inode + * without deleting the streams directory. + * Move it away or remove if streams_depot:delete_lost is set. + */ + + again: + delete_lost = lp_parm_bool(SNUM(handle->conn), "streams_depot", + "delete_lost", false); + + if (delete_lost) { + DEBUG(3, ("Someone has recreated a file under an " + "existing inode. Removing: %s\n", + smb_fname_hash->base_name)); + recursive_rmdir(talloc_tos(), handle->conn, + smb_fname_hash); + SMB_VFS_NEXT_UNLINKAT(handle, + handle->conn->cwd_fsp, + smb_fname_hash, + AT_REMOVEDIR); + } else { + newname = talloc_asprintf(talloc_tos(), "lost-%lu", + random()); + DEBUG(3, ("Someone has recreated a file under an " + "existing inode. Renaming: %s to: %s\n", + smb_fname_hash->base_name, + newname)); + if (newname == NULL) { + errno = ENOMEM; + goto fail; + } + + smb_fname_new = synthetic_smb_fname( + talloc_tos(), + newname, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + TALLOC_FREE(newname); + if (smb_fname_new == NULL) { + errno = ENOMEM; + goto fail; + } + + if (SMB_VFS_NEXT_RENAMEAT(handle, + handle->conn->cwd_fsp, + smb_fname_hash, + handle->conn->cwd_fsp, + smb_fname_new) == -1) { + TALLOC_FREE(smb_fname_new); + if ((errno == EEXIST) || (errno == ENOTEMPTY)) { + goto again; + } + goto fail; + } + + TALLOC_FREE(smb_fname_new); + } + } + + if (!create_it) { + errno = ENOENT; + goto fail; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + rootdir_fname, + 0755); + if ((ret != 0) && (errno != EEXIST)) { + goto fail; + } + + tmp = talloc_asprintf(result, "%s/%2.2X", rootdir, first); + if (tmp == NULL) { + errno = ENOMEM; + goto fail; + } + + tmp_fname = synthetic_smb_fname(talloc_tos(), + tmp, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (tmp_fname == NULL) { + errno = ENOMEM; + goto fail; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + tmp_fname, + 0755); + if ((ret != 0) && (errno != EEXIST)) { + goto fail; + } + + TALLOC_FREE(tmp); + TALLOC_FREE(tmp_fname); + + tmp = talloc_asprintf(result, "%s/%2.2X/%2.2X", rootdir, first, + second); + if (tmp == NULL) { + errno = ENOMEM; + goto fail; + } + + tmp_fname = synthetic_smb_fname(talloc_tos(), + tmp, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (tmp_fname == NULL) { + errno = ENOMEM; + goto fail; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + tmp_fname, + 0755); + if ((ret != 0) && (errno != EEXIST)) { + goto fail; + } + + TALLOC_FREE(tmp); + TALLOC_FREE(tmp_fname); + + /* smb_fname_hash is the struct smb_filename version of 'result' */ + ret = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + smb_fname_hash, + 0755); + if ((ret != 0) && (errno != EEXIST)) { + goto fail; + } + + TALLOC_FREE(rootdir_fname); + TALLOC_FREE(rootdir); + TALLOC_FREE(tmp_fname); + TALLOC_FREE(smb_fname_hash); + return result; + + fail: + TALLOC_FREE(rootdir_fname); + TALLOC_FREE(rootdir); + TALLOC_FREE(tmp_fname); + TALLOC_FREE(smb_fname_hash); + TALLOC_FREE(result); + return NULL; +} +/** + * Given a stream name, populate smb_fname_out with the actual location of the + * stream. + */ +static NTSTATUS stream_smb_fname(vfs_handle_struct *handle, + const struct stat_ex *base_sbuf, + const struct smb_filename *smb_fname, + struct smb_filename **smb_fname_out, + bool create_dir) +{ + char *dirname, *stream_fname; + const char *stype; + NTSTATUS status; + + *smb_fname_out = NULL; + + stype = strchr_m(smb_fname->stream_name + 1, ':'); + + if (stype) { + if (strcasecmp_m(stype, ":$DATA") != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + dirname = stream_dir(handle, smb_fname, base_sbuf, create_dir); + + if (dirname == NULL) { + status = map_nt_error_from_unix(errno); + goto fail; + } + + stream_fname = talloc_asprintf(talloc_tos(), "%s/%s", dirname, + smb_fname->stream_name); + + if (stream_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (stype == NULL) { + /* Append an explicit stream type if one wasn't specified. */ + stream_fname = talloc_asprintf(talloc_tos(), "%s:$DATA", + stream_fname); + if (stream_fname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } else { + /* Normalize the stream type to uppercase. */ + if (!strupper_m(strrchr_m(stream_fname, ':') + 1)) { + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + } + + DEBUG(10, ("stream filename = %s\n", stream_fname)); + + /* Create an smb_filename with stream_name == NULL. */ + *smb_fname_out = synthetic_smb_fname(talloc_tos(), + stream_fname, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (*smb_fname_out == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; + + fail: + DEBUG(5, ("stream_name failed: %s\n", strerror(errno))); + TALLOC_FREE(*smb_fname_out); + return status; +} + +static NTSTATUS walk_streams(vfs_handle_struct *handle, + struct smb_filename *smb_fname_base, + char **pdirname, + bool (*fn)(const struct smb_filename *dirname, + const char *dirent, + void *private_data), + void *private_data) +{ + char *dirname; + char *rootdir = NULL; + char *orig_connectpath = NULL; + struct smb_filename *dir_smb_fname = NULL; + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + NTSTATUS status; + + dirname = stream_dir(handle, smb_fname_base, &smb_fname_base->st, + false); + + if (dirname == NULL) { + if (errno == ENOENT) { + /* + * no stream around + */ + return NT_STATUS_OK; + } + return map_nt_error_from_unix(errno); + } + + DEBUG(10, ("walk_streams: dirname=%s\n", dirname)); + + dir_smb_fname = synthetic_smb_fname(talloc_tos(), + dirname, + NULL, + NULL, + smb_fname_base->twrp, + smb_fname_base->flags); + if (dir_smb_fname == NULL) { + TALLOC_FREE(dirname); + return NT_STATUS_NO_MEMORY; + } + + /* + * For OpenDir to succeed if the stream rootdir is outside + * the share path, we must temporarily swap out the connect + * path for this share. We're dealing with absolute paths + * here so we don't care about chdir calls. + */ + rootdir = stream_rootdir(handle, talloc_tos()); + if (rootdir == NULL) { + TALLOC_FREE(dir_smb_fname); + TALLOC_FREE(dirname); + return NT_STATUS_NO_MEMORY; + } + + orig_connectpath = handle->conn->connectpath; + handle->conn->connectpath = rootdir; + + status = OpenDir( + talloc_tos(), handle->conn, dir_smb_fname, NULL, 0, &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + handle->conn->connectpath = orig_connectpath; + TALLOC_FREE(rootdir); + TALLOC_FREE(dir_smb_fname); + TALLOC_FREE(dirname); + return status; + } + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + + DBG_DEBUG("dirent=%s\n", dname); + + if (!fn(dir_smb_fname, dname, private_data)) { + TALLOC_FREE(talloced); + break; + } + TALLOC_FREE(talloced); + } + + /* Restore the original connectpath. */ + handle->conn->connectpath = orig_connectpath; + TALLOC_FREE(rootdir); + TALLOC_FREE(dir_smb_fname); + TALLOC_FREE(dir_hnd); + + if (pdirname != NULL) { + *pdirname = dirname; + } + else { + TALLOC_FREE(dirname); + } + + return NT_STATUS_OK; +} + +static int streams_depot_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct smb_filename *smb_fname_stream = NULL; + NTSTATUS status; + int ret = -1; + + DEBUG(10, ("streams_depot_stat called for [%s]\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + /* Stat the actual stream now. */ + status = stream_smb_fname( + handle, NULL, smb_fname, &smb_fname_stream, false); + if (!NT_STATUS_IS_OK(status)) { + ret = -1; + errno = map_errno_from_nt_status(status); + goto done; + } + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname_stream); + + /* Update the original smb_fname with the stat info. */ + smb_fname->st = smb_fname_stream->st; + done: + TALLOC_FREE(smb_fname_stream); + return ret; +} + + + +static int streams_depot_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct smb_filename *smb_fname_stream = NULL; + NTSTATUS status; + int ret = -1; + + DEBUG(10, ("streams_depot_lstat called for [%s]\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + /* Stat the actual stream now. */ + status = stream_smb_fname( + handle, NULL, smb_fname, &smb_fname_stream, false); + if (!NT_STATUS_IS_OK(status)) { + ret = -1; + errno = map_errno_from_nt_status(status); + goto done; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_stream); + + done: + TALLOC_FREE(smb_fname_stream); + return ret; +} + +static int streams_depot_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + struct smb_filename *smb_fname_stream = NULL; + struct files_struct *fspcwd = NULL; + NTSTATUS status; + bool create_it; + int ret = -1; + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + SMB_ASSERT(dirfsp == NULL); + SMB_ASSERT(VALID_STAT(fsp->base_fsp->fsp_name->st)); + + create_it = (how->flags & O_CREAT); + + /* Determine the stream name, and then open it. */ + status = stream_smb_fname( + handle, + &fsp->base_fsp->fsp_name->st, + fsp->fsp_name, + &smb_fname_stream, + create_it); + if (!NT_STATUS_IS_OK(status)) { + ret = -1; + errno = map_errno_from_nt_status(status); + goto done; + } + + if (create_it) { + bool check_valid = lp_parm_bool( + SNUM(handle->conn), + "streams_depot", + "check_valid", + true); + + if (check_valid) { + char buf = '1'; + + DBG_DEBUG("marking file %s as valid\n", + fsp->base_fsp->fsp_name->base_name); + + ret = SMB_VFS_FSETXATTR( + fsp->base_fsp, + SAMBA_XATTR_MARKER, + &buf, + sizeof(buf), + 0); + + if (ret == -1) { + DBG_DEBUG("FSETXATTR failed: %s\n", + strerror(errno)); + goto done; + } + } + } + + status = vfs_at_fspcwd(talloc_tos(), handle->conn, &fspcwd); + if (!NT_STATUS_IS_OK(status)) { + ret = -1; + errno = map_errno_from_nt_status(status); + goto done; + } + + ret = SMB_VFS_NEXT_OPENAT(handle, + fspcwd, + smb_fname_stream, + fsp, + how); + + done: + TALLOC_FREE(smb_fname_stream); + TALLOC_FREE(fspcwd); + return ret; +} + +static int streams_depot_unlink_internal(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + char *dirname = NULL; + int ret = -1; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DEBUG(10, ("streams_depot_unlink called for %s\n", + smb_fname_str_dbg(full_fname))); + + /* If there is a valid stream, just unlink the stream and return. */ + if (is_named_stream(full_fname)) { + struct smb_filename *smb_fname_stream = NULL; + NTSTATUS status; + + status = stream_smb_fname( + handle, NULL, full_fname, &smb_fname_stream, false); + TALLOC_FREE(full_fname); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + smb_fname_stream, + 0); + + TALLOC_FREE(smb_fname_stream); + return ret; + } + + /* + * We potentially need to delete the per-inode streams directory + */ + + if (full_fname->flags & SMB_FILENAME_POSIX_PATH) { + ret = SMB_VFS_NEXT_LSTAT(handle, full_fname); + } else { + ret = SMB_VFS_NEXT_STAT(handle, full_fname); + if (ret == -1 && (errno == ENOENT || errno == ELOOP)) { + if (VALID_STAT(smb_fname->st) && + S_ISLNK(smb_fname->st.st_ex_mode)) { + /* + * Original name was a link - Could be + * trying to remove a dangling symlink. + */ + ret = SMB_VFS_NEXT_LSTAT(handle, full_fname); + } + } + } + if (ret == -1) { + TALLOC_FREE(full_fname); + return -1; + } + + /* + * We know the unlink should succeed as the ACL + * check is already done in the caller. Remove the + * file *after* the streams. + */ + dirname = stream_dir(handle, + full_fname, + &full_fname->st, + false); + TALLOC_FREE(full_fname); + if (dirname != NULL) { + struct smb_filename *smb_fname_dir = NULL; + + smb_fname_dir = synthetic_smb_fname(talloc_tos(), + dirname, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (smb_fname_dir == NULL) { + TALLOC_FREE(dirname); + errno = ENOMEM; + return -1; + } + + SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + smb_fname_dir, + AT_REMOVEDIR); + TALLOC_FREE(smb_fname_dir); + TALLOC_FREE(dirname); + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + return ret; +} + +static int streams_depot_rmdir_internal(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + struct smb_filename *full_fname = NULL; + struct smb_filename *smb_fname_base = NULL; + int ret = -1; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DBG_DEBUG("called for %s\n", full_fname->base_name); + + /* + * We potentially need to delete the per-inode streams directory + */ + + smb_fname_base = synthetic_smb_fname(talloc_tos(), + full_fname->base_name, + NULL, + NULL, + full_fname->twrp, + full_fname->flags); + TALLOC_FREE(full_fname); + if (smb_fname_base == NULL) { + errno = ENOMEM; + return -1; + } + + if (smb_fname_base->flags & SMB_FILENAME_POSIX_PATH) { + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_base); + } else { + ret = SMB_VFS_NEXT_STAT(handle, smb_fname_base); + } + + if (ret == -1) { + TALLOC_FREE(smb_fname_base); + return -1; + } + + /* + * We know the rmdir should succeed as the ACL + * check is already done in the caller. Remove the + * directory *after* the streams. + */ + { + char *dirname = stream_dir(handle, smb_fname_base, + &smb_fname_base->st, false); + + if (dirname != NULL) { + struct smb_filename *smb_fname_dir = + synthetic_smb_fname(talloc_tos(), + dirname, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (smb_fname_dir == NULL) { + TALLOC_FREE(smb_fname_base); + TALLOC_FREE(dirname); + errno = ENOMEM; + return -1; + } + SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + smb_fname_dir, + AT_REMOVEDIR); + TALLOC_FREE(smb_fname_dir); + } + TALLOC_FREE(dirname); + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + TALLOC_FREE(smb_fname_base); + return ret; +} + +static int streams_depot_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + if (flags & AT_REMOVEDIR) { + ret = streams_depot_rmdir_internal(handle, + dirfsp, + smb_fname); + } else { + ret = streams_depot_unlink_internal(handle, + dirfsp, + smb_fname, + flags); + } + return ret; +} + +static int streams_depot_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + struct smb_filename *smb_fname_src_stream = NULL; + struct smb_filename *smb_fname_dst_stream = NULL; + struct smb_filename *full_src = NULL; + struct smb_filename *full_dst = NULL; + bool src_is_stream, dst_is_stream; + NTSTATUS status; + int ret = -1; + + DEBUG(10, ("streams_depot_renameat called for %s => %s\n", + smb_fname_str_dbg(smb_fname_src), + smb_fname_str_dbg(smb_fname_dst))); + + src_is_stream = is_ntfs_stream_smb_fname(smb_fname_src); + dst_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst); + + if (!src_is_stream && !dst_is_stream) { + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + } + + /* for now don't allow renames from or to the default stream */ + if (is_ntfs_default_stream_smb_fname(smb_fname_src) || + is_ntfs_default_stream_smb_fname(smb_fname_dst)) { + errno = ENOSYS; + goto done; + } + + full_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_src == NULL) { + errno = ENOMEM; + goto done; + } + + full_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_dst == NULL) { + errno = ENOMEM; + goto done; + } + + status = stream_smb_fname( + handle, NULL, full_src, &smb_fname_src_stream, false); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto done; + } + + status = stream_smb_fname( + handle, NULL, full_dst, &smb_fname_dst_stream, false); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto done; + } + + /* + * We must use handle->conn->cwd_fsp as + * srcfsp and dstfsp directory handles here + * as we used the full pathname from the cwd dir + * to calculate the streams directory and filename + * within. + */ + ret = SMB_VFS_NEXT_RENAMEAT(handle, + handle->conn->cwd_fsp, + smb_fname_src_stream, + handle->conn->cwd_fsp, + smb_fname_dst_stream); + +done: + TALLOC_FREE(smb_fname_src_stream); + TALLOC_FREE(smb_fname_dst_stream); + return ret; +} + +static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, off_t size, + off_t alloc_size) +{ + struct stream_struct *tmp; + + tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_strdup(tmp, name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +struct streaminfo_state { + TALLOC_CTX *mem_ctx; + vfs_handle_struct *handle; + unsigned int num_streams; + struct stream_struct *streams; + NTSTATUS status; +}; + +static bool collect_one_stream(const struct smb_filename *dirfname, + const char *dirent, + void *private_data) +{ + const char *dirname = dirfname->base_name; + struct streaminfo_state *state = + (struct streaminfo_state *)private_data; + struct smb_filename *smb_fname = NULL; + char *sname = NULL; + bool ret; + + sname = talloc_asprintf(talloc_tos(), "%s/%s", dirname, dirent); + if (sname == NULL) { + state->status = NT_STATUS_NO_MEMORY; + ret = false; + goto out; + } + + smb_fname = synthetic_smb_fname(talloc_tos(), + sname, + NULL, + NULL, + dirfname->twrp, + 0); + if (smb_fname == NULL) { + state->status = NT_STATUS_NO_MEMORY; + ret = false; + goto out; + } + + if (SMB_VFS_NEXT_STAT(state->handle, smb_fname) == -1) { + DEBUG(10, ("Could not stat %s: %s\n", sname, + strerror(errno))); + ret = true; + goto out; + } + + if (!add_one_stream(state->mem_ctx, + &state->num_streams, &state->streams, + dirent, smb_fname->st.st_ex_size, + SMB_VFS_GET_ALLOC_SIZE(state->handle->conn, NULL, + &smb_fname->st))) { + state->status = NT_STATUS_NO_MEMORY; + ret = false; + goto out; + } + + ret = true; + out: + TALLOC_FREE(sname); + TALLOC_FREE(smb_fname); + return ret; +} + +static NTSTATUS streams_depot_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct smb_filename *smb_fname_base = NULL; + int ret; + NTSTATUS status; + struct streaminfo_state state; + + smb_fname_base = synthetic_smb_fname(talloc_tos(), + fsp->fsp_name->base_name, + NULL, + NULL, + fsp->fsp_name->twrp, + fsp->fsp_name->flags); + if (smb_fname_base == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &smb_fname_base->st); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + goto out; + } + + state.streams = *pstreams; + state.num_streams = *pnum_streams; + state.mem_ctx = mem_ctx; + state.handle = handle; + state.status = NT_STATUS_OK; + + status = walk_streams(handle, + smb_fname_base, + NULL, + collect_one_stream, + &state); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state.streams); + goto out; + } + + if (!NT_STATUS_IS_OK(state.status)) { + TALLOC_FREE(state.streams); + status = state.status; + goto out; + } + + *pnum_streams = state.num_streams; + *pstreams = state.streams; + status = SMB_VFS_NEXT_FSTREAMINFO(handle, + fsp->base_fsp ? fsp->base_fsp : fsp, + mem_ctx, + pnum_streams, + pstreams); + + out: + TALLOC_FREE(smb_fname_base); + return status; +} + +static uint32_t streams_depot_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_NAMED_STREAMS; +} + +static struct vfs_fn_pointers vfs_streams_depot_fns = { + .fs_capabilities_fn = streams_depot_fs_capabilities, + .openat_fn = streams_depot_openat, + .stat_fn = streams_depot_stat, + .lstat_fn = streams_depot_lstat, + .unlinkat_fn = streams_depot_unlinkat, + .renameat_fn = streams_depot_renameat, + .fstreaminfo_fn = streams_depot_fstreaminfo, +}; + +static_decl_vfs; +NTSTATUS vfs_streams_depot_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_depot", + &vfs_streams_depot_fns); +} diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c new file mode 100644 index 0000000..03ff614 --- /dev/null +++ b/source3/modules/vfs_streams_xattr.c @@ -0,0 +1,1614 @@ +/* + * Store streams in xattrs + * + * Copyright (C) Volker Lendecke, 2008 + * + * Partly based on James Peach's Darwin module, which is + * + * Copyright (C) James Peach 2006-2007 + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "lib/util/tevent_unix.h" +#include "librpc/gen_ndr/ioctl.h" +#include "hash_inode.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +struct streams_xattr_config { + const char *prefix; + size_t prefix_len; + bool store_stream_type; +}; + +struct stream_io { + char *base; + char *xattr_name; + void *fsp_name_ptr; + files_struct *fsp; + vfs_handle_struct *handle; +}; + +static ssize_t get_xattr_size_fsp(struct files_struct *fsp, + const char *xattr_name) +{ + NTSTATUS status; + struct ea_struct ea; + ssize_t result; + + status = get_ea_value_fsp(talloc_tos(), + fsp, + xattr_name, + &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + result = ea.value.length-1; + TALLOC_FREE(ea.value.data); + return result; +} + +/** + * Given a stream name, populate xattr_name with the xattr name to use for + * accessing the stream. + */ +static NTSTATUS streams_xattr_get_name(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const char *stream_name, + char **xattr_name) +{ + size_t stream_name_len = strlen(stream_name); + char *stype; + struct streams_xattr_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config, + return NT_STATUS_UNSUCCESSFUL); + + SMB_ASSERT(stream_name[0] == ':'); + stream_name += 1; + + /* + * With vfs_fruit option "fruit:encoding = native" we're + * already converting stream names that contain illegal NTFS + * characters from their on-the-wire Unicode Private Range + * encoding to their native ASCII representation. + * + * As as result the name of xattrs storing the streams (via + * vfs_streams_xattr) may contain a colon, so we have to use + * strrchr_m() instead of strchr_m() for matching the stream + * type suffix. + * + * In check_path_syntax() we've already ensured the streamname + * we got from the client is valid. + */ + stype = strrchr_m(stream_name, ':'); + + if (stype) { + /* + * We only support one stream type: "$DATA" + */ + if (strcasecmp_m(stype, ":$DATA") != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Split name and type */ + stream_name_len = (stype - stream_name); + } + + *xattr_name = talloc_asprintf(ctx, "%s%.*s%s", + config->prefix, + (int)stream_name_len, + stream_name, + config->store_stream_type ? ":$DATA" : ""); + if (*xattr_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10, ("xattr_name: %s, stream_name: %s\n", *xattr_name, + stream_name)); + + return NT_STATUS_OK; +} + +static bool streams_xattr_recheck(struct stream_io *sio) +{ + NTSTATUS status; + char *xattr_name = NULL; + + if (sio->fsp->fsp_name == sio->fsp_name_ptr) { + return true; + } + + if (sio->fsp->fsp_name->stream_name == NULL) { + /* how can this happen */ + errno = EINVAL; + return false; + } + + status = streams_xattr_get_name(sio->handle, talloc_tos(), + sio->fsp->fsp_name->stream_name, + &xattr_name); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + TALLOC_FREE(sio->xattr_name); + TALLOC_FREE(sio->base); + sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp), + xattr_name); + if (sio->xattr_name == NULL) { + DBG_DEBUG("sio->xattr_name==NULL\n"); + return false; + } + TALLOC_FREE(xattr_name); + + sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp), + sio->fsp->fsp_name->base_name); + if (sio->base == NULL) { + DBG_DEBUG("sio->base==NULL\n"); + return false; + } + + sio->fsp_name_ptr = sio->fsp->fsp_name; + + return true; +} + +static int streams_xattr_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int ret = -1; + struct stream_io *io = (struct stream_io *) + VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (io == NULL || !fsp_is_alternate_stream(fsp)) { + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + } + + DBG_DEBUG("streams_xattr_fstat called for %s\n", fsp_str_dbg(io->fsp)); + + if (!streams_xattr_recheck(io)) { + return -1; + } + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp->base_fsp, sbuf); + if (ret == -1) { + return -1; + } + + sbuf->st_ex_size = get_xattr_size_fsp(fsp->base_fsp, + io->xattr_name); + if (sbuf->st_ex_size == -1) { + SET_STAT_INVALID(*sbuf); + return -1; + } + + DEBUG(10, ("sbuf->st_ex_size = %d\n", (int)sbuf->st_ex_size)); + + sbuf->st_ex_ino = hash_inode(sbuf, io->xattr_name); + sbuf->st_ex_mode &= ~S_IFMT; + sbuf->st_ex_mode &= ~S_IFDIR; + sbuf->st_ex_mode |= S_IFREG; + sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1; + + return 0; +} + +static int streams_xattr_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + NTSTATUS status; + int result = -1; + char *xattr_name = NULL; + char *tmp_stream_name = NULL; + struct smb_filename *pathref = NULL; + struct files_struct *fsp = smb_fname->fsp; + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + /* Note if lp_posix_paths() is true, we can never + * get here as is_named_stream() is + * always false. So we never need worry about + * not following links here. */ + + /* Populate the stat struct with info from the base file. */ + tmp_stream_name = smb_fname->stream_name; + smb_fname->stream_name = NULL; + result = SMB_VFS_NEXT_STAT(handle, smb_fname); + smb_fname->stream_name = tmp_stream_name; + + if (result == -1) { + return -1; + } + + /* Derive the xattr name to lookup. */ + status = streams_xattr_get_name(handle, talloc_tos(), + smb_fname->stream_name, &xattr_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return -1; + } + + /* Augment the base file's stat information before returning. */ + if (fsp == NULL) { + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags, + &pathref); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(xattr_name); + SET_STAT_INVALID(smb_fname->st); + errno = ENOENT; + return -1; + } + fsp = pathref->fsp; + } else { + fsp = fsp->base_fsp; + } + + smb_fname->st.st_ex_size = get_xattr_size_fsp(fsp, + xattr_name); + if (smb_fname->st.st_ex_size == -1) { + TALLOC_FREE(xattr_name); + TALLOC_FREE(pathref); + SET_STAT_INVALID(smb_fname->st); + errno = ENOENT; + return -1; + } + + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, xattr_name); + smb_fname->st.st_ex_mode &= ~S_IFMT; + smb_fname->st.st_ex_mode |= S_IFREG; + smb_fname->st.st_ex_blocks = + smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1; + + TALLOC_FREE(xattr_name); + TALLOC_FREE(pathref); + return 0; +} + +static int streams_xattr_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + if (is_named_stream(smb_fname)) { + /* + * There can never be EA's on a symlink. + * Windows will never see a symlink, and + * in SMB_FILENAME_POSIX_PATH mode we don't + * allow EA's on a symlink. + */ + SET_STAT_INVALID(smb_fname->st); + errno = ENOENT; + return -1; + } + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); +} + +static int streams_xattr_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + NTSTATUS status; + struct streams_xattr_config *config = NULL; + struct stream_io *sio = NULL; + struct ea_struct ea; + char *xattr_name = NULL; + int fakefd = -1; + bool set_empty_xattr = false; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config, + return -1); + + DBG_DEBUG("called for %s with flags 0x%x\n", + smb_fname_str_dbg(smb_fname), + how->flags); + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + SMB_ASSERT(dirfsp == NULL); + + status = streams_xattr_get_name(handle, talloc_tos(), + smb_fname->stream_name, &xattr_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto fail; + } + + status = get_ea_value_fsp(talloc_tos(), + fsp->base_fsp, + xattr_name, + &ea); + + DBG_DEBUG("get_ea_value_fsp returned %s\n", nt_errstr(status)); + + if (!NT_STATUS_IS_OK(status)) { + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* + * The base file is not there. This is an error even if + * we got O_CREAT, the higher levels should have created + * the base file for us. + */ + DBG_DEBUG("streams_xattr_open: base file %s not around, " + "returning ENOENT\n", smb_fname->base_name); + errno = ENOENT; + goto fail; + } + + if (!(how->flags & O_CREAT)) { + errno = ENOATTR; + goto fail; + } + + set_empty_xattr = true; + } + + if (how->flags & O_TRUNC) { + set_empty_xattr = true; + } + + if (set_empty_xattr) { + /* + * The attribute does not exist or needs to be truncated + */ + + /* + * Darn, xattrs need at least 1 byte + */ + char null = '\0'; + + DEBUG(10, ("creating or truncating attribute %s on file %s\n", + xattr_name, smb_fname->base_name)); + + ret = SMB_VFS_FSETXATTR(fsp->base_fsp, + xattr_name, + &null, sizeof(null), + how->flags & O_EXCL ? XATTR_CREATE : 0); + if (ret != 0) { + goto fail; + } + } + + fakefd = vfs_fake_fd(); + + sio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct stream_io, NULL); + if (sio == NULL) { + errno = ENOMEM; + goto fail; + } + + sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp), + xattr_name); + if (sio->xattr_name == NULL) { + errno = ENOMEM; + goto fail; + } + + /* + * so->base needs to be a copy of fsp->fsp_name->base_name, + * making it identical to streams_xattr_recheck(). If the + * open is changing directories, fsp->fsp_name->base_name + * will be the full path from the share root, whilst + * smb_fname will be relative to the $cwd. + */ + sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp), + fsp->fsp_name->base_name); + if (sio->base == NULL) { + errno = ENOMEM; + goto fail; + } + + sio->fsp_name_ptr = fsp->fsp_name; + sio->handle = handle; + sio->fsp = fsp; + + return fakefd; + + fail: + if (fakefd >= 0) { + vfs_fake_fd_close(fakefd); + fakefd = -1; + } + + return -1; +} + +static int streams_xattr_close(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + int fd; + + fd = fsp_get_pathref_fd(fsp); + + DBG_DEBUG("streams_xattr_close called [%s] fd [%d]\n", + smb_fname_str_dbg(fsp->fsp_name), fd); + + if (!fsp_is_alternate_stream(fsp)) { + return SMB_VFS_NEXT_CLOSE(handle, fsp); + } + + ret = vfs_fake_fd_close(fd); + fsp_set_fd(fsp, -1); + + return ret; +} + +static int streams_xattr_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + NTSTATUS status; + int ret = -1; + char *xattr_name = NULL; + struct smb_filename *pathref = NULL; + struct files_struct *fsp = smb_fname->fsp; + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + } + + /* A stream can never be rmdir'ed */ + SMB_ASSERT((flags & AT_REMOVEDIR) == 0); + + status = streams_xattr_get_name(handle, talloc_tos(), + smb_fname->stream_name, &xattr_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto fail; + } + + if (fsp == NULL) { + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + NULL, + NULL, + smb_fname->twrp, + smb_fname->flags, + &pathref); + if (!NT_STATUS_IS_OK(status)) { + errno = ENOENT; + goto fail; + } + fsp = pathref->fsp; + } else { + SMB_ASSERT(fsp_is_alternate_stream(smb_fname->fsp)); + fsp = fsp->base_fsp; + } + + ret = SMB_VFS_FREMOVEXATTR(fsp, xattr_name); + + if ((ret == -1) && (errno == ENOATTR)) { + errno = ENOENT; + goto fail; + } + + ret = 0; + + fail: + TALLOC_FREE(xattr_name); + TALLOC_FREE(pathref); + return ret; +} + +static int streams_xattr_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + NTSTATUS status; + int ret = -1; + char *src_xattr_name = NULL; + char *dst_xattr_name = NULL; + bool src_is_stream, dst_is_stream; + ssize_t oret; + ssize_t nret; + struct ea_struct ea; + struct smb_filename *pathref_src = NULL; + struct smb_filename *pathref_dst = NULL; + struct smb_filename *full_src = NULL; + struct smb_filename *full_dst = NULL; + + src_is_stream = is_ntfs_stream_smb_fname(smb_fname_src); + dst_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst); + + if (!src_is_stream && !dst_is_stream) { + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + } + + /* For now don't allow renames from or to the default stream. */ + if (is_ntfs_default_stream_smb_fname(smb_fname_src) || + is_ntfs_default_stream_smb_fname(smb_fname_dst)) { + errno = ENOSYS; + goto done; + } + + /* Don't rename if the streams are identical. */ + if (strcasecmp_m(smb_fname_src->stream_name, + smb_fname_dst->stream_name) == 0) { + goto done; + } + + /* Get the xattr names. */ + status = streams_xattr_get_name(handle, talloc_tos(), + smb_fname_src->stream_name, + &src_xattr_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto fail; + } + status = streams_xattr_get_name(handle, talloc_tos(), + smb_fname_dst->stream_name, + &dst_xattr_name); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto fail; + } + + full_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_src == NULL) { + errno = ENOMEM; + goto fail; + } + full_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_dst == NULL) { + errno = ENOMEM; + goto fail; + } + + /* Get a pathref for full_src (base file, no stream name). */ + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + full_src->base_name, + NULL, + NULL, + full_src->twrp, + full_src->flags, + &pathref_src); + if (!NT_STATUS_IS_OK(status)) { + errno = ENOENT; + goto fail; + } + + /* Read the old stream from the base file fsp. */ + status = get_ea_value_fsp(talloc_tos(), + pathref_src->fsp, + src_xattr_name, + &ea); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + goto fail; + } + + /* Get a pathref for full_dst (base file, no stream name). */ + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + full_dst->base_name, + NULL, + NULL, + full_dst->twrp, + full_dst->flags, + &pathref_dst); + if (!NT_STATUS_IS_OK(status)) { + errno = ENOENT; + goto fail; + } + + /* (Over)write the new stream on the base file fsp. */ + nret = SMB_VFS_FSETXATTR( + pathref_dst->fsp, + dst_xattr_name, + ea.value.data, + ea.value.length, + 0); + if (nret < 0) { + if (errno == ENOATTR) { + errno = ENOENT; + } + goto fail; + } + + /* + * Remove the old stream from the base file fsp. + */ + oret = SMB_VFS_FREMOVEXATTR(pathref_src->fsp, + src_xattr_name); + if (oret < 0) { + if (errno == ENOATTR) { + errno = ENOENT; + } + goto fail; + } + + done: + errno = 0; + ret = 0; + fail: + TALLOC_FREE(pathref_src); + TALLOC_FREE(pathref_dst); + TALLOC_FREE(full_src); + TALLOC_FREE(full_dst); + TALLOC_FREE(src_xattr_name); + TALLOC_FREE(dst_xattr_name); + return ret; +} + +static NTSTATUS walk_xattr_streams(vfs_handle_struct *handle, + files_struct *fsp, + const struct smb_filename *smb_fname, + bool (*fn)(struct ea_struct *ea, + void *private_data), + void *private_data) +{ + NTSTATUS status; + char **names; + size_t i, num_names; + struct streams_xattr_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config, + return NT_STATUS_UNSUCCESSFUL); + + status = get_ea_names_from_fsp(talloc_tos(), + smb_fname->fsp, + &names, + &num_names); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (i=0; iprefix, + config->prefix_len) != 0) { + continue; + } + + status = get_ea_value_fsp(names, + smb_fname->fsp, + names[i], + &ea); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Could not get ea %s for file %s: %s\n", + names[i], + smb_fname->base_name, + nt_errstr(status))); + continue; + } + + ea.name = talloc_asprintf( + ea.value.data, ":%s%s", + names[i] + config->prefix_len, + config->store_stream_type ? "" : ":$DATA"); + if (ea.name == NULL) { + DEBUG(0, ("talloc failed\n")); + continue; + } + + if (!fn(&ea, private_data)) { + TALLOC_FREE(ea.value.data); + return NT_STATUS_OK; + } + + TALLOC_FREE(ea.value.data); + } + + TALLOC_FREE(names); + return NT_STATUS_OK; +} + +static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, off_t size, + off_t alloc_size) +{ + struct stream_struct *tmp; + + tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_strdup(tmp, name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +struct streaminfo_state { + TALLOC_CTX *mem_ctx; + vfs_handle_struct *handle; + unsigned int num_streams; + struct stream_struct *streams; + NTSTATUS status; +}; + +static bool collect_one_stream(struct ea_struct *ea, void *private_data) +{ + struct streaminfo_state *state = + (struct streaminfo_state *)private_data; + + if (!add_one_stream(state->mem_ctx, + &state->num_streams, &state->streams, + ea->name, ea->value.length-1, + smb_roundup(state->handle->conn, + ea->value.length-1))) { + state->status = NT_STATUS_NO_MEMORY; + return false; + } + + return true; +} + +static NTSTATUS streams_xattr_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + NTSTATUS status; + struct streaminfo_state state; + + state.streams = *pstreams; + state.num_streams = *pnum_streams; + state.mem_ctx = mem_ctx; + state.handle = handle; + state.status = NT_STATUS_OK; + + status = walk_xattr_streams(handle, + fsp, + fsp->fsp_name, + collect_one_stream, + &state); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state.streams); + return status; + } + + if (!NT_STATUS_IS_OK(state.status)) { + TALLOC_FREE(state.streams); + return state.status; + } + + *pnum_streams = state.num_streams; + *pstreams = state.streams; + + return SMB_VFS_NEXT_FSTREAMINFO(handle, + fsp, + mem_ctx, + pnum_streams, + pstreams); +} + +static uint32_t streams_xattr_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_NAMED_STREAMS; +} + +static int streams_xattr_connect(vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct streams_xattr_config *config; + const char *default_prefix = SAMBA_XATTR_DOSSTREAM_PREFIX; + const char *prefix; + int rc; + + rc = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (rc != 0) { + return rc; + } + + config = talloc_zero(handle->conn, struct streams_xattr_config); + if (config == NULL) { + DEBUG(1, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + prefix = lp_parm_const_string(SNUM(handle->conn), + "streams_xattr", "prefix", + default_prefix); + config->prefix = talloc_strdup(config, prefix); + if (config->prefix == NULL) { + DEBUG(1, ("talloc_strdup() failed\n")); + errno = ENOMEM; + return -1; + } + config->prefix_len = strlen(config->prefix); + DEBUG(10, ("streams_xattr using stream prefix: %s\n", config->prefix)); + + config->store_stream_type = lp_parm_bool(SNUM(handle->conn), + "streams_xattr", + "store_stream_type", + true); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct stream_xattr_config, + return -1); + + return 0; +} + +static ssize_t streams_xattr_pwrite(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + struct ea_struct ea; + NTSTATUS status; + int ret; + + DEBUG(10, ("streams_xattr_pwrite called for %d bytes\n", (int)n)); + + if (sio == NULL) { + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + } + + if (!streams_xattr_recheck(sio)) { + return -1; + } + + if ((offset + n) >= lp_smbd_max_xattr_size(SNUM(handle->conn))) { + /* + * Requested write is beyond what can be read based on + * samba configuration. + * ReFS returns STATUS_FILESYSTEM_LIMITATION, which causes + * entire file to be skipped by File Explorer. VFAT returns + * NT_STATUS_OBJECT_NAME_COLLISION causes user to be prompted + * to skip writing metadata, but copy data. + */ + DBG_ERR("Write to xattr [%s] on file [%s] exceeds maximum " + "supported extended attribute size. " + "Depending on filesystem type and operating system " + "(OS) specifics, this value may be increased using " + "the value of the parameter: " + "smbd max xattr size = . Consult OS and " + "filesystem manpages prior to increasing this limit.\n", + sio->xattr_name, sio->base); + errno = EOVERFLOW; + return -1; + } + + status = get_ea_value_fsp(talloc_tos(), + fsp->base_fsp, + sio->xattr_name, + &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + if ((offset + n) > ea.value.length-1) { + uint8_t *tmp; + + tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t, + offset + n + 1); + + if (tmp == NULL) { + TALLOC_FREE(ea.value.data); + errno = ENOMEM; + return -1; + } + ea.value.data = tmp; + ea.value.length = offset + n + 1; + ea.value.data[offset+n] = 0; + } + + memcpy(ea.value.data + offset, data, n); + + ret = SMB_VFS_FSETXATTR(fsp->base_fsp, + sio->xattr_name, + ea.value.data, + ea.value.length, + 0); + TALLOC_FREE(ea.value.data); + + if (ret == -1) { + return -1; + } + + return n; +} + +static ssize_t streams_xattr_pread(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + struct ea_struct ea; + NTSTATUS status; + size_t length, overlap; + + DEBUG(10, ("streams_xattr_pread: offset=%d, size=%d\n", + (int)offset, (int)n)); + + if (sio == NULL) { + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + } + + if (!streams_xattr_recheck(sio)) { + return -1; + } + + status = get_ea_value_fsp(talloc_tos(), + fsp->base_fsp, + sio->xattr_name, + &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + length = ea.value.length-1; + + DBG_DEBUG("get_ea_value_fsp returned %d bytes\n", + (int)length); + + /* Attempt to read past EOF. */ + if (length <= offset) { + return 0; + } + + overlap = (offset + n) > length ? (length - offset) : n; + memcpy(data, ea.value.data + offset, overlap); + + TALLOC_FREE(ea.value.data); + return overlap; +} + +struct streams_xattr_pread_state { + ssize_t nread; + struct vfs_aio_state vfs_aio_state; +}; + +static void streams_xattr_pread_done(struct tevent_req *subreq); + +static struct tevent_req *streams_xattr_pread_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct streams_xattr_pread_state *state = NULL; + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct streams_xattr_pread_state); + if (req == NULL) { + return NULL; + } + + if (sio == NULL) { + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, streams_xattr_pread_done, req); + return req; + } + + state->nread = SMB_VFS_PREAD(fsp, data, n, offset); + if (state->nread != n) { + if (state->nread != -1) { + errno = EIO; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void streams_xattr_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct streams_xattr_pread_state *state = tevent_req_data( + req, struct streams_xattr_pread_state); + + state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t streams_xattr_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct streams_xattr_pread_state *state = tevent_req_data( + req, struct streams_xattr_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->nread; +} + +struct streams_xattr_pwrite_state { + ssize_t nwritten; + struct vfs_aio_state vfs_aio_state; +}; + +static void streams_xattr_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *streams_xattr_pwrite_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct streams_xattr_pwrite_state *state = NULL; + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct streams_xattr_pwrite_state); + if (req == NULL) { + return NULL; + } + + if (sio == NULL) { + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, streams_xattr_pwrite_done, req); + return req; + } + + state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset); + if (state->nwritten != n) { + if (state->nwritten != -1) { + errno = EIO; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void streams_xattr_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct streams_xattr_pwrite_state *state = tevent_req_data( + req, struct streams_xattr_pwrite_state); + + state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t streams_xattr_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct streams_xattr_pwrite_state *state = tevent_req_data( + req, struct streams_xattr_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->nwritten; +} + +static int streams_xattr_ftruncate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + int ret; + uint8_t *tmp; + struct ea_struct ea; + NTSTATUS status; + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + DEBUG(10, ("streams_xattr_ftruncate called for file %s offset %.0f\n", + fsp_str_dbg(fsp), (double)offset)); + + if (sio == NULL) { + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); + } + + if (!streams_xattr_recheck(sio)) { + return -1; + } + + status = get_ea_value_fsp(talloc_tos(), + fsp->base_fsp, + sio->xattr_name, + &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t, + offset + 1); + + if (tmp == NULL) { + TALLOC_FREE(ea.value.data); + errno = ENOMEM; + return -1; + } + + /* Did we expand ? */ + if (ea.value.length < offset + 1) { + memset(&tmp[ea.value.length], '\0', + offset + 1 - ea.value.length); + } + + ea.value.data = tmp; + ea.value.length = offset + 1; + ea.value.data[offset] = 0; + + ret = SMB_VFS_FSETXATTR(fsp->base_fsp, + sio->xattr_name, + ea.value.data, + ea.value.length, + 0); + + TALLOC_FREE(ea.value.data); + + if (ret == -1) { + return -1; + } + + return 0; +} + +static int streams_xattr_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + DEBUG(10, ("streams_xattr_fallocate called for file %s offset %.0f" + "len = %.0f\n", + fsp_str_dbg(fsp), (double)offset, (double)len)); + + if (sio == NULL) { + return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + } + + if (!streams_xattr_recheck(sio)) { + return -1; + } + + /* Let the pwrite code path handle it. */ + errno = ENOSYS; + return -1; +} + +static int streams_xattr_fchown(vfs_handle_struct *handle, files_struct *fsp, + uid_t uid, gid_t gid) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid); + } + + return 0; +} + +static int streams_xattr_fchmod(vfs_handle_struct *handle, + files_struct *fsp, + mode_t mode) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + } + + return 0; +} + +static ssize_t streams_xattr_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + void *value, + size_t size) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); + } + + errno = ENOTSUP; + return -1; +} + +static ssize_t streams_xattr_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + char *list, + size_t size) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); + } + + errno = ENOTSUP; + return -1; +} + +static int streams_xattr_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); + } + + errno = ENOTSUP; + return -1; +} + +static int streams_xattr_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + const void *value, + size_t size, + int flags) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, + size, flags); + } + + errno = ENOTSUP; + return -1; +} + +struct streams_xattr_fsync_state { + int ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void streams_xattr_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *streams_xattr_fsync_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct streams_xattr_fsync_state *state = NULL; + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct streams_xattr_fsync_state); + if (req == NULL) { + return NULL; + } + + if (sio == NULL) { + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, streams_xattr_fsync_done, req); + return req; + } + + /* + * There's no pathname based sync variant and we don't have access to + * the basefile handle, so we can't do anything here. + */ + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void streams_xattr_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct streams_xattr_fsync_state *state = tevent_req_data( + req, struct streams_xattr_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + if (state->ret != 0) { + tevent_req_error(req, errno); + return; + } + + tevent_req_done(req); +} + +static int streams_xattr_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct streams_xattr_fsync_state *state = tevent_req_data( + req, struct streams_xattr_fsync_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static bool streams_xattr_lock(vfs_handle_struct *handle, + files_struct *fsp, + int op, + off_t offset, + off_t count, + int type) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type); + } + + return true; +} + +static bool streams_xattr_getlock(vfs_handle_struct *handle, + files_struct *fsp, + off_t *poffset, + off_t *pcount, + int *ptype, + pid_t *ppid) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, + pcount, ptype, ppid); + } + + errno = ENOTSUP; + return false; +} + +static int streams_xattr_filesystem_sharemode(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle, + fsp, + share_access, + access_mask); + } + + return 0; +} + +static int streams_xattr_linux_setlease(vfs_handle_struct *handle, + files_struct *fsp, + int leasetype) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype); + } + + return 0; +} + +static bool streams_xattr_strict_lock_check(struct vfs_handle_struct *handle, + files_struct *fsp, + struct lock_struct *plock) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (sio == NULL) { + return SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock); + } + + return true; +} + +static int streams_xattr_fcntl(vfs_handle_struct *handle, + files_struct *fsp, + int cmd, + va_list cmd_arg) +{ + va_list dup_cmd_arg; + void *arg; + int ret; + + if (fsp_is_alternate_stream(fsp)) { + switch (cmd) { + case F_GETFL: + case F_SETFL: + break; + default: + DBG_ERR("Unsupported fcntl() cmd [%d] on [%s]\n", + cmd, fsp_str_dbg(fsp)); + errno = EINVAL; + return -1; + } + } + + va_copy(dup_cmd_arg, cmd_arg); + arg = va_arg(dup_cmd_arg, void *); + + ret = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg); + + va_end(dup_cmd_arg); + + return ret; +} + +static struct vfs_fn_pointers vfs_streams_xattr_fns = { + .fs_capabilities_fn = streams_xattr_fs_capabilities, + .connect_fn = streams_xattr_connect, + .openat_fn = streams_xattr_openat, + .close_fn = streams_xattr_close, + .stat_fn = streams_xattr_stat, + .fstat_fn = streams_xattr_fstat, + .lstat_fn = streams_xattr_lstat, + .pread_fn = streams_xattr_pread, + .pwrite_fn = streams_xattr_pwrite, + .pread_send_fn = streams_xattr_pread_send, + .pread_recv_fn = streams_xattr_pread_recv, + .pwrite_send_fn = streams_xattr_pwrite_send, + .pwrite_recv_fn = streams_xattr_pwrite_recv, + .unlinkat_fn = streams_xattr_unlinkat, + .renameat_fn = streams_xattr_renameat, + .ftruncate_fn = streams_xattr_ftruncate, + .fallocate_fn = streams_xattr_fallocate, + .fstreaminfo_fn = streams_xattr_fstreaminfo, + + .fsync_send_fn = streams_xattr_fsync_send, + .fsync_recv_fn = streams_xattr_fsync_recv, + + .lock_fn = streams_xattr_lock, + .getlock_fn = streams_xattr_getlock, + .filesystem_sharemode_fn = streams_xattr_filesystem_sharemode, + .linux_setlease_fn = streams_xattr_linux_setlease, + .strict_lock_check_fn = streams_xattr_strict_lock_check, + .fcntl_fn = streams_xattr_fcntl, + + .fchown_fn = streams_xattr_fchown, + .fchmod_fn = streams_xattr_fchmod, + + .fgetxattr_fn = streams_xattr_fgetxattr, + .flistxattr_fn = streams_xattr_flistxattr, + .fremovexattr_fn = streams_xattr_fremovexattr, + .fsetxattr_fn = streams_xattr_fsetxattr, +}; + +static_decl_vfs; +NTSTATUS vfs_streams_xattr_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_xattr", + &vfs_streams_xattr_fns); +} diff --git a/source3/modules/vfs_syncops.c b/source3/modules/vfs_syncops.c new file mode 100644 index 0000000..a0d9809 --- /dev/null +++ b/source3/modules/vfs_syncops.c @@ -0,0 +1,418 @@ +/* + * ensure meta data operations are performed synchronously + * + * Copyright (C) Andrew Tridgell 2007 + * Copyright (C) Christian Ambach, 2010-2011 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "source3/smbd/dir.h" + +/* + + Some filesystems (even some journaled filesystems) require that a + fsync() be performed on many meta data operations to ensure that the + operation is guaranteed to remain in the filesystem after a power + failure. This is particularly important for some cluster filesystems + which are participating in a node failover system with clustered + Samba + + On those filesystems this module provides a way to perform those + operations safely. + + most of the performance loss with this module is in fsync on close(). + You can disable that with + syncops:onclose = no + that can be set either globally or per share. + + On certain filesystems that only require the last data written to be + fsync()'ed, you can disable the metadata synchronization of this module with + syncops:onmeta = no + This option can be set either globally or per share. + + you can also disable the module completely for a share with + syncops:disable = true + + */ + +struct syncops_config_data { + bool onclose; + bool onmeta; + bool disable; +}; + +/* + given a filename, find the parent directory + */ +static char *parent_dir(TALLOC_CTX *mem_ctx, const char *name) +{ + const char *p = strrchr(name, '/'); + if (p == NULL) { + return talloc_strdup(mem_ctx, "."); + } + return talloc_strndup(mem_ctx, name, (p+1) - name); +} + +/* + fsync a directory by name + */ +static void syncops_sync_directory(connection_struct *conn, + char *dname) +{ + struct smb_Dir *dir_hnd = NULL; + struct files_struct *dirfsp = NULL; + struct smb_filename smb_dname = { .base_name = dname }; + NTSTATUS status; + + status = OpenDir(talloc_tos(), + conn, + &smb_dname, + "*", + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return; + } + + dirfsp = dir_hnd_fetch_fsp(dir_hnd); + + smb_vfs_fsync_sync(dirfsp); + + TALLOC_FREE(dir_hnd); +} + +/* + sync two meta data changes for 2 names + */ +static void syncops_two_names(connection_struct *conn, + const struct smb_filename *name1, + const struct smb_filename *name2) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + char *parent1, *parent2; + parent1 = parent_dir(tmp_ctx, name1->base_name); + parent2 = parent_dir(tmp_ctx, name2->base_name); + if (!parent1 || !parent2) { + talloc_free(tmp_ctx); + return; + } + syncops_sync_directory(conn, parent1); + if (strcmp(parent1, parent2) != 0) { + syncops_sync_directory(conn, parent2); + } + talloc_free(tmp_ctx); +} + +/* + sync two meta data changes for 1 names + */ +static void syncops_smb_fname(connection_struct *conn, + const struct smb_filename *smb_fname) +{ + char *parent = NULL; + if (smb_fname != NULL) { + parent = parent_dir(NULL, smb_fname->base_name); + if (parent != NULL) { + syncops_sync_directory(conn, parent); + talloc_free(parent); + } + } +} + + +/* + renameat needs special handling, as we may need to fsync two directories + */ +static int syncops_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + + int ret; + struct smb_filename *full_fname_src = NULL; + struct smb_filename *full_fname_dst = NULL; + struct syncops_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct syncops_config_data, + return -1); + + ret = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (ret == -1) { + return ret; + } + if (config->disable) { + return ret; + } + if (!config->onmeta) { + return ret; + } + + full_fname_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_fname_src == NULL) { + errno = ENOMEM; + return ret; + } + full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_fname_dst == NULL) { + TALLOC_FREE(full_fname_src); + errno = ENOMEM; + return ret; + } + syncops_two_names(handle->conn, + full_fname_src, + full_fname_dst); + TALLOC_FREE(full_fname_src); + TALLOC_FREE(full_fname_dst); + return ret; +} + +#define SYNCOPS_NEXT_SMB_FNAME(op, fname, args) do { \ + int ret; \ + struct smb_filename *full_fname = NULL; \ + struct syncops_config_data *config; \ + SMB_VFS_HANDLE_GET_DATA(handle, config, \ + struct syncops_config_data, \ + return -1); \ + ret = SMB_VFS_NEXT_ ## op args; \ + if (ret != 0) { \ + return ret; \ + } \ + if (config->disable) { \ + return ret; \ + } \ + if (!config->onmeta) { \ + return ret; \ + } \ + full_fname = full_path_from_dirfsp_atname(talloc_tos(), \ + dirfsp, \ + smb_fname); \ + if (full_fname == NULL) { \ + return ret; \ + } \ + syncops_smb_fname(dirfsp->conn, full_fname); \ + TALLOC_FREE(full_fname); \ + return ret; \ +} while (0) + +static int syncops_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + SYNCOPS_NEXT_SMB_FNAME(SYMLINKAT, + smb_fname, + (handle, + link_contents, + dirfsp, + smb_fname)); +} + +static int syncops_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int ret; + struct syncops_config_data *config; + struct smb_filename *old_full_fname = NULL; + struct smb_filename *new_full_fname = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct syncops_config_data, + return -1); + + ret = SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); + + if (ret == -1) { + return ret; + } + if (config->disable) { + return ret; + } + if (!config->onmeta) { + return ret; + } + + old_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (old_full_fname == NULL) { + return ret; + } + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + TALLOC_FREE(old_full_fname); + return ret; + } + syncops_two_names(handle->conn, + old_full_fname, + new_full_fname); + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + return ret; +} + +static int syncops_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + SYNCOPS_NEXT_SMB_FNAME(OPENAT, (how->flags & O_CREAT ? smb_fname : NULL), + (handle, dirfsp, smb_fname, fsp, how)); +} + +static int syncops_unlinkat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + SYNCOPS_NEXT_SMB_FNAME(UNLINKAT, + smb_fname, + (handle, + dirfsp, + smb_fname, + flags)); +} + +static int syncops_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + SYNCOPS_NEXT_SMB_FNAME(MKNODAT, + smb_fname, + (handle, + dirfsp, + smb_fname, + mode, + dev)); +} + +static int syncops_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + SYNCOPS_NEXT_SMB_FNAME(MKDIRAT, + full_fname, + (handle, + dirfsp, + smb_fname, + mode)); +} + +/* close needs to be handled specially */ +static int syncops_close(vfs_handle_struct *handle, files_struct *fsp) +{ + struct syncops_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct syncops_config_data, + return -1); + + if (fsp->fsp_flags.can_write && config->onclose) { + /* ideally we'd only do this if we have written some + data, but there is no flag for that in fsp yet. */ + fsync(fsp_get_io_fd(fsp)); + } + return SMB_VFS_NEXT_CLOSE(handle, fsp); +} + +static int syncops_connect(struct vfs_handle_struct *handle, const char *service, + const char *user) +{ + + struct syncops_config_data *config; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + config = talloc_zero(handle->conn, struct syncops_config_data); + if (!config) { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0, ("talloc_zero() failed\n")); + return -1; + } + + config->onclose = lp_parm_bool(SNUM(handle->conn), "syncops", + "onclose", true); + + config->onmeta = lp_parm_bool(SNUM(handle->conn), "syncops", + "onmeta", true); + + config->disable = lp_parm_bool(SNUM(handle->conn), "syncops", + "disable", false); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct syncops_config_data, + return -1); + + return 0; + +} + +static struct vfs_fn_pointers vfs_syncops_fns = { + .connect_fn = syncops_connect, + .mkdirat_fn = syncops_mkdirat, + .openat_fn = syncops_openat, + .renameat_fn = syncops_renameat, + .unlinkat_fn = syncops_unlinkat, + .symlinkat_fn = syncops_symlinkat, + .linkat_fn = syncops_linkat, + .mknodat_fn = syncops_mknodat, + .close_fn = syncops_close, +}; + +static_decl_vfs; +NTSTATUS vfs_syncops_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "syncops", + &vfs_syncops_fns); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + return ret; +} diff --git a/source3/modules/vfs_time_audit.c b/source3/modules/vfs_time_audit.c new file mode 100644 index 0000000..59bc688 --- /dev/null +++ b/source3/modules/vfs_time_audit.c @@ -0,0 +1,2812 @@ +/* + * Time auditing VFS module for samba. Log time taken for VFS call to syslog + * facility. + * + * Copyright (C) Abhidnya Chirmule 2009 + * + * 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 . + */ + +/* + * This module implements logging for time taken for all Samba VFS operations. + * + * vfs objects = time_audit + */ + + +#include "includes.h" +#include "smbd/smbd.h" +#include "ntioctl.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/tevent_ntstatus.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static double audit_timeout; + +static void smb_time_audit_log_msg(const char *syscallname, double elapsed, + const char *msg) +{ + DEBUG(0, ("WARNING: VFS call \"%s\" took unexpectedly long " + "(%.2f seconds) %s%s-- Validate that file and storage " + "subsystems are operating normally\n", syscallname, + elapsed, (msg != NULL) ? msg : "", + (msg != NULL) ? " " : "")); +} + +static void smb_time_audit_log(const char *syscallname, double elapsed) +{ + smb_time_audit_log_msg(syscallname, elapsed, NULL); +} + +static void smb_time_audit_log_fsp(const char *syscallname, double elapsed, + const struct files_struct *fsp) +{ + char *base_name = NULL; + char *connectpath = NULL; + char *msg = NULL; + + if (fsp == NULL) { + smb_time_audit_log(syscallname, elapsed); + return; + } + if (fsp->conn) + connectpath = fsp->conn->connectpath; + if (fsp->fsp_name) + base_name = fsp->fsp_name->base_name; + + if (connectpath != NULL && base_name != NULL) { + msg = talloc_asprintf(talloc_tos(), "filename = \"%s/%s\"", + connectpath, base_name); + } else if (connectpath != NULL && base_name == NULL) { + msg = talloc_asprintf(talloc_tos(), "connectpath = \"%s\", " + "base_name = ", + connectpath); + } else if (connectpath == NULL && base_name != NULL) { + msg = talloc_asprintf(talloc_tos(), "connectpath = , " + "base_name = \"%s\"", + base_name); + } else { /* connectpath == NULL && base_name == NULL */ + msg = talloc_asprintf(talloc_tos(), "connectpath = , " + "base_name = "); + } + smb_time_audit_log_msg(syscallname, elapsed, msg); + TALLOC_FREE(msg); +} + +static void smb_time_audit_log_at(const char *syscallname, + double elapsed, + const struct files_struct *dir_fsp, + const struct smb_filename *smb_fname) +{ + char *msg = NULL; + + msg = talloc_asprintf(talloc_tos(), + "filename = \"%s/%s/%s\"", + dir_fsp->conn->connectpath, + dir_fsp->fsp_name->base_name, + smb_fname->base_name); + + smb_time_audit_log_msg(syscallname, elapsed, msg); + TALLOC_FREE(msg); +} + +static void smb_time_audit_log_fname(const char *syscallname, double elapsed, + const char *fname) +{ + char cwd[PATH_MAX]; + char *msg = NULL; + + if (getcwd(cwd, sizeof(cwd)) == NULL) { + snprintf(cwd, sizeof(cwd), "", errno); + } + if (fname != NULL) { + msg = talloc_asprintf(talloc_tos(), + "cwd = \"%s\", filename = \"%s\"", + cwd, fname); + } else { + msg = talloc_asprintf(talloc_tos(), + "cwd = \"%s\", filename = ", + cwd); + } + smb_time_audit_log_msg(syscallname, elapsed, msg); + TALLOC_FREE(msg); +} + +static void smb_time_audit_log_smb_fname(const char *syscallname, double elapsed, + const struct smb_filename *smb_fname) +{ + if (smb_fname != NULL) { + smb_time_audit_log_fname(syscallname, elapsed, + smb_fname->base_name); + } else { + smb_time_audit_log_fname(syscallname, elapsed, + "smb_fname = "); + } +} + +static int smb_time_audit_connect(vfs_handle_struct *handle, + const char *svc, const char *user) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + if (!handle) { + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CONNECT(handle, svc, user); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + if (timediff > audit_timeout) { + smb_time_audit_log_msg("connect", timediff, user); + } + return result; +} + +static void smb_time_audit_disconnect(vfs_handle_struct *handle) +{ + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + SMB_VFS_NEXT_DISCONNECT(handle); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("disconnect", timediff); + } +} + +static uint64_t smb_time_audit_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + uint64_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree, dsize); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + /* Don't have a reasonable notion of failure here */ + if (timediff > audit_timeout) { + smb_time_audit_log_fname("disk_free", + timediff, + smb_fname->base_name); + } + + return result; +} + +static int smb_time_audit_get_quota(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *qt) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("get_quota", + timediff, + smb_fname->base_name); + } + return result; +} + +static int smb_time_audit_set_quota(struct vfs_handle_struct *handle, + enum SMB_QUOTA_TYPE qtype, unid_t id, + SMB_DISK_QUOTA *qt) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, qt); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("set_quota", timediff); + } + + return result; +} + +static int smb_time_audit_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *shadow_copy_data, + bool labels) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GET_SHADOW_COPY_DATA(handle, fsp, + shadow_copy_data, labels); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("get_shadow_copy_data", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("statvfs", timediff, + smb_fname->base_name); + } + + return result; +} + +static uint32_t smb_time_audit_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + uint32_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("fs_capabilities", timediff); + } + + return result; +} + +static NTSTATUS smb_time_audit_get_dfs_referrals( + struct vfs_handle_struct *handle, + struct dfs_GetDFSReferral *r) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("get_dfs_referrals", timediff); + } + + return result; +} + +static NTSTATUS smb_time_audit_create_dfs_pathat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + const struct referral *reflist, + size_t referral_count) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, + dirfsp, + smb_fname, + reflist, + referral_count); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("create_dfs_pathat", + timediff, + full_fname); + } + TALLOC_FREE(full_fname); + return result; +} + +static NTSTATUS smb_time_audit_read_dfs_pathat(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + struct referral **ppreflist, + size_t *preferral_count) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_READ_DFS_PATHAT(handle, + mem_ctx, + dirfsp, + smb_fname, + ppreflist, + preferral_count); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("read_dfs_pathat", + timediff, + full_fname); + } + + TALLOC_FREE(full_fname); + return result; +} + +static NTSTATUS smb_time_audit_snap_check_path(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *service_path, + char **base_volume) +{ + NTSTATUS status; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + status = SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, service_path, + base_volume); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("snap_check_path", timediff); + } + + return status; +} + +static NTSTATUS smb_time_audit_snap_create(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const char *base_volume, + time_t *tstamp, + bool rw, + char **base_path, + char **snap_path) +{ + NTSTATUS status; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + status = SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, tstamp, + rw, base_path, snap_path); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2 ,&ts1) * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("snap_create", timediff); + } + + return status; +} + +static NTSTATUS smb_time_audit_snap_delete(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + char *base_path, + char *snap_path) +{ + NTSTATUS status; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + status = SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, base_path, + snap_path); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("snap_delete", timediff); + } + + return status; +} + +static DIR *smb_time_audit_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, + const char *mask, uint32_t attr) +{ + DIR *result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fdopendir", timediff, fsp); + } + + return result; +} + +static struct dirent *smb_time_audit_readdir(vfs_handle_struct *handle, + struct files_struct *dirfsp, + DIR *dirp) +{ + struct dirent *result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("readdir", timediff); + } + + return result; +} + +static void smb_time_audit_rewinddir(vfs_handle_struct *handle, + DIR *dirp) +{ + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + SMB_VFS_NEXT_REWINDDIR(handle, dirp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("rewinddir", timediff); + } + +} + +static int smb_time_audit_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct smb_filename *full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("mkdirat", + timediff, + full_fname); + } + + TALLOC_FREE(full_fname); + + return result; +} + +static int smb_time_audit_closedir(vfs_handle_struct *handle, + DIR *dirp) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CLOSEDIR(handle, dirp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("closedir", timediff); + } + + return result; +} + +static int smb_time_audit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("openat", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result_fsp, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CREATE_FILE( + handle, /* handle */ + req, /* req */ + dirfsp, /* dirfsp */ + fname, /* fname */ + access_mask, /* access_mask */ + share_access, /* share_access */ + create_disposition, /* create_disposition*/ + create_options, /* create_options */ + file_attributes, /* file_attributes */ + oplock_request, /* oplock_request */ + lease, /* lease */ + allocation_size, /* allocation_size */ + private_flags, + sd, /* sd */ + ea_list, /* ea_list */ + result_fsp, /* result */ + pinfo, + in_context_blobs, out_context_blobs); /* create context */ + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + /* + * can't use result_fsp this time, may have + * invalid content causing smbd crash + */ + smb_time_audit_log_smb_fname("create_file", timediff, + fname); + } + + return result; +} + +static int smb_time_audit_close(vfs_handle_struct *handle, files_struct *fsp) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CLOSE(handle, fsp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("close", timediff, fsp); + } + + return result; +} + +static ssize_t smb_time_audit_pread(vfs_handle_struct *handle, + files_struct *fsp, + void *data, size_t n, off_t offset) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("pread", timediff, fsp); + } + + return result; +} + +struct smb_time_audit_pread_state { + struct files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_time_audit_pread_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_pread_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct smb_time_audit_pread_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_time_audit_pread_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_time_audit_pread_done, req); + return req; +} + +static void smb_time_audit_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_time_audit_pread_state *state = tevent_req_data( + req, struct smb_time_audit_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t smb_time_audit_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_time_audit_pread_state *state = tevent_req_data( + req, struct smb_time_audit_pread_state); + double timediff; + + timediff = state->vfs_aio_state.duration * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("async pread", timediff, state->fsp); + } + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static ssize_t smb_time_audit_pwrite(vfs_handle_struct *handle, + files_struct *fsp, + const void *data, size_t n, + off_t offset) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("pwrite", timediff, fsp); + } + + return result; +} + +struct smb_time_audit_pwrite_state { + struct files_struct *fsp; + ssize_t ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_time_audit_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_pwrite_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp, + const void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct smb_time_audit_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_time_audit_pwrite_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_time_audit_pwrite_done, req); + return req; +} + +static void smb_time_audit_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_time_audit_pwrite_state *state = tevent_req_data( + req, struct smb_time_audit_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t smb_time_audit_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_time_audit_pwrite_state *state = tevent_req_data( + req, struct smb_time_audit_pwrite_state); + double timediff; + + timediff = state->vfs_aio_state.duration * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("async pwrite", timediff, state->fsp); + } + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static off_t smb_time_audit_lseek(vfs_handle_struct *handle, + files_struct *fsp, + off_t offset, int whence) +{ + off_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("lseek", timediff, fsp); + } + + return result; +} + +static ssize_t smb_time_audit_sendfile(vfs_handle_struct *handle, int tofd, + files_struct *fromfsp, + const DATA_BLOB *hdr, off_t offset, + size_t n) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, hdr, offset, n); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("sendfile", timediff, fromfsp); + } + + return result; +} + +static ssize_t smb_time_audit_recvfile(vfs_handle_struct *handle, int fromfd, + files_struct *tofsp, + off_t offset, + size_t n) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_RECVFILE(handle, fromfd, tofsp, offset, n); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("recvfile", timediff, tofsp); + } + + return result; +} + +static int smb_time_audit_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *oldname, + files_struct *dstfsp, + const struct smb_filename *newname) +{ + int result; + struct timespec ts1,ts2; + double timediff; + struct smb_filename *new_full_fname = NULL; + + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + newname); + if (new_full_fname == NULL) { + errno = ENOMEM; + return -1; + } + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + oldname, + dstfsp, + newname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("renameat", + timediff, + new_full_fname); + } + + TALLOC_FREE(new_full_fname); + return result; +} + +struct smb_time_audit_fsync_state { + struct files_struct *fsp; + int ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void smb_time_audit_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_fsync_send( + struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, struct files_struct *fsp) +{ + struct tevent_req *req, *subreq; + struct smb_time_audit_fsync_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct smb_time_audit_fsync_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_time_audit_fsync_done, req); + return req; +} + +static void smb_time_audit_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_time_audit_fsync_state *state = tevent_req_data( + req, struct smb_time_audit_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static int smb_time_audit_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct smb_time_audit_fsync_state *state = tevent_req_data( + req, struct smb_time_audit_fsync_state); + double timediff; + + timediff = state->vfs_aio_state.duration * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("async fsync", timediff, state->fsp); + } + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static int smb_time_audit_stat(vfs_handle_struct *handle, + struct smb_filename *fname) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_STAT(handle, fname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("stat", timediff, fname); + } + + return result; +} + +static int smb_time_audit_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fstat", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_lstat(vfs_handle_struct *handle, + struct smb_filename *path) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LSTAT(handle, path); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("lstat", timediff, path); + } + + return result; +} + +static int smb_time_audit_fstatat( + struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + SMB_STRUCT_STAT *sbuf, + int flags) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("fstatat", timediff, smb_fname); + } + + return result; +} + +static uint64_t smb_time_audit_get_alloc_size(vfs_handle_struct *handle, + files_struct *fsp, + const SMB_STRUCT_STAT *sbuf) +{ + uint64_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GET_ALLOC_SIZE(handle, fsp, sbuf); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("get_alloc_size", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *path, + int flags) +{ + struct smb_filename *full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + path); + if (full_fname == NULL) { + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + path, + flags); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("unlinkat", timediff, full_fname); + } + + TALLOC_FREE(full_fname); + return result; +} + +static int smb_time_audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, + mode_t mode) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fchmod", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_fchown(vfs_handle_struct *handle, files_struct *fsp, + uid_t uid, gid_t gid) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fchown", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("lchown", + timediff, + smb_fname->base_name); + } + + return result; +} + +static int smb_time_audit_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CHDIR(handle, smb_fname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("chdir", + timediff, + smb_fname->base_name); + } + + return result; +} + +static struct smb_filename *smb_time_audit_getwd(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx) +{ + struct smb_filename *result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GETWD(handle, mem_ctx); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("getwd", timediff); + } + + return result; +} + +static int smb_time_audit_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fntimes", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_ftruncate(vfs_handle_struct *handle, + files_struct *fsp, + off_t len) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("ftruncate", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_fallocate(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + int result; + int saved_errno = 0; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + if (result == -1) { + saved_errno = errno; + } + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fallocate", timediff, fsp); + } + if (result == -1) { + errno = saved_errno; + } + return result; +} + +static bool smb_time_audit_lock(vfs_handle_struct *handle, files_struct *fsp, + int op, off_t offset, off_t count, + int type) +{ + bool result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("lock", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_filesystem_sharemode(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t share_access, + uint32_t access_mask) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle, + fsp, + share_access, + access_mask); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("filesystem_sharemode", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_fcntl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + int cmd, va_list cmd_arg) +{ + void *arg; + va_list dup_cmd_arg; + int result; + struct timespec ts1,ts2; + double timediff; + + va_copy(dup_cmd_arg, cmd_arg); + arg = va_arg(dup_cmd_arg, void *); + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg); + clock_gettime_mono(&ts2); + va_end(dup_cmd_arg); + + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fcntl", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_linux_setlease(vfs_handle_struct *handle, + files_struct *fsp, + int leasetype) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("linux_setlease", timediff, fsp); + } + + return result; +} + +static bool smb_time_audit_getlock(vfs_handle_struct *handle, + files_struct *fsp, + off_t *poffset, off_t *pcount, + int *ptype, pid_t *ppid) +{ + bool result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, + ppid); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("getlock", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + struct smb_filename *full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("symlinkat", timediff, + full_fname->base_name); + } + + TALLOC_FREE(full_fname); + return result; +} + +static int smb_time_audit_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + struct smb_filename *full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("readlinkat", timediff, + full_fname->base_name); + } + + TALLOC_FREE(full_fname); + return result; +} + +static int smb_time_audit_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + struct smb_filename *new_full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("linkat", timediff, + new_full_fname->base_name); + } + + TALLOC_FREE(new_full_fname); + return result; +} + +static int smb_time_audit_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + struct smb_filename *full_fname = NULL; + int result; + struct timespec ts1,ts2; + double timediff; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("mknodat", timediff, full_fname); + } + + TALLOC_FREE(full_fname); + return result; +} + +static struct smb_filename *smb_time_audit_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + struct smb_filename *result_fname; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("realpath", timediff, + smb_fname->base_name); + } + + return result_fname; +} + +static int smb_time_audit_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_smb_fname("chflags", + timediff, + fsp->fsp_name); + } + + return result; +} + +static struct file_id smb_time_audit_file_id_create(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + struct file_id id_zero; + struct file_id result; + struct timespec ts1,ts2; + double timediff; + + ZERO_STRUCT(id_zero); + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("file_id_create", timediff); + } + + return result; +} + +static uint64_t smb_time_audit_fs_file_id(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *sbuf) +{ + uint64_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FS_FILE_ID(handle, sbuf); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("fs_file_id", timediff); + } + + return result; +} + +static NTSTATUS smb_time_audit_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx, + pnum_streams, pstreams); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fstreaminfo", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("get_real_filename_at", + timediff, + fsp_str_dbg(dirfsp)); + } + + return result; +} + +static const char *smb_time_audit_connectpath( + vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + const char *result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("connectpath", timediff, + smb_fname->base_name); + } + + return result; +} + +static NTSTATUS smb_time_audit_brl_lock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + struct lock_struct *plock) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("brl_lock_windows", timediff, + brl_fsp(br_lck)); + } + + return result; +} + +static bool smb_time_audit_brl_unlock_windows(struct vfs_handle_struct *handle, + struct byte_range_lock *br_lck, + const struct lock_struct *plock) +{ + bool result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("brl_unlock_windows", timediff, + brl_fsp(br_lck)); + } + + return result; +} + +static bool smb_time_audit_strict_lock_check(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct lock_struct *plock) +{ + bool result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("strict_lock_check", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_translate_name(struct vfs_handle_struct *handle, + const char *name, + enum vfs_translate_direction direction, + TALLOC_CTX *mem_ctx, + char **mapped_name) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_TRANSLATE_NAME(handle, name, direction, mem_ctx, + mapped_name); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("translate_name", timediff, name); + } + + return result; +} + +static NTSTATUS smb_time_audit_parent_pathname(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + const struct smb_filename *smb_fname_in, + struct smb_filename **parent_dir_out, + struct smb_filename **atname_out) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_PARENT_PATHNAME(handle, + mem_ctx, + smb_fname_in, + parent_dir_out, + atname_out); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("parent_pathname", + timediff, + smb_fname_in->base_name); + } + + return result; +} + +static NTSTATUS smb_time_audit_fsctl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *ctx, + uint32_t function, + uint16_t req_flags, + const uint8_t *_in_data, + uint32_t in_len, + uint8_t **_out_data, + uint32_t max_out_len, + uint32_t *out_len) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSCTL(handle, + fsp, + ctx, + function, + req_flags, + _in_data, + in_len, + _out_data, + max_out_len, + out_len); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fsctl", timediff, fsp); + } + + return result; +} + +struct smb_time_audit_get_dos_attributes_state { + struct vfs_aio_state aio_state; + files_struct *dir_fsp; + const struct smb_filename *smb_fname; + uint32_t dosmode; +}; + +static void smb_time_audit_get_dos_attributes_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_get_dos_attributes_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + struct smb_filename *smb_fname) +{ + struct tevent_req *req = NULL; + struct smb_time_audit_get_dos_attributes_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb_time_audit_get_dos_attributes_state); + if (req == NULL) { + return NULL; + } + *state = (struct smb_time_audit_get_dos_attributes_state) { + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + }; + + subreq = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_SEND(mem_ctx, + ev, + handle, + dir_fsp, + smb_fname); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + smb_time_audit_get_dos_attributes_done, + req); + + return req; +} + +static void smb_time_audit_get_dos_attributes_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb_time_audit_get_dos_attributes_state *state = + tevent_req_data(req, + struct smb_time_audit_get_dos_attributes_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_RECV(subreq, + &state->aio_state, + &state->dosmode); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; +} + +static NTSTATUS smb_time_audit_get_dos_attributes_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + uint32_t *dosmode) +{ + struct smb_time_audit_get_dos_attributes_state *state = + tevent_req_data(req, + struct smb_time_audit_get_dos_attributes_state); + NTSTATUS status; + double timediff; + + timediff = state->aio_state.duration * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_at("async get_dos_attributes", + timediff, + state->dir_fsp, + state->smb_fname); + } + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *aio_state = state->aio_state; + *dosmode = state->dosmode; + tevent_req_received(req); + return NT_STATUS_OK; +} + +static NTSTATUS smb_time_fget_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t *dosmode) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, + fsp, + dosmode); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fget_dos_attributes", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, + fsp, + dosmode); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fset_dos_attributes", timediff, fsp); + } + + return result; +} + +struct time_audit_offload_read_state { + struct vfs_handle_struct *handle; + struct timespec ts_send; + uint32_t flags; + uint64_t xferlen; + DATA_BLOB token_blob; +}; + +static void smb_time_audit_offload_read_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct time_audit_offload_read_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct time_audit_offload_read_state); + if (req == NULL) { + return NULL; + } + state->handle = handle; + clock_gettime_mono(&state->ts_send); + + subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, + handle, fsp, + fsctl, ttl, + offset, to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, smb_time_audit_offload_read_done, req); + return req; +} + +static void smb_time_audit_offload_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct time_audit_offload_read_state *state = tevent_req_data( + req, struct time_audit_offload_read_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq, + state->handle, + state, + &state->flags, + &state->xferlen, + &state->token_blob); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static NTSTATUS smb_time_audit_offload_read_recv( + struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *token_blob) +{ + struct time_audit_offload_read_state *state = tevent_req_data( + req, struct time_audit_offload_read_state); + struct timespec ts_recv; + double timediff; + NTSTATUS status; + + clock_gettime_mono(&ts_recv); + timediff = nsec_time_diff(&ts_recv, &state->ts_send) * 1.0e-9; + if (timediff > audit_timeout) { + smb_time_audit_log("offload_read", timediff); + } + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *flags = state->flags; + *xferlen = state->xferlen; + token_blob->length = state->token_blob.length; + token_blob->data = talloc_move(mem_ctx, &state->token_blob.data); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct time_audit_offload_write_state { + struct timespec ts_send; + struct vfs_handle_struct *handle; + off_t copied; +}; +static void smb_time_audit_offload_write_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_offload_write_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct time_audit_offload_write_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct time_audit_offload_write_state); + if (req == NULL) { + return NULL; + } + + state->handle = handle; + clock_gettime_mono(&state->ts_send); + subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, state, ev, + fsctl, token, transfer_offset, + dest_fsp, dest_off, num); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, smb_time_audit_offload_write_done, req); + return req; +} + +static void smb_time_audit_offload_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct time_audit_offload_write_state *state = tevent_req_data( + req, struct time_audit_offload_write_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle, + subreq, + &state->copied); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static NTSTATUS smb_time_audit_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + struct time_audit_offload_write_state *state = tevent_req_data( + req, struct time_audit_offload_write_state); + struct timespec ts_recv; + double timediff; + NTSTATUS status; + + clock_gettime_mono(&ts_recv); + timediff = nsec_time_diff(&ts_recv, &state->ts_send)*1.0e-9; + if (timediff > audit_timeout) { + smb_time_audit_log("offload_write", timediff); + } + + *copied = state->copied; + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +static NTSTATUS smb_time_audit_fget_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t *_compression_fmt) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FGET_COMPRESSION(handle, mem_ctx, fsp, + _compression_fmt); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("get_compression", + timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_set_compression(vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + uint16_t compression_fmt) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp, + compression_fmt); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("set_compression", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **pattr_data) +{ + NTSTATUS status; + struct timespec ts1, ts2; + double timediff; + + clock_gettime_mono(&ts1); + status = SMB_VFS_NEXT_FREADDIR_ATTR(handle, fsp, mem_ctx, pattr_data); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("freaddir_attr", timediff, fsp); + } + + return status; +} + +static NTSTATUS smb_time_audit_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fget_nt_acl", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, + psd); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fset_nt_acl", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_audit_file(struct vfs_handle_struct *handle, + struct smb_filename *smb_fname, + struct security_acl *sacl, + uint32_t access_requested, + uint32_t access_denied) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_AUDIT_FILE(handle, + smb_fname, + sacl, + access_requested, + access_denied); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fname("audit_file", + timediff, + smb_fname->base_name); + } + + return result; +} + +static SMB_ACL_T smb_time_audit_sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + SMB_ACL_T result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("sys_acl_get_fd", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_sys_acl_blob_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + TALLOC_CTX *mem_ctx, + char **blob_description, + DATA_BLOB *blob) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, blob_description, blob); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("sys_acl_blob_get_fd", timediff); + } + + return result; +} + +static int smb_time_audit_sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("sys_acl_set_fd", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("sys_acl_delete_def_fd", timediff, fsp); + } + + return result; +} + +struct smb_time_audit_getxattrat_state { + struct vfs_aio_state aio_state; + files_struct *dir_fsp; + const struct smb_filename *smb_fname; + const char *xattr_name; + ssize_t xattr_size; + uint8_t *xattr_value; +}; + +static void smb_time_audit_getxattrat_done(struct tevent_req *subreq); + +static struct tevent_req *smb_time_audit_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb_time_audit_getxattrat_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smb_time_audit_getxattrat_state); + if (req == NULL) { + return NULL; + } + *state = (struct smb_time_audit_getxattrat_state) { + .dir_fsp = dir_fsp, + .smb_fname = smb_fname, + .xattr_name = xattr_name, + }; + + subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state, + ev, + handle, + dir_fsp, + smb_fname, + xattr_name, + alloc_hint); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb_time_audit_getxattrat_done, req); + + return req; +} + +static void smb_time_audit_getxattrat_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smb_time_audit_getxattrat_state *state = tevent_req_data( + req, struct smb_time_audit_getxattrat_state); + + state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq, + &state->aio_state, + state, + &state->xattr_value); + TALLOC_FREE(subreq); + if (state->xattr_size == -1) { + tevent_req_error(req, state->aio_state.error); + return; + } + + tevent_req_done(req); +} + +static ssize_t smb_time_audit_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct smb_time_audit_getxattrat_state *state = tevent_req_data( + req, struct smb_time_audit_getxattrat_state); + ssize_t xattr_size; + double timediff; + + timediff = state->aio_state.duration * 1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_at("async getxattrat", + timediff, + state->dir_fsp, + state->smb_fname); + } + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *aio_state = state->aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +static ssize_t smb_time_audit_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, void *value, + size_t size) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fgetxattr", timediff, fsp); + } + + return result; +} + +static ssize_t smb_time_audit_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + ssize_t result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("flistxattr", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fremovexattr", timediff, fsp); + } + + return result; +} + +static int smb_time_audit_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name, + const void *value, size_t size, int flags) +{ + int result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("fsetxattr", timediff, fsp); + } + + return result; +} + +static bool smb_time_audit_aio_force(struct vfs_handle_struct *handle, + struct files_struct *fsp) +{ + bool result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_AIO_FORCE(handle, fsp); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("aio_force", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_durable_cookie(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + DATA_BLOB *cookie) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_DURABLE_COOKIE(handle, fsp, mem_ctx, cookie); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("durable_cookie", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_durable_disconnect(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + DATA_BLOB *new_cookie) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_DURABLE_DISCONNECT(handle, fsp, old_cookie, + mem_ctx, new_cookie); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log_fsp("durable_disconnect", timediff, fsp); + } + + return result; +} + +static NTSTATUS smb_time_audit_durable_reconnect(struct vfs_handle_struct *handle, + struct smb_request *smb1req, + struct smbXsrv_open *op, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, + struct files_struct **fsp, + DATA_BLOB *new_cookie) +{ + NTSTATUS result; + struct timespec ts1,ts2; + double timediff; + + clock_gettime_mono(&ts1); + result = SMB_VFS_NEXT_DURABLE_RECONNECT(handle, smb1req, op, old_cookie, + mem_ctx, fsp, new_cookie); + clock_gettime_mono(&ts2); + timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9; + + if (timediff > audit_timeout) { + smb_time_audit_log("durable_reconnect", timediff); + } + + return result; +} + +/* VFS operations */ + +static struct vfs_fn_pointers vfs_time_audit_fns = { + .connect_fn = smb_time_audit_connect, + .disconnect_fn = smb_time_audit_disconnect, + .disk_free_fn = smb_time_audit_disk_free, + .get_quota_fn = smb_time_audit_get_quota, + .set_quota_fn = smb_time_audit_set_quota, + .get_shadow_copy_data_fn = smb_time_audit_get_shadow_copy_data, + .statvfs_fn = smb_time_audit_statvfs, + .fs_capabilities_fn = smb_time_audit_fs_capabilities, + .get_dfs_referrals_fn = smb_time_audit_get_dfs_referrals, + .create_dfs_pathat_fn = smb_time_audit_create_dfs_pathat, + .read_dfs_pathat_fn = smb_time_audit_read_dfs_pathat, + .fdopendir_fn = smb_time_audit_fdopendir, + .readdir_fn = smb_time_audit_readdir, + .rewind_dir_fn = smb_time_audit_rewinddir, + .mkdirat_fn = smb_time_audit_mkdirat, + .closedir_fn = smb_time_audit_closedir, + .openat_fn = smb_time_audit_openat, + .create_file_fn = smb_time_audit_create_file, + .close_fn = smb_time_audit_close, + .pread_fn = smb_time_audit_pread, + .pread_send_fn = smb_time_audit_pread_send, + .pread_recv_fn = smb_time_audit_pread_recv, + .pwrite_fn = smb_time_audit_pwrite, + .pwrite_send_fn = smb_time_audit_pwrite_send, + .pwrite_recv_fn = smb_time_audit_pwrite_recv, + .lseek_fn = smb_time_audit_lseek, + .sendfile_fn = smb_time_audit_sendfile, + .recvfile_fn = smb_time_audit_recvfile, + .renameat_fn = smb_time_audit_renameat, + .fsync_send_fn = smb_time_audit_fsync_send, + .fsync_recv_fn = smb_time_audit_fsync_recv, + .stat_fn = smb_time_audit_stat, + .fstat_fn = smb_time_audit_fstat, + .lstat_fn = smb_time_audit_lstat, + .fstatat_fn = smb_time_audit_fstatat, + .get_alloc_size_fn = smb_time_audit_get_alloc_size, + .unlinkat_fn = smb_time_audit_unlinkat, + .fchmod_fn = smb_time_audit_fchmod, + .fchown_fn = smb_time_audit_fchown, + .lchown_fn = smb_time_audit_lchown, + .chdir_fn = smb_time_audit_chdir, + .getwd_fn = smb_time_audit_getwd, + .fntimes_fn = smb_time_audit_fntimes, + .ftruncate_fn = smb_time_audit_ftruncate, + .fallocate_fn = smb_time_audit_fallocate, + .lock_fn = smb_time_audit_lock, + .filesystem_sharemode_fn = smb_time_audit_filesystem_sharemode, + .fcntl_fn = smb_time_audit_fcntl, + .linux_setlease_fn = smb_time_audit_linux_setlease, + .getlock_fn = smb_time_audit_getlock, + .symlinkat_fn = smb_time_audit_symlinkat, + .readlinkat_fn = smb_time_audit_readlinkat, + .linkat_fn = smb_time_audit_linkat, + .mknodat_fn = smb_time_audit_mknodat, + .realpath_fn = smb_time_audit_realpath, + .fchflags_fn = smb_time_audit_fchflags, + .file_id_create_fn = smb_time_audit_file_id_create, + .fs_file_id_fn = smb_time_audit_fs_file_id, + .offload_read_send_fn = smb_time_audit_offload_read_send, + .offload_read_recv_fn = smb_time_audit_offload_read_recv, + .offload_write_send_fn = smb_time_audit_offload_write_send, + .offload_write_recv_fn = smb_time_audit_offload_write_recv, + .fget_compression_fn = smb_time_audit_fget_compression, + .set_compression_fn = smb_time_audit_set_compression, + .snap_check_path_fn = smb_time_audit_snap_check_path, + .snap_create_fn = smb_time_audit_snap_create, + .snap_delete_fn = smb_time_audit_snap_delete, + .fstreaminfo_fn = smb_time_audit_fstreaminfo, + .get_real_filename_at_fn = smb_time_audit_get_real_filename_at, + .connectpath_fn = smb_time_audit_connectpath, + .brl_lock_windows_fn = smb_time_audit_brl_lock_windows, + .brl_unlock_windows_fn = smb_time_audit_brl_unlock_windows, + .strict_lock_check_fn = smb_time_audit_strict_lock_check, + .translate_name_fn = smb_time_audit_translate_name, + .parent_pathname_fn = smb_time_audit_parent_pathname, + .fsctl_fn = smb_time_audit_fsctl, + .get_dos_attributes_send_fn = smb_time_audit_get_dos_attributes_send, + .get_dos_attributes_recv_fn = smb_time_audit_get_dos_attributes_recv, + .fget_dos_attributes_fn = smb_time_fget_dos_attributes, + .fset_dos_attributes_fn = smb_time_fset_dos_attributes, + .fget_nt_acl_fn = smb_time_audit_fget_nt_acl, + .fset_nt_acl_fn = smb_time_audit_fset_nt_acl, + .audit_file_fn = smb_time_audit_audit_file, + .sys_acl_get_fd_fn = smb_time_audit_sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = smb_time_audit_sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = smb_time_audit_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = smb_time_audit_sys_acl_delete_def_fd, + .getxattrat_send_fn = smb_time_audit_getxattrat_send, + .getxattrat_recv_fn = smb_time_audit_getxattrat_recv, + .fgetxattr_fn = smb_time_audit_fgetxattr, + .flistxattr_fn = smb_time_audit_flistxattr, + .fremovexattr_fn = smb_time_audit_fremovexattr, + .fsetxattr_fn = smb_time_audit_fsetxattr, + .aio_force_fn = smb_time_audit_aio_force, + .durable_cookie_fn = smb_time_audit_durable_cookie, + .durable_disconnect_fn = smb_time_audit_durable_disconnect, + .durable_reconnect_fn = smb_time_audit_durable_reconnect, + .freaddir_attr_fn = smb_time_audit_freaddir_attr, +}; + + +static_decl_vfs; +NTSTATUS vfs_time_audit_init(TALLOC_CTX *ctx) +{ + smb_vfs_assert_all_fns(&vfs_time_audit_fns, "time_audit"); + + audit_timeout = (double)lp_parm_int(-1, "time_audit", "timeout", + 10000) / 1000.0; + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "time_audit", + &vfs_time_audit_fns); +} diff --git a/source3/modules/vfs_tsmsm.c b/source3/modules/vfs_tsmsm.c new file mode 100644 index 0000000..89191f7 --- /dev/null +++ b/source3/modules/vfs_tsmsm.c @@ -0,0 +1,573 @@ +/* + Unix SMB/CIFS implementation. + Samba VFS module for handling offline files + with Tivoli Storage Manager Space Management + + (c) Alexander Bokovoy, 2007, 2008 + (c) Andrew Tridgell, 2007, 2008 + + 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 . + */ +/* + This VFS module accepts following options: + tsmsm: hsm script = (default does nothing) + hsm script should point to a shell script which accepts two arguments: + + where is currently 'offline' to set offline status of the + + tsmsm: online ratio = ratio to check reported size against actual file size (0.5 by default) + tsmsm: dmapi attribute = name of DMAPI attribute that is present when a file is offline. + Default is "IBMobj" (which is what GPFS uses) + + The TSMSM VFS module tries to avoid calling expensive DMAPI calls with some heuristics + based on the fact that number of blocks reported of a file multiplied by 512 will be + bigger than 'online ratio' of actual size for online (non-migrated) files. + + If checks fail, we call DMAPI and ask for specific attribute which present for + offline (migrated) files. If this attribute presents, we consider file offline. + */ + +#include "includes.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_unix.h" + +#ifndef USE_DMAPI +#error "This module requires DMAPI support!" +#endif + +#ifdef HAVE_XFS_DMAPI_H +#include +#elif defined(HAVE_SYS_DMI_H) +#include +#elif defined(HAVE_SYS_JFSDMAPI_H) +#include +#elif defined(HAVE_SYS_DMAPI_H) +#include +#elif defined(HAVE_DMAPI_H) +#include +#endif + +#ifndef _ISOC99_SOURCE +#define _ISOC99_SOURCE +#endif + +#include + +/* optimisation tunables - used to avoid the DMAPI slow path */ +#define FILE_IS_ONLINE_RATIO 0.5 + +/* default attribute name to look for */ +#define DM_ATTRIB_OBJECT "IBMObj" + +struct tsmsm_struct { + float online_ratio; + char *hsmscript; + const char *attrib_name; + const char *attrib_value; +}; + +static void tsmsm_free_data(void **pptr) { + struct tsmsm_struct **tsmd = (struct tsmsm_struct **)pptr; + if(!tsmd) return; + TALLOC_FREE(*tsmd); +} + +/* + called when a client connects to a share +*/ +static int tsmsm_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) { + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct tsmsm_struct *tsmd; + const char *fres; + const char *tsmname; + int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + + if (ret < 0) { + return ret; + } + + tsmd = talloc_zero(handle, struct tsmsm_struct); + if (!tsmd) { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0,("tsmsm_connect: out of memory!\n")); + return -1; + } + + if (!dmapi_have_session()) { + SMB_VFS_NEXT_DISCONNECT(handle); + DEBUG(0,("tsmsm_connect: no DMAPI session for Samba is available!\n")); + TALLOC_FREE(tsmd); + return -1; + } + + tsmname = (handle->param ? handle->param : "tsmsm"); + + /* Get 'hsm script' and 'dmapi attribute' parameters to tsmd context */ + tsmd->hsmscript = lp_parm_substituted_string( + tsmd, lp_sub, SNUM(handle->conn), tsmname, + "hsm script", NULL); + talloc_steal(tsmd, tsmd->hsmscript); + + tsmd->attrib_name = lp_parm_substituted_string( + tsmd, lp_sub, SNUM(handle->conn), tsmname, + "dmapi attribute", DM_ATTRIB_OBJECT); + talloc_steal(tsmd, tsmd->attrib_name); + + tsmd->attrib_value = lp_parm_substituted_string( + tsmd, lp_sub, SNUM(handle->conn), tsmname, + "dmapi value", NULL); + talloc_steal(tsmd, tsmd->attrib_value); + + /* retrieve 'online ratio'. In case of error default to FILE_IS_ONLINE_RATIO */ + fres = lp_parm_const_string(SNUM(handle->conn), tsmname, + "online ratio", NULL); + if (fres == NULL) { + tsmd->online_ratio = FILE_IS_ONLINE_RATIO; + } else { + tsmd->online_ratio = strtof(fres, NULL); + if (tsmd->online_ratio > 1.0 || + tsmd->online_ratio <= 0.0) { + DEBUG(1, ("tsmsm_connect: invalid online ration %f - using %f.\n", + tsmd->online_ratio, (float)FILE_IS_ONLINE_RATIO)); + } + } + + /* Store the private data. */ + SMB_VFS_HANDLE_SET_DATA(handle, tsmd, tsmsm_free_data, + struct tsmsm_struct, return -1); + return 0; +} + +static bool tsmsm_is_offline(struct vfs_handle_struct *handle, + const struct smb_filename *fname, + SMB_STRUCT_STAT *stbuf) +{ + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + const dm_sessid_t *dmsession_id; + void *dmhandle = NULL; + size_t dmhandle_len = 0; + size_t rlen; + dm_attrname_t dmname; + int ret, lerrno; + bool offline; + char *buf = NULL; + size_t buflen; + NTSTATUS status; + char *path; + + status = get_full_smb_filename(talloc_tos(), fname, &path); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return false; + } + + /* if the file has more than FILE_IS_ONLINE_RATIO of blocks available, + then assume it is not offline (it may not be 100%, as it could be sparse) */ + if (512 * stbuf->st_ex_blocks >= + stbuf->st_ex_size * tsmd->online_ratio) { + DEBUG(10,("%s not offline: st_blocks=%llu st_size=%llu " + "online_ratio=%.2f\n", path, + (unsigned long long)stbuf->st_ex_blocks, + (unsigned long long)stbuf->st_ex_size, tsmd->online_ratio)); + return false; + } + + dmsession_id = dmapi_get_current_session(); + if (dmsession_id == NULL) { + DEBUG(2, ("tsmsm_is_offline: no DMAPI session available? " + "Assume file is online.\n")); + return false; + } + + /* using POSIX capabilities does not work here. It's a slow path, so + * become_root() is just as good anyway (tridge) + */ + + /* Also, AIX has DMAPI but no POSIX capabilities support. In this case, + * we need to be root to do DMAPI manipulations. + */ + become_root(); + + /* go the slow DMAPI route */ + if (dm_path_to_handle((char*)path, &dmhandle, &dmhandle_len) != 0) { + DEBUG(2,("dm_path_to_handle failed - assuming offline (%s) - %s\n", + path, strerror(errno))); + offline = true; + goto done; + } + + memset(&dmname, 0, sizeof(dmname)); + strlcpy((char *)&dmname.an_chars[0], tsmd->attrib_name, sizeof(dmname.an_chars)); + + if (tsmd->attrib_value != NULL) { + buflen = strlen(tsmd->attrib_value); + } else { + buflen = 1; + } + buf = talloc_zero_size(tsmd, buflen); + if (buf == NULL) { + DEBUG(0,("out of memory in tsmsm_is_offline -- assuming online (%s)\n", path)); + errno = ENOMEM; + offline = false; + goto done; + } + + do { + lerrno = 0; + + ret = dm_get_dmattr(*dmsession_id, dmhandle, dmhandle_len, + DM_NO_TOKEN, &dmname, buflen, buf, &rlen); + if (ret == -1 && errno == EINVAL) { + DEBUG(0, ("Stale DMAPI session, re-creating it.\n")); + lerrno = EINVAL; + if (dmapi_new_session()) { + dmsession_id = dmapi_get_current_session(); + } else { + DEBUG(0, + ("Unable to re-create DMAPI session, assuming offline (%s) - %s\n", + path, strerror(errno))); + offline = true; + dm_handle_free(dmhandle, dmhandle_len); + goto done; + } + } + } while (ret == -1 && lerrno == EINVAL); + + /* check if we need a specific attribute value */ + if (tsmd->attrib_value != NULL) { + offline = (ret == 0 && rlen == buflen && + memcmp(buf, tsmd->attrib_value, buflen) == 0); + } else { + /* its offline if the specified DMAPI attribute exists */ + offline = (ret == 0 || (ret == -1 && errno == E2BIG)); + } + + DEBUG(10,("dm_get_dmattr %s ret=%d (%s)\n", path, ret, strerror(errno))); + + ret = 0; + + dm_handle_free(dmhandle, dmhandle_len); + +done: + talloc_free(buf); + unbecome_root(); + return offline; +} + +static NTSTATUS tsmsm_fget_dos_attributes(struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t *dosmode) +{ + bool offline; + + offline = tsmsm_is_offline(handle, fsp->fsp_name, &fsp->fsp_name->st); + if (offline) { + *dosmode |= FILE_ATTRIBUTE_OFFLINE; + } + + return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode); +} + +static bool tsmsm_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp) +{ + SMB_STRUCT_STAT sbuf; + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + /* see if the file might be offline. This is called before each IO + to ensure we use AIO if the file is offline. We don't do the full dmapi + call as that would be too slow, instead we err on the side of using AIO + if the file might be offline + */ + if(SMB_VFS_FSTAT(fsp, &sbuf) == 0) { + DEBUG(10,("tsmsm_aio_force st_blocks=%llu st_size=%llu " + "online_ratio=%.2f\n", (unsigned long long)sbuf.st_ex_blocks, + (unsigned long long)sbuf.st_ex_size, tsmd->online_ratio)); + return !(512 * sbuf.st_ex_blocks >= + sbuf.st_ex_size * tsmd->online_ratio); + } + return false; +} + +struct tsmsm_pread_state { + struct files_struct *fsp; + ssize_t ret; + bool was_offline; + struct vfs_aio_state vfs_aio_state; +}; + +static void tsmsm_pread_done(struct tevent_req *subreq); + +static struct tevent_req *tsmsm_pread_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, size_t n, off_t offset) +{ + struct tevent_req *req, *subreq; + struct tsmsm_pread_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tsmsm_pread_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + state->was_offline = tsmsm_aio_force(handle, fsp); + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tsmsm_pread_done, req); + return req; +} + +static void tsmsm_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tsmsm_pread_state *state = tevent_req_data( + req, struct tsmsm_pread_state); + + state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t tsmsm_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct tsmsm_pread_state *state = tevent_req_data( + req, struct tsmsm_pread_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + if (state->ret >= 0 && state->was_offline) { + struct files_struct *fsp = state->fsp; + notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +struct tsmsm_pwrite_state { + struct files_struct *fsp; + ssize_t ret; + bool was_offline; + struct vfs_aio_state vfs_aio_state; +}; + +static void tsmsm_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *tsmsm_pwrite_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, size_t n, + off_t offset) +{ + struct tevent_req *req, *subreq; + struct tsmsm_pwrite_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tsmsm_pwrite_state); + if (req == NULL) { + return NULL; + } + state->fsp = fsp; + state->was_offline = tsmsm_aio_force(handle, fsp); + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data, + n, offset); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tsmsm_pwrite_done, req); + return req; +} + +static void tsmsm_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tsmsm_pwrite_state *state = tevent_req_data( + req, struct tsmsm_pwrite_state); + + state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static ssize_t tsmsm_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct tsmsm_pwrite_state *state = tevent_req_data( + req, struct tsmsm_pwrite_state); + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + return -1; + } + if (state->ret >= 0 && state->was_offline) { + struct files_struct *fsp = state->fsp; + notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + *vfs_aio_state = state->vfs_aio_state; + return state->ret; +} + +static ssize_t tsmsm_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fsp, const DATA_BLOB *hdr, + off_t offset, size_t n) +{ + bool file_offline = tsmsm_aio_force(handle, fsp); + + if (file_offline) { + DEBUG(10,("tsmsm_sendfile on offline file - rejecting\n")); + errno = ENOSYS; + return -1; + } + + return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, hdr, offset, n); +} + +/* We do overload pread to allow notification when file becomes online after offline status */ +/* We don't intercept SMB_VFS_READ here because all file I/O now goes through SMB_VFS_PREAD instead */ +static ssize_t tsmsm_pread(struct vfs_handle_struct *handle, struct files_struct *fsp, + void *data, size_t n, off_t offset) { + ssize_t result; + bool notify_online = tsmsm_aio_force(handle, fsp); + + result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + if((result != -1) && notify_online) { + /* We can't actually force AIO at this point (came here not from reply_read_and_X) + what we can do is to send notification that file became online + */ + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return result; +} + +static ssize_t tsmsm_pwrite(struct vfs_handle_struct *handle, struct files_struct *fsp, + const void *data, size_t n, off_t offset) { + ssize_t result; + bool notify_online = tsmsm_aio_force(handle, fsp); + + result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + if((result != -1) && notify_online) { + /* We can't actually force AIO at this point (came here not from reply_read_and_X) + what we can do is to send notification that file became online + */ + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name->base_name); + } + + return result; +} + +static NTSTATUS tsmsm_set_offline(struct vfs_handle_struct *handle, + const struct smb_filename *fname) +{ + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + int result = 0; + char *command; + NTSTATUS status; + char *path; + + if (tsmd->hsmscript == NULL) { + /* no script enabled */ + DEBUG(1, ("tsmsm_set_offline: No 'tsmsm:hsm script' configured\n")); + return NT_STATUS_OK; + } + + status = get_full_smb_filename(talloc_tos(), fname, &path); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Now, call the script */ + command = talloc_asprintf(tsmd, "%s offline \"%s\"", tsmd->hsmscript, path); + if(!command) { + DEBUG(1, ("tsmsm_set_offline: can't allocate memory to run hsm script\n")); + return NT_STATUS_NO_MEMORY; + } + DEBUG(10, ("tsmsm_set_offline: Running [%s]\n", command)); + result = smbrun(command, NULL, NULL); + if(result != 0) { + DEBUG(1,("tsmsm_set_offline: Running [%s] returned %d\n", command, result)); + TALLOC_FREE(command); + return NT_STATUS_INTERNAL_ERROR; + } + TALLOC_FREE(command); + return NT_STATUS_OK; +} + +static NTSTATUS tsmsm_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + NTSTATUS status; + uint32_t old_dosmode; + + old_dosmode = fdos_mode(fsp); + + status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!(old_dosmode & FILE_ATTRIBUTE_OFFLINE) && + (dosmode & FILE_ATTRIBUTE_OFFLINE)) + { + return NT_STATUS_OK; + } + + return tsmsm_set_offline(handle, fsp->fsp_name); +} + +static uint32_t tsmsm_fs_capabilities(struct vfs_handle_struct *handle, + enum timestamp_set_resolution *p_ts_res) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_SUPPORTS_REMOTE_STORAGE | FILE_SUPPORTS_REPARSE_POINTS; +} + +static struct vfs_fn_pointers tsmsm_fns = { + .connect_fn = tsmsm_connect, + .fs_capabilities_fn = tsmsm_fs_capabilities, + .aio_force_fn = tsmsm_aio_force, + .pread_fn = tsmsm_pread, + .pread_send_fn = tsmsm_pread_send, + .pread_recv_fn = tsmsm_pread_recv, + .pwrite_fn = tsmsm_pwrite, + .pwrite_send_fn = tsmsm_pwrite_send, + .pwrite_recv_fn = tsmsm_pwrite_recv, + .sendfile_fn = tsmsm_sendfile, + .fset_dos_attributes_fn = tsmsm_fset_dos_attributes, + .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send, + .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv, + .fget_dos_attributes_fn = tsmsm_fget_dos_attributes, +}; + +static_decl_vfs; +NTSTATUS vfs_tsmsm_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "tsmsm", &tsmsm_fns); +} diff --git a/source3/modules/vfs_unityed_media.c b/source3/modules/vfs_unityed_media.c new file mode 100644 index 0000000..c848cf5 --- /dev/null +++ b/source3/modules/vfs_unityed_media.c @@ -0,0 +1,1544 @@ +/* + * Samba VFS module supporting multiple AVID clients sharing media. + * + * Copyright (C) 2005 Philip de Nier + * Copyright (C) 2012 Andrew Klaassen + * Copyright (C) 2013 Milos Lukacek + * Copyright (C) 2013 Ralph Boehme + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* + * Unityed Media is a Samba VFS module that allows multiple AVID + * clients to share media. + * + * Add this module to the vfs objects option in your Samba share + * configuration. + * eg. + * + * [avid_win] + * path = /video + * vfs objects = unityed_media + * ... + * + * It is recommended that you separate out Samba shares for Mac + * and Windows clients, and add the following options to the shares + * for Windows clients (NOTE: replace @ with *): + * + * veto files = /.DS_Store/._@/.Trash@/.Spotlight@/.hidden/.hotfiles@/.vol/ + * delete veto files = yes + * + * This prevents hidden files from Mac clients interfering with Windows + * clients. If you find any more problem hidden files then add them to + * the list. + * + * Notes: + * This module is designed to work with AVID editing applications that + * look in the Avid MediaFiles or OMFI MediaFiles directory for media. + * It is not designed to work as expected in all circumstances for + * general use. + */ + + +#include "includes.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "../smbd/globals.h" +#include "auth.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/util/smb_strtox.h" +#include +#include "source3/lib/substitute.h" + +#define UM_PARAM_TYPE_NAME "unityed_media" + +static const char *AVID_MXF_DIRNAME = "Avid MediaFiles/MXF"; +static const size_t AVID_MXF_DIRNAME_LEN = 19; +static const char *OMFI_MEDIAFILES_DIRNAME = "OMFI MediaFiles"; +static const size_t OMFI_MEDIAFILES_DIRNAME_LEN = 15; +static const char *APPLE_DOUBLE_PREFIX = "._"; +static const size_t APPLE_DOUBLE_PREFIX_LEN = 2; +static int vfs_um_debug_level = DBGC_VFS; + +enum um_clientid {UM_CLIENTID_NAME, UM_CLIENTID_IP, UM_CLIENTID_HOSTNAME}; + +struct um_config_data { + enum um_clientid clientid; +}; + +static const struct enum_list um_clientid[] = { + {UM_CLIENTID_NAME, "user"}, + {UM_CLIENTID_IP, "ip"}, + {UM_CLIENTID_HOSTNAME, "hostname"}, + {-1, NULL} +}; + +/* supplements the directory list stream */ +typedef struct um_dirinfo_struct { + DIR* dirstream; + char *dirpath; + char *clientPath; + bool isInMediaFiles; + char *clientSubDirname; +} um_dirinfo_struct; + +/** + * Returns true and first group of digits in path, false and 0 otherwise + **/ +static bool get_digit_group(const char *path, uintmax_t *digit) +{ + const char *p = path; + codepoint_t cp; + size_t size; + int error = 0; + + DEBUG(10, ("get_digit_group entering with path '%s'\n", + path)); + + /* + * Delibiretly initialize to 0 because callers use this result + * even though the string doesn't contain any number and we + * returned false + */ + *digit = 0; + + while (*p) { + cp = next_codepoint(p, &size); + if (cp == -1) { + return false; + } + if ((size == 1) && (isdigit(cp))) { + *digit = (uintmax_t)smb_strtoul(p, + NULL, + 10, + &error, + SMB_STR_STANDARD); + if (error != 0) { + return false; + } + DEBUG(10, ("num_suffix = '%ju'\n", + *digit)); + return true; + } + p += size; + } + + return false; +} + +/* Add "_." suffix to path or filename. + * + * Success: return 0 + * Failure: set errno, path NULL, return -1 + */ + +static int alloc_append_client_suffix(vfs_handle_struct *handle, + char **path) +{ + int status = 0; + uintmax_t number; + const char *clientid; + struct um_config_data *config; + + DEBUG(10, ("Entering with path '%s'\n", *path)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct um_config_data, + return -1); + + (void)get_digit_group(*path, &number); + + switch (config->clientid) { + + case UM_CLIENTID_IP: + clientid = tsocket_address_inet_addr_string( + handle->conn->sconn->remote_address, talloc_tos()); + if (clientid == NULL) { + errno = ENOMEM; + status = -1; + goto err; + } + break; + + case UM_CLIENTID_HOSTNAME: + clientid = get_remote_machine_name(); + break; + + case UM_CLIENTID_NAME: + default: + clientid = get_current_username(); + break; + } + + *path = talloc_asprintf_append(*path, "_%s.%ju", + clientid, number); + if (*path == NULL) { + DEBUG(1, ("alloc_append_client_suffix " + "out of memory\n")); + errno = ENOMEM; + status = -1; + goto err; + } + DEBUG(10, ("Leaving with *path '%s'\n", *path)); +err: + return status; +} + +/* Returns true if the file or directory begins with the appledouble + * prefix. + */ +static bool is_apple_double(const char* fname) +{ + bool ret = false; + + DEBUG(10, ("Entering with fname '%s'\n", fname)); + + if (strnequal(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN)) { + ret = true; + } + DEBUG(10, ("Leaving with ret '%s'\n", + ret == true ? "true" : "false")); + return ret; +} + +static bool starts_with_media_dir(const char* media_dirname, + size_t media_dirname_len, + const char *path) +{ + bool ret = false; + const char *path_start = path; + + DEBUG(10, ("Entering with media_dirname '%s' " + "path '%s'\n", media_dirname, path)); + + /* Sometimes Samba gives us "./OMFI MediaFiles". */ + if (strnequal(path, "./", 2)) { + path_start += 2; + } + + if (strnequal(media_dirname, path_start, media_dirname_len) + && + ((path_start[media_dirname_len] == '\0') || + (path_start[media_dirname_len] == '/'))) { + ret = true; + } + + DEBUG(10, ("Leaving with ret '%s'\n", + ret == true ? "true" : "false")); + return ret; +} + +/* + * Returns true if the file or directory referenced by the path is ONE + * LEVEL below the AVID_MXF_DIRNAME or OMFI_MEDIAFILES_DIRNAME + * directory + */ +static bool is_in_media_dir(const char *path) +{ + int transition_count = 0; + const char *path_start = path; + const char *p; + const char *media_dirname; + size_t media_dirname_len; + + DEBUG(10, ("Entering with path '%s'\n", path)); + + /* Sometimes Samba gives us "./OMFI MediaFiles". */ + if (strnequal(path, "./", 2)) { + path_start += 2; + } + + if (strnequal(path_start, AVID_MXF_DIRNAME, AVID_MXF_DIRNAME_LEN)) { + media_dirname = AVID_MXF_DIRNAME; + media_dirname_len = AVID_MXF_DIRNAME_LEN; + } else if (strnequal(path_start, + OMFI_MEDIAFILES_DIRNAME, + OMFI_MEDIAFILES_DIRNAME_LEN)) { + media_dirname = OMFI_MEDIAFILES_DIRNAME; + media_dirname_len = OMFI_MEDIAFILES_DIRNAME_LEN; + } else { + return false; + } + + if (path_start[media_dirname_len] == '\0') { + goto out; + } + + p = path_start + media_dirname_len + 1; + + while (true) { + if (*p == '\0' || *p == '/') { + if (strnequal(p - 3, "/..", 3)) { + transition_count--; + } else if ((p[-1] != '/') || !strnequal(p - 2, "/.", 2)) { + transition_count++; + } + } + if (*p == '\0') { + break; + } + p++; + } + +out: + DEBUG(10, ("Going out with transition_count '%i'\n", + transition_count)); + if (((transition_count == 1) && (media_dirname == AVID_MXF_DIRNAME)) + || + ((transition_count == 0) && (media_dirname == OMFI_MEDIAFILES_DIRNAME))) { + return true; + } + else return false; +} + +/* + * Returns true if the file or directory referenced by the path is + * below the AVID_MEDIAFILES_DIRNAME or OMFI_MEDIAFILES_DIRNAME + * directory The AVID_MEDIAFILES_DIRNAME and OMFI_MEDIAFILES_DIRNAME + * are assumed to be in the root directory, which is generally a safe + * assumption in the fixed-path world of Avid. + */ +static bool is_in_media_files(const char *path) +{ + bool ret = false; + + DEBUG(10, ("Entering with path '%s'\n", path)); + + if (starts_with_media_dir(AVID_MXF_DIRNAME, + AVID_MXF_DIRNAME_LEN, path) || + starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME, + OMFI_MEDIAFILES_DIRNAME_LEN, path)) { + ret = true; + } + DEBUG(10, ("Leaving with ret '%s'\n", + ret == true ? "true" : "false")); + return ret; +} + + +/* Add client suffix to "pure-number" path. + * + * Caller must free newPath. + * + * Success: return 0 + * Failure: set errno, newPath NULL, return -1 + */ +static int alloc_get_client_path(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const char *path_in, + char **path_out) +{ + int status = 0; + char *p; + char *digits; + size_t digits_len; + uintmax_t number; + + *path_out = talloc_strdup(ctx, path_in); + if (*path_out == NULL) { + DEBUG(1, ("alloc_get_client_path ENOMEM\n")); + return -1; + } + + (void)get_digit_group(*path_out, &number); + + digits = talloc_asprintf(NULL, "%ju", number); + if (digits == NULL) { + DEBUG(1, ("alloc_get_client_path ENOMEM\n")); + return -1; + } + digits_len = strlen(digits); + + p = strstr_m(path_in, digits); + if ((p) + && + ((p[digits_len] == '\0') || (p[digits_len] == '/')) + && + (((p - path_in > 0) && (p[-1] == '/')) + || + (((p - path_in) > APPLE_DOUBLE_PREFIX_LEN) + && + is_apple_double(p - APPLE_DOUBLE_PREFIX_LEN) + && + (p[-(APPLE_DOUBLE_PREFIX_LEN + 1)] == '/')))) + { + (*path_out)[p - path_in + digits_len] = '\0'; + + status = alloc_append_client_suffix(handle, path_out); + if (status != 0) { + goto out; + } + + *path_out = talloc_strdup_append(*path_out, p + digits_len); + if (*path_out == NULL) { + DEBUG(1, ("alloc_get_client_path ENOMEM\n")); + status = -1; + goto out; + } + } +out: + /* path_out must be freed in caller. */ + DEBUG(10, ("Result:'%s'\n", *path_out)); + return status; +} + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int alloc_get_client_smb_fname(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname, + struct smb_filename **client_fname) +{ + int status ; + + DEBUG(10, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + *client_fname = cp_smb_filename(ctx, smb_fname); + if (*client_fname == NULL) { + DEBUG(1, ("cp_smb_filename returned NULL\n")); + return -1; + } + status = alloc_get_client_path(handle, ctx, + smb_fname->base_name, + &(*client_fname)->base_name); + if (status != 0) { + return -1; + } + + DEBUG(10, ("Leaving with (*client_fname)->base_name " + "'%s'\n", (*client_fname)->base_name)); + + return 0; +} + + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int alloc_set_client_dirinfo_path(struct vfs_handle_struct *handle, + TALLOC_CTX *ctx, + char **path, + const char *suffix_number) +{ + int status; + + DEBUG(10, ("Entering with suffix_number '%s'\n", + suffix_number)); + + *path = talloc_strdup(ctx, suffix_number); + if (*path == NULL) { + DEBUG(1, ("alloc_set_client_dirinfo_path ENOMEM\n")); + return -1; + } + status = alloc_append_client_suffix(handle, path); + if (status != 0) { + return -1; + } + + DEBUG(10, ("Leaving with *path '%s'\n", *path)); + + return 0; +} + +static int alloc_set_client_dirinfo(vfs_handle_struct *handle, + const char *fname, + struct um_dirinfo_struct **di_result) +{ + int status = 0; + char *digits; + uintmax_t number; + struct um_dirinfo_struct *dip; + + DEBUG(10, ("Entering with fname '%s'\n", fname)); + + *di_result = talloc(NULL, struct um_dirinfo_struct); + if (*di_result == NULL) { + goto err; + } + dip = *di_result; + + dip->dirpath = talloc_strdup(dip, fname); + if (dip->dirpath == NULL) { + goto err; + } + + if (!is_in_media_files(fname)) { + dip->isInMediaFiles = false; + dip->clientPath = NULL; + dip->clientSubDirname = NULL; + goto out; + } + + dip->isInMediaFiles = true; + + (void)get_digit_group(fname, &number); + digits = talloc_asprintf(talloc_tos(), "%ju", number); + if (digits == NULL) { + goto err; + } + + status = alloc_set_client_dirinfo_path(handle, dip, + &dip->clientSubDirname, + digits); + if (status != 0) { + goto err; + } + + status = alloc_get_client_path(handle, dip, fname, + &dip->clientPath); + if (status != 0 || dip->clientPath == NULL) { + goto err; + } + +out: + DEBUG(10, ("Leaving with (*dirInfo)->dirpath '%s', " + "(*dirInfo)->clientPath '%s'\n", + dip->dirpath, dip->clientPath)); + return status; + +err: + DEBUG(1, ("Failing with fname '%s'\n", fname)); + TALLOC_FREE(*di_result); + status = -1; + errno = ENOMEM; + return status; +} + +/********************************************************************** + * VFS functions + **********************************************************************/ + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int um_statvfs(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct vfs_statvfs_struct *statbuf) +{ + int status; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering with path '%s'\n", smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf); + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_STATVFS(handle, client_fname, statbuf); +err: + TALLOC_FREE(client_fname); + DEBUG(10, ("Leaving with path '%s'\n", smb_fname->base_name)); + return status; +} + +static DIR *um_fdopendir(vfs_handle_struct *handle, + files_struct *fsp, + const char *mask, + uint32_t attr) +{ + struct um_dirinfo_struct *dirInfo = NULL; + DIR *dirstream; + + DEBUG(10, ("Entering with fsp->fsp_name->base_name '%s'\n", + fsp->fsp_name->base_name)); + + dirstream = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr); + if (!dirstream) { + goto err; + } + + if (alloc_set_client_dirinfo(handle, + fsp->fsp_name->base_name, + &dirInfo)) { + goto err; + } + + dirInfo->dirstream = dirstream; + + if (!dirInfo->isInMediaFiles) { + /* + * FIXME: this is the original code, something must be + * missing here, but what? -slow + */ + goto out; + } + +out: + DEBUG(10, ("Leaving with dirInfo->dirpath '%s', " + "dirInfo->clientPath '%s', " + "fsp->fsp_name->st.st_ex_mtime %s", + dirInfo->dirpath, + dirInfo->clientPath, + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)))); + return (DIR *) dirInfo; + +err: + DEBUG(1, ("Failing with fsp->fsp_name->base_name '%s'\n", + fsp->fsp_name->base_name)); + TALLOC_FREE(dirInfo); + return NULL; +} + +/* + * skip own suffixed directory + * replace own suffixed directory with non suffixed. + * + * Success: return dirent + * End of data: return NULL + * Failure: set errno, return NULL + */ +static struct dirent * +um_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp) +{ + um_dirinfo_struct* dirInfo = (um_dirinfo_struct*)dirp; + struct dirent *d = NULL; + int skip; + + DEBUG(10, ("dirInfo->dirpath '%s', " + "dirInfo->clientPath '%s', " + "dirInfo->isInMediaFiles '%s', " + "dirInfo->clientSubDirname '%s'\n", + dirInfo->dirpath, + dirInfo->clientPath, + dirInfo->isInMediaFiles ? "true" : "false", + dirInfo->clientSubDirname)); + + if (!dirInfo->isInMediaFiles) { + return SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream); + } + + do { + const char* dname; + bool isAppleDouble; + char *digits; + size_t digits_len; + uintmax_t number; + + skip = false; + d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream); + + if (d == NULL) { + break; + } + + /* ignore apple double prefix for logic below */ + if (is_apple_double(d->d_name)) { + dname = &d->d_name[APPLE_DOUBLE_PREFIX_LEN]; + isAppleDouble = true; + } else { + dname = d->d_name; + isAppleDouble = false; + } + + DEBUG(10, ("dname = '%s'\n", dname)); + + (void)get_digit_group(dname, &number); + digits = talloc_asprintf(talloc_tos(), "%ju", number); + if (digits == NULL) { + DEBUG(1, ("out of memory\n")); + goto err; + } + digits_len = strlen(digits); + + if (alloc_set_client_dirinfo_path(handle, + dirInfo, + &((dirInfo)->clientSubDirname), + digits)) { + goto err; + } + + /* + * If set to "true", vfs shows digits-only + * non-suffixed subdirectories. Normally, such + * subdirectories can exists only in non-media + * directories, so we set it to "false". Otherwise, + * if we have such subdirectories (probably created + * over not "unityed" connection), it can be little + * bit confusing. + */ + if (strequal(dname, digits)) { + skip = false; + } else if (strequal(dname, dirInfo->clientSubDirname)) { + /* + * Remove suffix of this client's suffixed + * subdirectories + */ + if (isAppleDouble) { + d->d_name[digits_len + APPLE_DOUBLE_PREFIX_LEN] = '\0'; + } else { + d->d_name[digits_len] = '\0'; + } + } else if (strnequal(digits, dname, digits_len)) { + /* + * Set to false to see another clients subdirectories + */ + skip = false; + } + } while (skip); + + DEBUG(10, ("Leaving um_readdir\n")); + return d; +err: + TALLOC_FREE(dirInfo); + return NULL; +} + +static void um_rewinddir(vfs_handle_struct *handle, + DIR *dirp) +{ + DEBUG(10, ("Entering and leaving um_rewinddir\n")); + SMB_VFS_NEXT_REWINDDIR(handle, + ((um_dirinfo_struct*)dirp)->dirstream); +} + +static int um_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + int status; + const char *path = NULL; + struct smb_filename *client_fname = NULL; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + path = full_fname->base_name; + DEBUG(10, ("Entering with path '%s'\n", path)); + + if (!is_in_media_files(path) || !is_in_media_dir(path)) { + TALLOC_FREE(full_fname); + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + full_fname, + &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + client_fname, + mode); +err: + DEBUG(10, ("Leaving with path '%s'\n", path)); + TALLOC_FREE(client_fname); + TALLOC_FREE(full_fname); + return status; +} + +static int um_closedir(vfs_handle_struct *handle, + DIR *dirp) +{ + DIR *realdirp = ((um_dirinfo_struct*)dirp)->dirstream; + + TALLOC_FREE(dirp); + + return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp); +} + +static int um_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + struct smb_filename *client_fname = NULL; + int ret; + + DBG_DEBUG("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + if (alloc_get_client_smb_fname(handle, talloc_tos(), + smb_fname, + &client_fname)) { + ret = -1; + goto err; + } + + /* + * FIXME: + * What about fsp->fsp_name? We also have to get correct stat + * info into fsp and smb_fname for DB files, don't we? + */ + + DEBUG(10, ("Leaving with smb_fname->base_name '%s' " + "smb_fname->st.st_ex_mtime %s" + "fsp->fsp_name->st.st_ex_mtime %s", + smb_fname->base_name, + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)), + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)))); + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + client_fname, + fsp, + how); +err: + TALLOC_FREE(client_fname); + DEBUG(10, ("Leaving with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + return ret; +} + +static NTSTATUS um_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result_fsp, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS status; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_CREATE_FILE( + handle, + req, + dirfsp, + smb_fname, + access_mask, + share_access, + create_disposition, + create_options, + file_attributes, + oplock_request, + lease, + allocation_size, + private_flags, + sd, + ea_list, + result_fsp, + pinfo, + in_context_blobs, + out_context_blobs); + } + + if (alloc_get_client_smb_fname(handle, talloc_tos(), + smb_fname, + &client_fname)) { + status = map_nt_error_from_unix(errno); + goto err; + } + + /* + * FIXME: + * This only creates files, so we don't have to worry about + * our fake directory stat'ing here. But we still need to + * route stat calls for DB files properly, right? + */ + status = SMB_VFS_NEXT_CREATE_FILE( + handle, + req, + dirfsp, + client_fname, + access_mask, + share_access, + create_disposition, + create_options, + file_attributes, + oplock_request, + lease, + allocation_size, + private_flags, + sd, + ea_list, + result_fsp, + pinfo, + in_context_blobs, + out_context_blobs); +err: + TALLOC_FREE(client_fname); + DEBUG(10, ("Leaving with smb_fname->base_name '%s'" + "smb_fname->st.st_ex_mtime %s" + " fsp->fsp_name->st.st_ex_mtime %s", + smb_fname->base_name, + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)), + (*result_fsp) && VALID_STAT((*result_fsp)->fsp_name->st) ? + ctime(&((*result_fsp)->fsp_name->st.st_ex_mtime.tv_sec)) : + "No fsp time\n")); + return status; +} + +static int um_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int status; + struct smb_filename *src_full_fname = NULL; + struct smb_filename *dst_full_fname = NULL; + struct smb_filename *src_client_fname = NULL; + struct smb_filename *dst_client_fname = NULL; + + src_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (src_full_fname == NULL) { + errno = ENOMEM; + return -1; + } + dst_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (dst_full_fname == NULL) { + TALLOC_FREE(src_full_fname); + errno = ENOMEM; + return -1; + } + + DBG_DEBUG( "Entering with " + "smb_fname_src->base_name '%s', " + "smb_fname_dst->base_name '%s'\n", + smb_fname_src->base_name, + smb_fname_dst->base_name); + + if (!is_in_media_files(src_full_fname->base_name) + && + !is_in_media_files(dst_full_fname->base_name)) { + TALLOC_FREE(src_full_fname); + TALLOC_FREE(dst_full_fname); + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + src_full_fname, + &src_client_fname); + if (status != 0) { + goto err; + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + dst_full_fname, + &dst_client_fname); + + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_RENAMEAT(handle, + handle->conn->cwd_fsp, + src_client_fname, + handle->conn->cwd_fsp, + dst_client_fname); + +err: + TALLOC_FREE(dst_client_fname); + TALLOC_FREE(src_client_fname); + TALLOC_FREE(src_full_fname); + TALLOC_FREE(dst_full_fname); + DBG_DEBUG( "Leaving with smb_fname_src->base_name '%s'," + " smb_fname_dst->base_name '%s'\n", + smb_fname_src->base_name, + smb_fname_dst->base_name); + return status; +} + + +/* + * Success: return 0 + * Failure: set errno, return -1 + */ +static int um_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int status = 0; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + smb_fname, + &client_fname); + if (status != 0) { + goto err; + } + DEBUG(10, ("Stat'ing client_fname->base_name '%s'\n", + client_fname->base_name)); + + status = SMB_VFS_NEXT_STAT(handle, client_fname); + if (status != 0) { + goto err; + } + + /* + * Unlike functions with const smb_filename, we have to modify + * smb_fname itself to pass our info back up. + */ + DEBUG(10, ("Setting smb_fname '%s' stat from client_fname '%s'\n", + smb_fname->base_name, client_fname->base_name)); + smb_fname->st = client_fname->st; + +err: + TALLOC_FREE(client_fname); + DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s", + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)))); + return status; +} + +static int um_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int status = 0; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering with smb_fname->base_name '%s'\n", + smb_fname->base_name)); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + client_fname = NULL; + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + smb_fname, + &client_fname); + if (status != 0) { + goto err; + } + status = SMB_VFS_NEXT_LSTAT(handle, client_fname); + if (status != 0) { + goto err; + } + + smb_fname->st = client_fname->st; + +err: + TALLOC_FREE(client_fname); + DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s", + ctime(&(smb_fname->st.st_ex_mtime.tv_sec)))); + return status; +} + +static int um_fstat(vfs_handle_struct *handle, + files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int status = 0; + + DEBUG(10, ("Entering with fsp->fsp_name->base_name " + "'%s'\n", fsp_str_dbg(fsp))); + + status = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (status != 0) { + goto out; + } + + if ((fsp->fsp_name == NULL) || + !is_in_media_files(fsp->fsp_name->base_name)) { + goto out; + } + + status = um_stat(handle, fsp->fsp_name); + if (status != 0) { + goto out; + } + + *sbuf = fsp->fsp_name->st; + +out: + DEBUG(10, ("Leaving with fsp->fsp_name->st.st_ex_mtime %s", + fsp->fsp_name != NULL ? + ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)) : "0\n")); + return status; +} + +static int um_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret; + struct smb_filename *full_fname = NULL; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering um_unlinkat\n")); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + ret = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, + &client_fname); + if (ret != 0) { + goto err; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp->conn->cwd_fsp, + client_fname, + flags); + +err: + TALLOC_FREE(full_fname); + TALLOC_FREE(client_fname); + return ret; +} + +static int um_lchown(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uid_t uid, + gid_t gid) +{ + int status; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering um_lchown\n")); + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid); + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_LCHOWN(handle, client_fname, uid, gid); + +err: + TALLOC_FREE(client_fname); + return status; +} + +static int um_chdir(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int status; + struct smb_filename *client_fname = NULL; + + DEBUG(10, ("Entering um_chdir\n")); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); + } + + status = alloc_get_client_smb_fname(handle, + talloc_tos(), + smb_fname, + &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_CHDIR(handle, client_fname); + +err: + TALLOC_FREE(client_fname); + return status; +} + +static int um_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int status; + struct smb_filename *new_link_target = NULL; + struct smb_filename *new_client_fname = NULL; + struct smb_filename *full_fname = NULL; + + DEBUG(10, ("Entering um_symlinkat\n")); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + new_smb_fname); + if (full_fname == NULL) { + return -1; + } + + if (!is_in_media_files(link_contents->base_name) && + !is_in_media_files(full_fname->base_name)) { + TALLOC_FREE(full_fname); + return SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + link_contents, &new_link_target); + if (status != 0) { + goto err; + } + status = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, &new_client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_SYMLINKAT(handle, + new_link_target, + handle->conn->cwd_fsp, + new_client_fname); + +err: + TALLOC_FREE(new_link_target); + TALLOC_FREE(new_client_fname); + TALLOC_FREE(full_fname); + return status; +} + +static int um_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + char *buf, + size_t bufsiz) +{ + int status; + struct smb_filename *client_fname = NULL; + struct smb_filename *full_fname = NULL; + + DEBUG(10, ("Entering um_readlinkat\n")); + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + if (!is_in_media_files(full_fname->base_name)) { + TALLOC_FREE(full_fname); + return SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + smb_fname, + buf, + bufsiz); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + client_fname, + buf, + bufsiz); + +err: + TALLOC_FREE(full_fname); + TALLOC_FREE(client_fname); + return status; +} + +static int um_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int status; + struct smb_filename *old_full_fname = NULL; + struct smb_filename *new_full_fname = NULL; + struct smb_filename *old_client_fname = NULL; + struct smb_filename *new_client_fname = NULL; + + old_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + old_smb_fname); + if (old_full_fname == NULL) { + return -1; + } + new_full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + new_smb_fname); + if (new_full_fname == NULL) { + TALLOC_FREE(old_full_fname); + return -1; + } + + DEBUG(10, ("Entering um_linkat\n")); + if (!is_in_media_files(old_full_fname->base_name) && + !is_in_media_files(new_full_fname->base_name)) { + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + return SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + old_full_fname, &old_client_fname); + if (status != 0) { + goto err; + } + status = alloc_get_client_smb_fname(handle, talloc_tos(), + new_full_fname, &new_client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_LINKAT(handle, + handle->conn->cwd_fsp, + old_client_fname, + handle->conn->cwd_fsp, + new_client_fname, + flags); + +err: + TALLOC_FREE(old_full_fname); + TALLOC_FREE(new_full_fname); + TALLOC_FREE(old_client_fname); + TALLOC_FREE(new_client_fname); + return status; +} + +static int um_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + int status; + struct smb_filename *client_fname = NULL; + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + DEBUG(10, ("Entering um_mknodat\n")); + if (!is_in_media_files(full_fname->base_name)) { + TALLOC_FREE(full_fname); + return SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + smb_fname, + mode, + dev); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + full_fname, &client_fname); + if (status != 0) { + goto err; + } + + status = SMB_VFS_NEXT_MKNODAT(handle, + handle->conn->cwd_fsp, + client_fname, + mode, + dev); + +err: + TALLOC_FREE(client_fname); + TALLOC_FREE(full_fname); + return status; +} + +static struct smb_filename *um_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname) +{ + struct smb_filename *client_fname = NULL; + struct smb_filename *result_fname = NULL; + int status; + + DEBUG(10, ("Entering um_realpath\n")); + + if (!is_in_media_files(smb_fname->base_name)) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); + } + + status = alloc_get_client_smb_fname(handle, talloc_tos(), + smb_fname, &client_fname); + if (status != 0) { + goto err; + } + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, client_fname); + +err: + TALLOC_FREE(client_fname); + return result_fname; +} + +static int um_connect(vfs_handle_struct *handle, + const char *service, + const char *user) +{ + int rc; + struct um_config_data *config; + int enumval; + + rc = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (rc != 0) { + return rc; + } + + config = talloc_zero(handle->conn, struct um_config_data); + if (!config) { + DEBUG(1, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + enumval = lp_parm_enum(SNUM(handle->conn), UM_PARAM_TYPE_NAME, + "clientid", um_clientid, UM_CLIENTID_NAME); + if (enumval == -1) { + DEBUG(1, ("value for %s: type unknown\n", + UM_PARAM_TYPE_NAME)); + return -1; + } + config->clientid = (enum um_clientid)enumval; + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct um_config_data, + return -1); + + return 0; +} + +/* VFS operations structure */ + +static struct vfs_fn_pointers vfs_um_fns = { + .connect_fn = um_connect, + + /* Disk operations */ + + .statvfs_fn = um_statvfs, + + /* Directory operations */ + + .fdopendir_fn = um_fdopendir, + .readdir_fn = um_readdir, + .rewind_dir_fn = um_rewinddir, + .mkdirat_fn = um_mkdirat, + .closedir_fn = um_closedir, + + /* File operations */ + + .openat_fn = um_openat, + .create_file_fn = um_create_file, + .renameat_fn = um_renameat, + .stat_fn = um_stat, + .lstat_fn = um_lstat, + .fstat_fn = um_fstat, + .unlinkat_fn = um_unlinkat, + .lchown_fn = um_lchown, + .chdir_fn = um_chdir, + .symlinkat_fn = um_symlinkat, + .readlinkat_fn = um_readlinkat, + .linkat_fn = um_linkat, + .mknodat_fn = um_mknodat, + .realpath_fn = um_realpath, + + /* EA operations. */ + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, +}; + +static_decl_vfs; +NTSTATUS vfs_unityed_media_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "unityed_media", &vfs_um_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + vfs_um_debug_level = debug_add_class("unityed_media"); + + if (vfs_um_debug_level == -1) { + vfs_um_debug_level = DBGC_VFS; + DEBUG(1, ("unityed_media_init: Couldn't register custom " + "debugging class.\n")); + } + + return ret; +} diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c new file mode 100644 index 0000000..ea1886d --- /dev/null +++ b/source3/modules/vfs_virusfilter.c @@ -0,0 +1,1677 @@ +/* + * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + * Copyright (C) 2016-2017 Trever L. Adams + * Copyright (C) 2017 Ralph Boehme + * Copyright (C) 2017 Jeremy Allison + * + * 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 "vfs_virusfilter_common.h" +#include "vfs_virusfilter_utils.h" + +/* + * Default configuration values + * ====================================================================== + */ + +#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX "virusfilter." +#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX ".infected" +#define VIRUSFILTER_DEFAULT_RENAME_PREFIX "virusfilter." +#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX ".infected" + +/* ====================================================================== */ + +enum virusfilter_scanner_enum { + VIRUSFILTER_SCANNER_CLAMAV, + VIRUSFILTER_SCANNER_DUMMY, + VIRUSFILTER_SCANNER_FSAV, + VIRUSFILTER_SCANNER_SOPHOS +}; + +static const struct enum_list scanner_list[] = { + { VIRUSFILTER_SCANNER_CLAMAV, "clamav" }, + { VIRUSFILTER_SCANNER_DUMMY, "dummy" }, + { VIRUSFILTER_SCANNER_FSAV, "fsav" }, + { VIRUSFILTER_SCANNER_SOPHOS, "sophos" }, + { -1, NULL } +}; + +static const struct enum_list virusfilter_actions[] = { + { VIRUSFILTER_ACTION_QUARANTINE, "quarantine" }, + { VIRUSFILTER_ACTION_RENAME, "rename" }, + { VIRUSFILTER_ACTION_DELETE, "delete" }, + + /* alias for "delete" */ + { VIRUSFILTER_ACTION_DELETE, "remove" }, + + /* alias for "delete" */ + { VIRUSFILTER_ACTION_DELETE, "unlink" }, + { VIRUSFILTER_ACTION_DO_NOTHING, "nothing" }, + { -1, NULL} +}; + +static int virusfilter_config_destructor(struct virusfilter_config *config) +{ + TALLOC_FREE(config->backend); + return 0; +} + +/* + * This is adapted from vfs_recycle module. + * Caller must have become_root(); + */ +static bool quarantine_directory_exist( + struct vfs_handle_struct *handle, + const char *dname) +{ + int ret = -1; + struct smb_filename smb_fname = { + .base_name = discard_const_p(char, dname) + }; + + ret = SMB_VFS_STAT(handle->conn, &smb_fname); + if (ret == 0) { + return S_ISDIR(smb_fname.st.st_ex_mode); + } + + return false; +} + +/** + * Create directory tree + * @param conn connection + * @param dname Directory tree to be created + * @return Returns true for success + * This is adapted from vfs_recycle module. + * Caller must have become_root(); + */ +static bool quarantine_create_dir( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *dname) +{ + size_t len = 0; + size_t cat_len = 0; + char *new_dir = NULL; + char *tmp_str = NULL; + char *token = NULL; + char *tok_str = NULL; + bool status = false; + bool ok = false; + int ret = -1; + char *saveptr = NULL; + + tmp_str = talloc_strdup(talloc_tos(), dname); + if (tmp_str == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + errno = ENOMEM; + goto done; + } + tok_str = tmp_str; + + len = strlen(dname)+1; + new_dir = (char *)talloc_size(talloc_tos(), len + 1); + if (new_dir == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + errno = ENOMEM; + goto done; + } + *new_dir = '\0'; + if (dname[0] == '/') { + /* Absolute path. */ + cat_len = strlcat(new_dir, "/", len + 1); + if (cat_len >= len+1) { + goto done; + } + } + + /* Create directory tree if necessary */ + for (token = strtok_r(tok_str, "/", &saveptr); + token != NULL; + token = strtok_r(NULL, "/", &saveptr)) + { + cat_len = strlcat(new_dir, token, len + 1); + if (cat_len >= len+1) { + goto done; + } + ok = quarantine_directory_exist(handle, new_dir); + if (ok == true) { + DBG_DEBUG("quarantine: dir %s already exists\n", + new_dir); + } else { + struct smb_filename *smb_fname = NULL; + + DBG_INFO("quarantine: creating new dir %s\n", new_dir); + + smb_fname = synthetic_smb_fname(talloc_tos(), + new_dir, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + goto done; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + handle->conn->cwd_fsp, + smb_fname, + config->quarantine_dir_mode); + if (ret != 0) { + TALLOC_FREE(smb_fname); + + DBG_WARNING("quarantine: mkdirat failed for %s " + "with error: %s\n", new_dir, + strerror(errno)); + status = false; + goto done; + } + TALLOC_FREE(smb_fname); + } + cat_len = strlcat(new_dir, "/", len + 1); + if (cat_len >= len + 1) { + goto done; + } + } + + status = true; +done: + TALLOC_FREE(tmp_str); + TALLOC_FREE(new_dir); + return status; +} + +static int virusfilter_vfs_connect( + struct vfs_handle_struct *handle, + const char *svc, + const char *user) +{ + int snum = SNUM(handle->conn); + struct virusfilter_config *config = NULL; + const char *exclude_files = NULL; + const char *infected_files = NULL; + const char *temp_quarantine_dir_mode = NULL; + const char *infected_file_command = NULL; + const char *scan_error_command = NULL; + const char *quarantine_dir = NULL; + const char *quarantine_prefix = NULL; + const char *quarantine_suffix = NULL; + const char *rename_prefix = NULL; + const char *rename_suffix = NULL; + const char *socket_path = NULL; + char *sret = NULL; + char *tmp = NULL; + enum virusfilter_scanner_enum backend; + int connect_timeout = 0; + int io_timeout = 0; + int ret = -1; + + config = talloc_zero(handle, struct virusfilter_config); + if (config == NULL) { + DBG_ERR("talloc_zero failed\n"); + return -1; + } + talloc_set_destructor(config, virusfilter_config_destructor); + + SMB_VFS_HANDLE_SET_DATA(handle, config, NULL, + struct virusfilter_config, return -1); + + config->scan_request_limit = lp_parm_int( + snum, "virusfilter", "scan request limit", 0); + + config->scan_on_open = lp_parm_bool( + snum, "virusfilter", "scan on open", true); + + config->scan_on_close = lp_parm_bool( + snum, "virusfilter", "scan on close", false); + + config->max_nested_scan_archive = lp_parm_int( + snum, "virusfilter", "max nested scan archive", 1); + + config->scan_archive = lp_parm_bool( + snum, "virusfilter", "scan archive", false); + + config->scan_mime = lp_parm_bool( + snum, "virusfilter", "scan mime", false); + + config->max_file_size = (ssize_t)lp_parm_ulong( + snum, "virusfilter", "max file size", 100000000L); + + config->min_file_size = (ssize_t)lp_parm_ulong( + snum, "virusfilter", "min file size", 10); + + exclude_files = lp_parm_const_string( + snum, "virusfilter", "exclude files", NULL); + if (exclude_files != NULL) { + set_namearray(&config->exclude_files, exclude_files); + } + + infected_files = lp_parm_const_string( + snum, "virusfilter", "infected files", NULL); + if (infected_files != NULL) { + set_namearray(&config->infected_files, infected_files); + } + + config->cache_entry_limit = lp_parm_int( + snum, "virusfilter", "cache entry limit", 100); + + config->cache_time_limit = lp_parm_int( + snum, "virusfilter", "cache time limit", 10); + + config->infected_file_action = lp_parm_enum( + snum, "virusfilter", "infected file action", + virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING); + + infected_file_command = lp_parm_const_string( + snum, "virusfilter", "infected file command", NULL); + if (infected_file_command != NULL) { + config->infected_file_command = talloc_strdup( + config, + infected_file_command); + if (config->infected_file_command == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + scan_error_command = lp_parm_const_string( + snum, "virusfilter", "scan error command", NULL); + if (scan_error_command != NULL) { + config->scan_error_command = talloc_strdup(config, + scan_error_command); + if (config->scan_error_command == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + config->block_access_on_error = lp_parm_bool( + snum, "virusfilter", "block access on error", false); + + tmp = talloc_asprintf(config, "%s/.quarantine", + handle->conn->connectpath); + + quarantine_dir = lp_parm_const_string( + snum, "virusfilter", "quarantine directory", + tmp ? tmp : "/tmp/.quarantine"); + if (quarantine_dir != NULL) { + config->quarantine_dir = talloc_strdup(config, quarantine_dir); + if (config->quarantine_dir == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + if (tmp != config->quarantine_dir) { + TALLOC_FREE(tmp); + } + + temp_quarantine_dir_mode = lp_parm_const_string( + snum, "virusfilter", "quarantine directory mode", "0755"); + if (temp_quarantine_dir_mode != NULL) { + unsigned int mode = 0; + sscanf(temp_quarantine_dir_mode, "%o", &mode); + config->quarantine_dir_mode = mode; + } + + quarantine_prefix = lp_parm_const_string( + snum, "virusfilter", "quarantine prefix", + VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX); + if (quarantine_prefix != NULL) { + config->quarantine_prefix = talloc_strdup(config, + quarantine_prefix); + if (config->quarantine_prefix == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + quarantine_suffix = lp_parm_const_string( + snum, "virusfilter", "quarantine suffix", + VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX); + if (quarantine_suffix != NULL) { + config->quarantine_suffix = talloc_strdup(config, + quarantine_suffix); + if (config->quarantine_suffix == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + /* + * Make sure prefixes and suffixes do not contain directory + * delimiters + */ + if (config->quarantine_prefix != NULL) { + sret = strstr(config->quarantine_prefix, "/"); + if (sret != NULL) { + DBG_ERR("quarantine prefix must not contain directory " + "delimiter(s) such as '/' (%s replaced with %s)\n", + config->quarantine_prefix, + VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX); + config->quarantine_prefix = + VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX; + } + } + if (config->quarantine_suffix != NULL) { + sret = strstr(config->quarantine_suffix, "/"); + if (sret != NULL) { + DBG_ERR("quarantine suffix must not contain directory " + "delimiter(s) such as '/' (%s replaced with %s)\n", + config->quarantine_suffix, + VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX); + config->quarantine_suffix = + VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX; + } + } + + config->quarantine_keep_tree = lp_parm_bool( + snum, "virusfilter", "quarantine keep tree", true); + + config->quarantine_keep_name = lp_parm_bool( + snum, "virusfilter", "quarantine keep name", true); + + rename_prefix = lp_parm_const_string( + snum, "virusfilter", "rename prefix", + VIRUSFILTER_DEFAULT_RENAME_PREFIX); + if (rename_prefix != NULL) { + config->rename_prefix = talloc_strdup(config, rename_prefix); + if (config->rename_prefix == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + rename_suffix = lp_parm_const_string( + snum, "virusfilter", "rename suffix", + VIRUSFILTER_DEFAULT_RENAME_SUFFIX); + if (rename_suffix != NULL) { + config->rename_suffix = talloc_strdup(config, rename_suffix); + if (config->rename_suffix == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + /* + * Make sure prefixes and suffixes do not contain directory + * delimiters + */ + if (config->rename_prefix != NULL) { + sret = strstr(config->rename_prefix, "/"); + if (sret != NULL) { + DBG_ERR("rename prefix must not contain directory " + "delimiter(s) such as '/' (%s replaced with %s)\n", + config->rename_prefix, + VIRUSFILTER_DEFAULT_RENAME_PREFIX); + config->rename_prefix = + VIRUSFILTER_DEFAULT_RENAME_PREFIX; + } + } + if (config->rename_suffix != NULL) { + sret = strstr(config->rename_suffix, "/"); + if (sret != NULL) { + DBG_ERR("rename suffix must not contain directory " + "delimiter(s) such as '/' (%s replaced with %s)\n", + config->rename_suffix, + VIRUSFILTER_DEFAULT_RENAME_SUFFIX); + config->rename_suffix = + VIRUSFILTER_DEFAULT_RENAME_SUFFIX; + } + } + + config->infected_open_errno = lp_parm_int( + snum, "virusfilter", "infected file errno on open", EACCES); + + config->infected_close_errno = lp_parm_int( + snum, "virusfilter", "infected file errno on close", 0); + + config->scan_error_open_errno = lp_parm_int( + snum, "virusfilter", "scan error errno on open", EACCES); + + config->scan_error_close_errno = lp_parm_int( + snum, "virusfilter", "scan error errno on close", 0); + + socket_path = lp_parm_const_string( + snum, "virusfilter", "socket path", NULL); + if (socket_path != NULL) { + config->socket_path = talloc_strdup(config, socket_path); + if (config->socket_path == NULL) { + DBG_ERR("virusfilter-vfs: out of memory!\n"); + return -1; + } + } + + /* canonicalize socket_path */ + if (config->socket_path != NULL && config->socket_path[0] != '/') { + DBG_ERR("socket path must be an absolute path. " + "Using backend default\n"); + config->socket_path = NULL; + } + if (config->socket_path != NULL) { + config->socket_path = canonicalize_absolute_path( + handle, config->socket_path); + if (config->socket_path == NULL) { + errno = ENOMEM; + return -1; + } + } + + connect_timeout = lp_parm_int(snum, "virusfilter", + "connect timeout", 30000); + + io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000); + + config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout); + if (config->io_h == NULL) { + DBG_ERR("virusfilter_io_new failed\n"); + return -1; + } + + if (config->cache_entry_limit > 0) { + config->cache = virusfilter_cache_new(handle, + config->cache_entry_limit, + config->cache_time_limit); + if (config->cache == NULL) { + DBG_ERR("Initializing cache failed: Cache disabled\n"); + return -1; + } + } + + /* + * Check quarantine directory now to save processing + * and becoming root over and over. + */ + if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) { + bool ok = true; + bool dir_exists; + + /* + * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir) + * hierarchy + */ + become_root(); + dir_exists = quarantine_directory_exist(handle, + config->quarantine_dir); + if (!dir_exists) { + DBG_DEBUG("Creating quarantine directory: %s\n", + config->quarantine_dir); + ok = quarantine_create_dir(handle, config, + config->quarantine_dir); + } + unbecome_root(); + if (!ok) { + DBG_ERR("Creating quarantine directory %s " + "failed with %s\n", + config->quarantine_dir, + strerror(errno)); + return -1; + } + } + + /* + * Now that the frontend options are initialized, load the configured + * backend. + */ + + backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum, + "virusfilter", + "scanner", + scanner_list, + -1); + if (backend == (enum virusfilter_scanner_enum)-1) { + DBG_ERR("No AV-Scanner configured, " + "please set \"virusfilter:scanner\"\n"); + return -1; + } + + switch (backend) { + case VIRUSFILTER_SCANNER_SOPHOS: + ret = virusfilter_sophos_init(config); + break; + case VIRUSFILTER_SCANNER_FSAV: + ret = virusfilter_fsav_init(config); + break; + case VIRUSFILTER_SCANNER_CLAMAV: + ret = virusfilter_clamav_init(config); + break; + case VIRUSFILTER_SCANNER_DUMMY: + ret = virusfilter_dummy_init(config); + break; + default: + DBG_ERR("Unhandled scanner %d\n", backend); + return -1; + } + if (ret != 0) { + DBG_ERR("Scanner backend init failed\n"); + return -1; + } + + if (config->backend->fns->connect != NULL) { + ret = config->backend->fns->connect(handle, config, svc, user); + if (ret == -1) { + return -1; + } + } + + return SMB_VFS_NEXT_CONNECT(handle, svc, user); +} + +static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle) +{ + struct virusfilter_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return); + + if (config->backend->fns->disconnect != NULL) { + config->backend->fns->disconnect(handle); + } + + free_namearray(config->exclude_files); + virusfilter_io_disconnect(config->io_h); + + SMB_VFS_NEXT_DISCONNECT(handle); +} + +static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx, + struct virusfilter_config *config, + char **env_list) +{ + int ret; + + ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION", + VIRUSFILTER_VERSION); + if (ret == -1) { + return -1; + } + ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME", + config->backend->name); + if (ret == -1) { + return -1; + } + + if (config->backend->version != 0) { + char *version = NULL; + + version = talloc_asprintf(talloc_tos(), "%u", + config->backend->version); + if (version == NULL) { + return -1; + } + ret = virusfilter_env_set(mem_ctx, env_list, + "VIRUSFILTER_MODULE_VERSION", + version); + TALLOC_FREE(version); + if (ret == -1) { + return -1; + } + } + + return 0; +} + +static char *quarantine_check_tree(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct smb_filename *smb_fname, + char *q_dir_in, + char *cwd_fname) +{ + char *temp_path = NULL; + char *q_dir_out = NULL; + bool ok; + + temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname); + if (temp_path == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + goto out; + } + + become_root(); + ok = quarantine_directory_exist(handle, temp_path); + unbecome_root(); + if (ok) { + DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path); + q_dir_out = talloc_move(mem_ctx, &temp_path); + goto out; + } + + DBG_DEBUG("quarantine: Creating directory %s\n", temp_path); + + become_root(); + ok = quarantine_create_dir(handle, config, temp_path); + unbecome_root(); + if (!ok) { + DBG_NOTICE("Could not create quarantine directory [%s], " + "ignoring for [%s]\n", + temp_path, smb_fname_str_dbg(smb_fname)); + goto out; + } + + q_dir_out = talloc_move(mem_ctx, &temp_path); + +out: + TALLOC_FREE(temp_path); + return q_dir_out; +} + +static virusfilter_action infected_file_action_quarantine( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + TALLOC_CTX *mem_ctx, + const struct files_struct *fsp, + const char **filepath_newp) +{ + TALLOC_CTX *frame = talloc_stackframe(); + connection_struct *conn = handle->conn; + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + char *fname = fsp->fsp_name->base_name; + const struct smb_filename *smb_fname = fsp->fsp_name; + struct smb_filename *q_smb_fname = NULL; + char *q_dir = NULL; + char *q_prefix = NULL; + char *q_suffix = NULL; + char *q_filepath = NULL; + char *dir_name = NULL; + const char *base_name = NULL; + char *rand_filename_component = NULL; + virusfilter_action action = VIRUSFILTER_ACTION_QUARANTINE; + bool ok = false; + int ret = -1; + int saved_errno = 0; + + q_dir = virusfilter_string_sub(frame, conn, + config->quarantine_dir); + q_prefix = virusfilter_string_sub(frame, conn, + config->quarantine_prefix); + q_suffix = virusfilter_string_sub(frame, conn, + config->quarantine_suffix); + if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) { + DBG_ERR("Quarantine failed: %s/%s: Cannot allocate " + "memory\n", cwd_fname, fname); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + if (config->quarantine_keep_name || config->quarantine_keep_tree) { + ok = parent_dirname(frame, smb_fname->base_name, + &dir_name, &base_name); + if (!ok) { + DBG_ERR("parent_dirname failed\n"); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + if (config->quarantine_keep_tree) { + char *tree = NULL; + + tree = quarantine_check_tree(frame, handle, config, + smb_fname, q_dir, + cwd_fname); + if (tree == NULL) { + /* + * If we can't create the tree, just move it + * into the toplevel quarantine dir. + */ + tree = q_dir; + } + q_dir = tree; + } + } + + /* Get a 16 byte + \0 random filename component. */ + rand_filename_component = generate_random_str(frame, 16); + if (rand_filename_component == NULL) { + DBG_ERR("generate_random_str failed\n"); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + if (config->quarantine_keep_name) { + q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s", + q_dir, q_prefix, + base_name, q_suffix, + rand_filename_component); + } else { + q_filepath = talloc_asprintf(frame, "%s/%s%s", + q_dir, q_prefix, + rand_filename_component); + } + if (q_filepath == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + q_smb_fname = synthetic_smb_fname(frame, + q_filepath, + smb_fname->stream_name, + NULL, + 0, + smb_fname->flags); + if (q_smb_fname == NULL) { + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + become_root(); + ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + unbecome_root(); + if (ret == -1) { + DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n", + cwd_fname, fname, q_filepath, strerror(saved_errno)); + errno = saved_errno; + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + *filepath_newp = talloc_move(mem_ctx, &q_filepath); + +out: + TALLOC_FREE(frame); + return action; +} + +static virusfilter_action infected_file_action_rename( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + TALLOC_CTX *mem_ctx, + const struct files_struct *fsp, + const char **filepath_newp) +{ + TALLOC_CTX *frame = talloc_stackframe(); + connection_struct *conn = handle->conn; + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + char *fname = fsp->fsp_name->base_name; + const struct smb_filename *smb_fname = fsp->fsp_name; + struct smb_filename *q_smb_fname = NULL; + char *q_dir = NULL; + char *q_prefix = NULL; + char *q_suffix = NULL; + char *q_filepath = NULL; + const char *base_name = NULL; + virusfilter_action action = VIRUSFILTER_ACTION_RENAME; + bool ok = false; + int ret = -1; + int saved_errno = 0; + + q_prefix = virusfilter_string_sub(frame, conn, + config->rename_prefix); + q_suffix = virusfilter_string_sub(frame, conn, + config->rename_suffix); + if (q_prefix == NULL || q_suffix == NULL) { + DBG_ERR("Rename failed: %s/%s: Cannot allocate " + "memory\n", cwd_fname, fname); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + ok = parent_dirname(frame, fname, &q_dir, &base_name); + if (!ok) { + DBG_ERR("Rename failed: %s/%s: Cannot allocate " + "memory\n", cwd_fname, fname); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + if (q_dir == NULL) { + DBG_ERR("Rename failed: %s/%s: Cannot allocate " + "memory\n", cwd_fname, fname); + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir, + q_prefix, base_name, q_suffix); + + q_smb_fname = synthetic_smb_fname(frame, q_filepath, + smb_fname->stream_name, NULL, + 0, + smb_fname->flags); + if (q_smb_fname == NULL) { + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + become_root(); + ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname); + if (ret == -1) { + saved_errno = errno; + } + unbecome_root(); + + if (ret == -1) { + DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n", + cwd_fname, fname, strerror(saved_errno)); + errno = saved_errno; + action = VIRUSFILTER_ACTION_DO_NOTHING; + goto out; + } + + *filepath_newp = talloc_move(mem_ctx, &q_filepath); + +out: + TALLOC_FREE(frame); + return action; +} + +static virusfilter_action infected_file_action_delete( + struct vfs_handle_struct *handle, + const struct files_struct *fsp) +{ + int ret; + int saved_errno = 0; + + become_root(); + ret = SMB_VFS_NEXT_UNLINKAT(handle, + handle->conn->cwd_fsp, + fsp->fsp_name, + 0); + if (ret == -1) { + saved_errno = errno; + } + unbecome_root(); + if (ret == -1) { + DBG_ERR("Delete [%s/%s] failed: %s\n", + fsp->conn->cwd_fsp->fsp_name->base_name, + fsp->fsp_name->base_name, + strerror(saved_errno)); + errno = saved_errno; + return VIRUSFILTER_ACTION_DO_NOTHING; + } + + return VIRUSFILTER_ACTION_DELETE; +} + +static virusfilter_action virusfilter_do_infected_file_action( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + TALLOC_CTX *mem_ctx, + const struct files_struct *fsp, + const char **filepath_newp) +{ + virusfilter_action action; + + *filepath_newp = NULL; + + switch (config->infected_file_action) { + case VIRUSFILTER_ACTION_RENAME: + action = infected_file_action_rename(handle, config, mem_ctx, + fsp, filepath_newp); + break; + + case VIRUSFILTER_ACTION_QUARANTINE: + action = infected_file_action_quarantine(handle, config, mem_ctx, + fsp, filepath_newp); + break; + + case VIRUSFILTER_ACTION_DELETE: + action = infected_file_action_delete(handle, fsp); + break; + + case VIRUSFILTER_ACTION_DO_NOTHING: + default: + action = VIRUSFILTER_ACTION_DO_NOTHING; + break; + } + + return action; +} + +static virusfilter_action virusfilter_treat_infected_file( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + const char *report, + bool is_cache) +{ + connection_struct *conn = handle->conn; + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + char *fname = fsp->fsp_name->base_name; + TALLOC_CTX *mem_ctx = talloc_tos(); + int i; + virusfilter_action action; + const char *action_name = "UNKNOWN"; + const char *filepath_q = NULL; + char *env_list = NULL; + char *command = NULL; + int command_result; + int ret; + + action = virusfilter_do_infected_file_action(handle, config, mem_ctx, + fsp, &filepath_q); + for (i=0; virusfilter_actions[i].name; i++) { + if (virusfilter_actions[i].value == action) { + action_name = virusfilter_actions[i].name; + break; + } + } + DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname, + fname, action_name); + + if (!config->infected_file_command) { + return action; + } + + ret = virusfilter_set_module_env(mem_ctx, config, &env_list); + if (ret == -1) { + goto done; + } + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH", + fname); + if (ret == -1) { + goto done; + } + if (report != NULL) { + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_INFECTED_FILE_REPORT", + report); + if (ret == -1) { + goto done; + } + } + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_INFECTED_FILE_ACTION", + action_name); + if (ret == -1) { + goto done; + } + if (filepath_q != NULL) { + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_QUARANTINED_FILE_PATH", + filepath_q); + if (ret == -1) { + goto done; + } + } + if (is_cache) { + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_RESULT_IS_CACHE", "yes"); + if (ret == -1) { + goto done; + } + } + + command = virusfilter_string_sub(mem_ctx, conn, + config->infected_file_command); + if (command == NULL) { + DBG_ERR("virusfilter_string_sub failed\n"); + goto done; + } + + DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname, + fname, command); + + command_result = virusfilter_shell_run(mem_ctx, command, &env_list, + conn, true); + if (command_result != 0) { + DBG_ERR("Infected file command failed: %d\n", command_result); + } + + DBG_DEBUG("Infected file command finished: %d\n", command_result); + +done: + TALLOC_FREE(env_list); + TALLOC_FREE(command); + + return action; +} + +static void virusfilter_treat_scan_error( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + const char *report, + bool is_cache) +{ + connection_struct *conn = handle->conn; + const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + const char *fname = fsp->fsp_name->base_name; + TALLOC_CTX *mem_ctx = talloc_tos(); + char *env_list = NULL; + char *command = NULL; + int command_result; + int ret; + + if (!config->scan_error_command) { + return; + } + ret = virusfilter_set_module_env(mem_ctx, config, &env_list); + if (ret == -1) { + goto done; + } + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH", + fname); + if (ret == -1) { + goto done; + } + if (report != NULL) { + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_SCAN_ERROR_REPORT", + report); + if (ret == -1) { + goto done; + } + } + if (is_cache) { + ret = virusfilter_env_set(mem_ctx, &env_list, + "VIRUSFILTER_RESULT_IS_CACHE", "1"); + if (ret == -1) { + goto done; + } + } + + command = virusfilter_string_sub(mem_ctx, conn, + config->scan_error_command); + if (command == NULL) { + DBG_ERR("virusfilter_string_sub failed\n"); + goto done; + } + + DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname, + fname, command); + + command_result = virusfilter_shell_run(mem_ctx, command, &env_list, + conn, true); + if (command_result != 0) { + DBG_ERR("Scan error command failed: %d\n", command_result); + } + +done: + TALLOC_FREE(env_list); + TALLOC_FREE(command); +} + +static virusfilter_result virusfilter_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp) +{ + virusfilter_result scan_result; + char *scan_report = NULL; + const char *fname = fsp->fsp_name->base_name; + const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + struct virusfilter_cache_entry *scan_cache_e = NULL; + bool is_cache = false; + virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING; + bool add_scan_cache = true; + bool ok = false; + + if (config->cache) { + DBG_DEBUG("Searching cache entry: fname: %s\n", fname); + scan_cache_e = virusfilter_cache_get(config->cache, + cwd_fname, fname); + if (scan_cache_e != NULL) { + DBG_DEBUG("Cache entry found: cached result: %d\n", + scan_cache_e->result); + is_cache = true; + scan_result = scan_cache_e->result; + scan_report = scan_cache_e->report; + goto virusfilter_scan_result_eval; + } + DBG_DEBUG("Cache entry not found\n"); + } + + if (config->backend->fns->scan_init != NULL) { + scan_result = config->backend->fns->scan_init(config); + if (scan_result != VIRUSFILTER_RESULT_OK) { + scan_result = VIRUSFILTER_RESULT_ERROR; + scan_report = talloc_asprintf( + talloc_tos(), + "Initializing scanner failed"); + goto virusfilter_scan_result_eval; + } + } + + scan_result = config->backend->fns->scan(handle, config, fsp, + &scan_report); + + if (config->backend->fns->scan_end != NULL) { + bool scan_end = true; + + if (config->scan_request_limit > 0) { + scan_end = false; + config->scan_request_count++; + if (config->scan_request_count >= + config->scan_request_limit) + { + scan_end = true; + config->scan_request_count = 0; + } + } + if (scan_end) { + config->backend->fns->scan_end(config); + } + } + +virusfilter_scan_result_eval: + + switch (scan_result) { + case VIRUSFILTER_RESULT_CLEAN: + DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname); + break; + + case VIRUSFILTER_RESULT_INFECTED: + DBG_ERR("Scan result: Infected: %s/%s: %s\n", + cwd_fname, fname, scan_report ? scan_report : + "infected (memory error on report)"); + file_action = virusfilter_treat_infected_file(handle, + config, fsp, scan_report, is_cache); + if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) { + add_scan_cache = false; + } + break; + + case VIRUSFILTER_RESULT_SUSPECTED: + if (!config->block_suspected_file) { + break; + } + DBG_ERR("Scan result: Suspected: %s/%s: %s\n", + cwd_fname, fname, scan_report ? scan_report : + "suspected infection (memory error on report)"); + file_action = virusfilter_treat_infected_file(handle, + config, fsp, scan_report, is_cache); + if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) { + add_scan_cache = false; + } + break; + + case VIRUSFILTER_RESULT_ERROR: + DBG_ERR("Scan result: Error: %s/%s: %s\n", + cwd_fname, fname, scan_report ? scan_report : + "error (memory error on report)"); + virusfilter_treat_scan_error(handle, config, fsp, + scan_report, is_cache); + add_scan_cache = false; + break; + + default: + DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n", + scan_result, cwd_fname, fname, scan_report ? + scan_report : "Unknown (memory error on report)"); + virusfilter_treat_scan_error(handle, config, fsp, + scan_report, is_cache); + add_scan_cache = false; + break; + } + + if (config->cache) { + if (!is_cache && add_scan_cache) { + DBG_DEBUG("Adding new cache entry: %s, %d\n", fname, + scan_result); + ok = virusfilter_cache_entry_add( + config->cache, cwd_fname, fname, + scan_result, scan_report); + if (!ok) { + DBG_ERR("Cannot create cache entry: " + "virusfilter_cache_entry_new failed\n"); + goto virusfilter_scan_return; + } + } else if (is_cache) { + virusfilter_cache_entry_free(scan_cache_e); + } + } + +virusfilter_scan_return: + return scan_result; +} + +static int virusfilter_vfs_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + TALLOC_CTX *mem_ctx = talloc_tos(); + struct virusfilter_config *config = NULL; + const char *cwd_fname = dirfsp->fsp_name->base_name; + virusfilter_result scan_result; + const char *fname = fsp->fsp_name->base_name; + char *dir_name = NULL; + const char *base_name = NULL; + int scan_errno = 0; + size_t test_prefix; + size_t test_suffix; + int rename_trap_count = 0; + int ret; + bool ok1; + char *sret = NULL; + struct smb_filename *smb_fname = NULL; + SMB_STRUCT_STAT sbuf = smb_fname_in->st; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + if (fsp->fsp_flags.is_directory) { + DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname); + goto virusfilter_vfs_open_next; + } + + test_prefix = strlen(config->rename_prefix); + test_suffix = strlen(config->rename_suffix); + if (test_prefix > 0) { + rename_trap_count++; + } + if (test_suffix > 0) { + rename_trap_count++; + } + + smb_fname = cp_smb_filename(mem_ctx, smb_fname_in); + if (smb_fname == NULL) { + goto virusfilter_vfs_open_fail; + } + + if (is_named_stream(smb_fname)) { + DBG_INFO("Not scanned: only file backed streams can be scanned:" + " %s/%s\n", cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + + if (!config->scan_on_open) { + DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + + if (how->flags & O_TRUNC) { + DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf); + if (ret != 0) { + + /* + * Do not return immediately if !(flags & O_CREAT) && + * errno != ENOENT. + * Do not do this here or anywhere else. The module is + * stackable and there may be modules below, such as audit + * modules, which should be handled. + */ + goto virusfilter_vfs_open_next; + } + ret = S_ISREG(sbuf.st_ex_mode); + if (ret == 0) { + DBG_INFO("Not scanned: Directory or special file: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + if (config->max_file_size > 0 && + sbuf.st_ex_size > config->max_file_size) + { + DBG_INFO("Not scanned: file size > max file size: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + if (config->min_file_size > 0 && + sbuf.st_ex_size < config->min_file_size) + { + DBG_INFO("Not scanned: file size < min file size: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + + ok1 = is_in_path(fname, config->exclude_files, false); + if (config->exclude_files && ok1) + { + DBG_INFO("Not scanned: exclude files: %s/%s\n", + cwd_fname, fname); + goto virusfilter_vfs_open_next; + } + + if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) { + sret = strstr_m(fname, config->quarantine_dir); + if (sret != NULL) { + scan_errno = config->infected_open_errno; + goto virusfilter_vfs_open_fail; + } + } + + if (test_prefix > 0 || test_suffix > 0) { + ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name); + if (ok1) + { + if (test_prefix > 0) { + ret = strncmp(base_name, + config->rename_prefix, test_prefix); + if (ret != 0) { + test_prefix = 0; + } + } + if (test_suffix > 0) { + ret = strcmp(base_name + (strlen(base_name) + - test_suffix), + config->rename_suffix); + if (ret != 0) { + test_suffix = 0; + } + } + + TALLOC_FREE(dir_name); + + if ((rename_trap_count == 2 && test_prefix && + test_suffix) || (rename_trap_count == 1 && + (test_prefix || test_suffix))) + { + scan_errno = + config->infected_open_errno; + goto virusfilter_vfs_open_fail; + } + } + } + + scan_result = virusfilter_scan(handle, config, fsp); + + switch (scan_result) { + case VIRUSFILTER_RESULT_CLEAN: + break; + case VIRUSFILTER_RESULT_INFECTED: + scan_errno = config->infected_open_errno; + goto virusfilter_vfs_open_fail; + case VIRUSFILTER_RESULT_ERROR: + if (config->block_access_on_error) { + DBG_INFO("Block access\n"); + scan_errno = config->scan_error_open_errno; + goto virusfilter_vfs_open_fail; + } + break; + default: + scan_errno = config->scan_error_open_errno; + goto virusfilter_vfs_open_fail; + } + + TALLOC_FREE(smb_fname); + +virusfilter_vfs_open_next: + return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname_in, fsp, how); + +virusfilter_vfs_open_fail: + TALLOC_FREE(smb_fname); + errno = (scan_errno != 0) ? scan_errno : EACCES; + return -1; +} + +static int virusfilter_vfs_close( + struct vfs_handle_struct *handle, + files_struct *fsp) +{ + /* + * The name of this variable is for consistency. If API changes to + * match _open change to cwd_fname as in virusfilter_vfs_open. + */ + const char *cwd_fname = handle->conn->connectpath; + + struct virusfilter_config *config = NULL; + char *fname = fsp->fsp_name->base_name; + int close_result = -1; + int close_errno = 0; + virusfilter_result scan_result; + int scan_errno = 0; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + /* + * Must close after scan? It appears not as the scanners are not + * internal and other modules such as greyhole seem to do + * SMB_VFS_NEXT_* functions before processing. + */ + close_result = SMB_VFS_NEXT_CLOSE(handle, fsp); + if (close_result == -1) { + close_errno = errno; + } + + /* + * Return immediately if close_result == -1, and close_errno == EBADF. + * If close failed, file likely doesn't exist, do not try to scan. + */ + if (close_result == -1 && close_errno == EBADF) { + if (fsp->fsp_flags.modified) { + DBG_DEBUG("Removing cache entry (if existent): " + "fname: %s\n", fname); + virusfilter_cache_remove(config->cache, + cwd_fname, fname); + } + goto virusfilter_vfs_close_fail; + } + + if (fsp->fsp_flags.is_directory) { + DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname); + return close_result; + } + + if (fsp_is_alternate_stream(fsp)) { + if (config->scan_on_open && fsp->fsp_flags.modified) { + if (config->cache) { + DBG_DEBUG("Removing cache entry (if existent)" + ": fname: %s\n", fname); + virusfilter_cache_remove( + config->cache, + cwd_fname, fname); + } + } + DBG_INFO("Not scanned: only file backed streams can be scanned:" + " %s/%s\n", cwd_fname, fname); + return close_result; + } + + if (!config->scan_on_close) { + if (config->scan_on_open && fsp->fsp_flags.modified) { + if (config->cache) { + DBG_DEBUG("Removing cache entry (if existent)" + ": fname: %s\n", fname); + virusfilter_cache_remove( + config->cache, + cwd_fname, fname); + } + } + DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n", + cwd_fname, fname); + return close_result; + } + + if (!fsp->fsp_flags.modified) { + DBG_NOTICE("Not scanned: File not modified: %s/%s\n", + cwd_fname, fname); + + return close_result; + } + + if (is_in_path(fname, config->exclude_files, false)) { + DBG_INFO("Not scanned: exclude files: %s/%s\n", + cwd_fname, fname); + return close_result; + } + + scan_result = virusfilter_scan(handle, config, fsp); + + switch (scan_result) { + case VIRUSFILTER_RESULT_CLEAN: + break; + case VIRUSFILTER_RESULT_INFECTED: + scan_errno = config->infected_close_errno; + goto virusfilter_vfs_close_fail; + case VIRUSFILTER_RESULT_ERROR: + if (config->block_access_on_error) { + DBG_INFO("Block access\n"); + scan_errno = config->scan_error_close_errno; + goto virusfilter_vfs_close_fail; + } + break; + default: + scan_errno = config->scan_error_close_errno; + goto virusfilter_vfs_close_fail; + } + + if (close_errno != 0) { + errno = close_errno; + } + + return close_result; + +virusfilter_vfs_close_fail: + + errno = (scan_errno != 0) ? scan_errno : close_errno; + + return close_result; +} + +static int virusfilter_vfs_unlinkat(struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + int ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + flags); + struct virusfilter_config *config = NULL; + struct smb_filename *full_fname = NULL; + char *fname = NULL; + char *cwd_fname = dirfsp->fsp_name->base_name; + + if (ret != 0 && errno != ENOENT) { + return ret; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + if (config->cache == NULL) { + return 0; + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + fname = full_fname->base_name; + + DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname); + virusfilter_cache_remove(config->cache, cwd_fname, fname); + + TALLOC_FREE(full_fname); + return 0; +} + +static int virusfilter_vfs_renameat( + struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int ret = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + struct virusfilter_config *config = NULL; + char *fname = NULL; + char *dst_fname = NULL; + char *cwd_fname = handle->conn->cwd_fsp->fsp_name->base_name; + struct smb_filename *full_src = NULL; + struct smb_filename *full_dst = NULL; + + if (ret != 0) { + return ret; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct virusfilter_config, return -1); + + if (config->cache == NULL) { + return 0; + } + + full_src = full_path_from_dirfsp_atname(talloc_tos(), + srcfsp, + smb_fname_src); + if (full_src == NULL) { + errno = ENOMEM; + ret = -1; + goto out; + } + + full_dst = full_path_from_dirfsp_atname(talloc_tos(), + dstfsp, + smb_fname_dst); + if (full_dst == NULL) { + errno = ENOMEM; + ret = -1; + goto out; + } + + fname = full_src->base_name; + dst_fname = full_dst->base_name; + + DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n", + fname, dst_fname); + virusfilter_cache_entry_rename(config->cache, + cwd_fname, + fname, + dst_fname); + + ret = 0; + out: + TALLOC_FREE(full_src); + TALLOC_FREE(full_dst); + return ret; +} + + +/* VFS operations */ +static struct vfs_fn_pointers vfs_virusfilter_fns = { + .connect_fn = virusfilter_vfs_connect, + .disconnect_fn = virusfilter_vfs_disconnect, + .openat_fn = virusfilter_vfs_openat, + .close_fn = virusfilter_vfs_close, + .unlinkat_fn = virusfilter_vfs_unlinkat, + .renameat_fn = virusfilter_vfs_renameat, +}; + +NTSTATUS vfs_virusfilter_init(TALLOC_CTX *); +NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "virusfilter", + &vfs_virusfilter_fns); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + virusfilter_debug_class = debug_add_class("virusfilter"); + if (virusfilter_debug_class == -1) { + virusfilter_debug_class = DBGC_VFS; + DBG_ERR("Couldn't register custom debugging class!\n"); + } else { + DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class); + } + + DBG_INFO("registered\n"); + + return status; +} diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c new file mode 100644 index 0000000..fb93cae --- /dev/null +++ b/source3/modules/vfs_virusfilter_clamav.c @@ -0,0 +1,195 @@ +/* + Samba-VirusFilter VFS modules + ClamAV clamd support + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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 . +*/ + +/* Default values for standard "extra" configuration variables */ + +#ifdef CLAMAV_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH CLAMAV_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/clamav/clamd.ctl" +#endif + +#include "modules/vfs_virusfilter_common.h" +#include "modules/vfs_virusfilter_utils.h" + +static int virusfilter_clamav_connect(struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + + /* To use clamd "zXXXX" commands */ + virusfilter_io_set_writel_eol(config->io_h, "\0", 1); + virusfilter_io_set_readl_eol(config->io_h, "\0", 1); + + return 0; +} + +static virusfilter_result virusfilter_clamav_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + bool ok; + + DBG_INFO("clamd: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("clamd: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + return VIRUSFILTER_RESULT_ERROR; + } + + DBG_INFO("clamd: Connected\n"); + + return VIRUSFILTER_RESULT_OK; +} + +static void virusfilter_clamav_scan_end( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("clamd: Disconnecting\n"); + + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_clamav_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + const char *fname = fsp->fsp_name->base_name; + size_t filepath_len = strlen(cwd_fname) + 1 /* slash */ + strlen(fname); + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_CLEAN; + char *report = NULL; + char *reply = NULL; + char *reply_msg = NULL; + char *reply_token; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s", + cwd_fname, fname); + if (!ok) { + DBG_ERR("clamd: zSCAN: I/O error: %s\n", strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + goto virusfilter_clamav_scan_return; + } + + if (reply[filepath_len] != ':' || + reply[filepath_len+1] != ' ') + { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + goto virusfilter_clamav_scan_return; + } + reply_msg = reply + filepath_len + 2; + + reply_token = strrchr(reply, ' '); + + if (reply_token == NULL) { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + goto virusfilter_clamav_scan_return; + } + *reply_token = '\0'; + reply_token++; + + if (strcmp(reply_token, "OK") == 0) { + + /* : OK */ + result = VIRUSFILTER_RESULT_CLEAN; + report = talloc_asprintf(talloc_tos(), "Clean"); + } else if (strcmp(reply_token, "FOUND") == 0) { + + /* : FOUND */ + result = VIRUSFILTER_RESULT_INFECTED; + report = talloc_strdup(talloc_tos(), reply_msg); + } else if (strcmp(reply_token, "ERROR") == 0) { + + /* : ERROR */ + DBG_ERR("clamd: zSCAN: Error: %s\n", reply_msg); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s\t", reply_msg); + } else { + DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + } + +virusfilter_clamav_scan_return: + TALLOC_FREE(reply); + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), + "Scanner report memory error"); + } else { + *reportp = report; + } + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_clamav = { + .connect = virusfilter_clamav_connect, + .disconnect = NULL, + .scan_init = virusfilter_clamav_scan_init, + .scan = virusfilter_clamav_scan, + .scan_end = virusfilter_clamav_scan_end, +}; + +int virusfilter_clamav_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_clamav; + backend->name = "clamav"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h new file mode 100644 index 0000000..24359bf --- /dev/null +++ b/source3/modules/vfs_virusfilter_common.h @@ -0,0 +1,154 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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 . +*/ + +#ifndef _VIRUSFILTER_COMMON_H +#define _VIRUSFILTER_COMMON_H + +#include +#include + +/* Samba common include file */ +#include "includes.h" + +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "system/filesys.h" +#include "transfer_file.h" +#include "auth.h" +#include "passdb.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "../lib/tsocket/tsocket.h" + +/* Samba debug class for VIRUSFILTER */ +#undef DBGC_CLASS +#define DBGC_CLASS virusfilter_debug_class +extern int virusfilter_debug_class; + +#define VIRUSFILTER_VERSION "0.1.5" + +/* ====================================================================== */ + +typedef enum { + VIRUSFILTER_ACTION_DO_NOTHING, + VIRUSFILTER_ACTION_QUARANTINE, + VIRUSFILTER_ACTION_RENAME, + VIRUSFILTER_ACTION_DELETE, +} virusfilter_action; + +typedef enum { + VIRUSFILTER_RESULT_OK, + VIRUSFILTER_RESULT_CLEAN, + VIRUSFILTER_RESULT_ERROR, + VIRUSFILTER_RESULT_INFECTED, + VIRUSFILTER_RESULT_SUSPECTED, + /* FIXME: VIRUSFILTER_RESULT_RISKWARE, */ +} virusfilter_result; + +struct virusfilter_config { + int scan_request_count; + int scan_request_limit; + + /* Scan on file operations */ + bool scan_on_open; + bool scan_on_close; + + /* Special scan options */ + bool scan_archive; + int max_nested_scan_archive; + bool scan_mime; + bool block_suspected_file; + + /* Size limit */ + size_t max_file_size; + size_t min_file_size; + + /* Exclude files */ + name_compare_entry *exclude_files; + + /* Infected files */ + name_compare_entry *infected_files; + + /* Scan result cache */ + struct virusfilter_cache *cache; + int cache_entry_limit; + int cache_time_limit; + + /* Infected file options */ + virusfilter_action infected_file_action; + const char * infected_file_command; + int infected_open_errno; + int infected_close_errno; + + /* Scan error options */ + const char * scan_error_command; + int scan_error_open_errno; + int scan_error_close_errno; + bool block_access_on_error; + + /* Quarantine infected files */ + const char * quarantine_dir; + const char * quarantine_prefix; + const char * quarantine_suffix; + bool quarantine_keep_tree; + bool quarantine_keep_name; + mode_t quarantine_dir_mode; + + /* Rename infected files */ + const char * rename_prefix; + const char * rename_suffix; + + /* Network options */ + const char * socket_path; + struct virusfilter_io_handle *io_h; + + /* The backend AV engine */ + struct virusfilter_backend *backend; +}; + +struct virusfilter_backend_fns { + int (*connect)( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user); + void (*disconnect)( + struct vfs_handle_struct *handle); + virusfilter_result (*scan_init)( + struct virusfilter_config *config); + virusfilter_result (*scan)( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp); + void (*scan_end)( + struct virusfilter_config *config); +}; + +struct virusfilter_backend { + unsigned version; + const char *name; + const struct virusfilter_backend_fns *fns; + void *backend_private; +}; + +int virusfilter_sophos_init(struct virusfilter_config *config); +int virusfilter_fsav_init(struct virusfilter_config *config); +int virusfilter_clamav_init(struct virusfilter_config *config); +int virusfilter_dummy_init(struct virusfilter_config *config); + +#endif /* _VIRUSFILTER_COMMON_H */ diff --git a/source3/modules/vfs_virusfilter_dummy.c b/source3/modules/vfs_virusfilter_dummy.c new file mode 100644 index 0000000..03405cd --- /dev/null +++ b/source3/modules/vfs_virusfilter_dummy.c @@ -0,0 +1,58 @@ +/* + Samba-VirusFilter VFS modules + Dummy scanner with infected files support. + Copyright (C) 2022 Pavel Filipenský + + 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 "modules/vfs_virusfilter_utils.h" + +static virusfilter_result virusfilter_dummy_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + bool ok; + + DBG_INFO("Scanning file: %s\n", fsp_str_dbg(fsp)); + ok = is_in_path(fsp->fsp_name->base_name, + config->infected_files, + false); + return ok ? VIRUSFILTER_RESULT_INFECTED : VIRUSFILTER_RESULT_CLEAN; +} + +static struct virusfilter_backend_fns virusfilter_backend_dummy = { + .connect = NULL, + .disconnect = NULL, + .scan_init = NULL, + .scan = virusfilter_dummy_scan, + .scan_end = NULL, +}; + +int virusfilter_dummy_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_dummy; + backend->name = "dummy"; + config->backend = backend; + return 0; +} diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c new file mode 100644 index 0000000..25a2906 --- /dev/null +++ b/source3/modules/vfs_virusfilter_fsav.c @@ -0,0 +1,451 @@ +/* + Samba-VirusFilter VFS modules + F-Secure Anti-Virus fsavd support + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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 "vfs_virusfilter_common.h" +#include "vfs_virusfilter_utils.h" + +#ifdef FSAV_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH FSAV_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/tmp/.fsav-0" +#endif + +/* Default values for module-specific configuration variables */ +/* 5 = F-Secure Linux 7 or later? */ + +#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL 5 +#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE false +#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST true +#define VIRUSFILTER_DEFAULT_FILTER_FILENAME false + +struct virusfilter_fsav_config { + /* Backpointer */ + struct virusfilter_config *config; + + int fsav_protocol; + bool scan_riskware; + bool stop_scan_on_first; + bool filter_filename; +}; + +static void virusfilter_fsav_scan_end(struct virusfilter_config *config); + +static int virusfilter_fsav_destruct_config( + struct virusfilter_fsav_config *fsav_config) +{ + virusfilter_fsav_scan_end(fsav_config->config); + return 0; +} + +static int virusfilter_fsav_connect( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + int snum = SNUM(handle->conn); + struct virusfilter_fsav_config *fsav_config = NULL; + + fsav_config = talloc_zero(config->backend, + struct virusfilter_fsav_config); + if (fsav_config == NULL) { + return -1; + } + + fsav_config->config = config; + + fsav_config->fsav_protocol = lp_parm_int( + snum, "virusfilter", "fsav protocol", + VIRUSFILTER_DEFAULT_FSAV_PROTOCOL); + + fsav_config->scan_riskware = lp_parm_bool( + snum, "virusfilter", "scan riskware", + VIRUSFILTER_DEFAULT_SCAN_RISKWARE); + + fsav_config->stop_scan_on_first = lp_parm_bool( + snum, "virusfilter", "stop scan on first", + VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST); + + fsav_config->filter_filename = lp_parm_bool( + snum, "virusfilter", "filter filename", + VIRUSFILTER_DEFAULT_FILTER_FILENAME); + + talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config); + + config->backend->backend_private = fsav_config; + + config->block_suspected_file = lp_parm_bool( + snum, "virusfilter", "block suspected file", false); + + return 0; +} + +static virusfilter_result virusfilter_fsav_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_fsav_config *fsav_config = NULL; + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + bool ok; + int ret; + + fsav_config = talloc_get_type_abort(config->backend->backend_private, + struct virusfilter_fsav_config); + + if (io_h->stream != NULL) { + DBG_DEBUG("fsavd: Checking if connection is alive\n"); + + /* FIXME: I don't know the correct PING command format... */ + ok = virusfilter_io_writefl_readl(io_h, &reply, "PING"); + if (ok) { + ret = strncmp(reply, "ERROR\t", 6); + if (ret == 0) { + DBG_DEBUG("fsavd: Re-using existent " + "connection\n"); + goto virusfilter_fsav_init_succeed; + } + } + + DBG_DEBUG("fsavd: Closing dead connection\n"); + virusfilter_fsav_scan_end(config); + } + + DBG_INFO("fsavd: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("fsavd: Reading greeting message failed: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "DBVERSION\t", 10); + if (ret != 0) { + DBG_ERR("fsavd: Invalid greeting message: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + DBG_DEBUG("fsavd: Connected\n"); + + DBG_INFO("fsavd: Configuring\n"); + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d", + fsav_config->fsav_protocol); + if (!ok) { + DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tSTOPONFIRST\t%d", + fsav_config->stop_scan_on_first ? + 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d", + fsav_config->filter_filename ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tARCHIVE\t%d", + config->scan_archive ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tMAXARCH\t%d", + config->max_nested_scan_archive); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "CONFIGURE\tMIME\t%d", + config->scan_mime ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d", + fsav_config->scan_riskware ? 1 : 0); + if (!ok) { + DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_init_failed; + } + ret = strncmp(reply, "OK\t", 3); + if (ret != 0) { + DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n", + reply); + goto virusfilter_fsav_init_failed; + } + + DBG_DEBUG("fsavd: Configured\n"); + +virusfilter_fsav_init_succeed: + TALLOC_FREE(reply); + return VIRUSFILTER_RESULT_OK; + +virusfilter_fsav_init_failed: + TALLOC_FREE(reply); + virusfilter_fsav_scan_end(config); + + return VIRUSFILTER_RESULT_ERROR; +} + +static void virusfilter_fsav_scan_end(struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("fsavd: Disconnecting\n"); + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_fsav_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + const char *fname = fsp->fsp_name->base_name; + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_CLEAN; + char *report = NULL; + char *reply = NULL; + char *reply_token = NULL, *reply_saveptr = NULL; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname, + (int)strlen(cwd_fname), "/", 1, fname, + (int)strlen(fname), NULL); + if (!ok) { + DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + goto virusfilter_fsav_scan_return; + } + + TALLOC_FREE(reply); + + for (;;) { + if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) { + DBG_ERR("fsavd: SCANFILE: Read error: %s\n", + strerror(errno)); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", + strerror(errno)); + break; + } + + reply_token = strtok_r(reply, "\t", &reply_saveptr); + + if (strcmp(reply_token, "OK") == 0) { + break; + } else if (strcmp(reply_token, "CLEAN") == 0) { + + /* CLEAN\t */ + result = VIRUSFILTER_RESULT_CLEAN; + report = talloc_asprintf(talloc_tos(), "Clean"); + } else if (strcmp(reply_token, "INFECTED") == 0 || + strcmp(reply_token, "ARCHIVE_INFECTED") == 0 || + strcmp(reply_token, "MIME_INFECTED") == 0 || + strcmp(reply_token, "RISKWARE") == 0 || + strcmp(reply_token, "ARCHIVE_RISKWARE") == 0 || + strcmp(reply_token, "MIME_RISKWARE") == 0) + { + + /* INFECTED\t\t\t */ + result = VIRUSFILTER_RESULT_INFECTED; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN INFECTION"); + } + } else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) { + + /* Ignore */ + } else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) { + + /* Ignore */ + } else if ((strcmp(reply_token, "SUSPECTED") == 0 || + strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 || + strcmp(reply_token, "MIME_SUSPECTED") == 0) && + config->block_suspected_file) + { + result = VIRUSFILTER_RESULT_SUSPECTED; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN REASON SUSPECTED"); + } + } else if (strcmp(reply_token, "SCAN_FAILURE") == 0) { + + /* SCAN_FAILURE\t\t0x\t [] */ + result = VIRUSFILTER_RESULT_ERROR; + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + reply_token = strtok_r(NULL, "\t", &reply_saveptr); + DBG_ERR("fsavd: SCANFILE: Scaner error: %s\n", + reply_token ? reply_token : "UNKNOWN ERROR"); + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s", + reply_token ? reply_token : + "UNKNOWN ERROR"); + } else { + result = VIRUSFILTER_RESULT_ERROR; + DBG_ERR("fsavd: SCANFILE: Invalid reply: %s\n", + reply_token); + report = talloc_asprintf(talloc_tos(), + "Scanner communication error"); + } + + TALLOC_FREE(reply); + } + +virusfilter_fsav_scan_return: + TALLOC_FREE(reply); + + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), "Scanner report memory " + "error"); + } else { + *reportp = report; + } + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_fsav ={ + .connect = virusfilter_fsav_connect, + .disconnect = NULL, + .scan_init = virusfilter_fsav_scan_init, + .scan = virusfilter_fsav_scan, + .scan_end = virusfilter_fsav_scan_end, +}; + +int virusfilter_fsav_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_fsav; + backend->name = "fsav"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c new file mode 100644 index 0000000..c8cdec5 --- /dev/null +++ b/source3/modules/vfs_virusfilter_sophos.c @@ -0,0 +1,391 @@ +/* + Samba-VirusFilter VFS modules + Sophos Anti-Virus savdid (SSSP/1.0) support + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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 "vfs_virusfilter_common.h" +#include "vfs_virusfilter_utils.h" + +/* Default values for standard "extra" configuration variables */ +#ifdef SOPHOS_DEFAULT_SOCKET_PATH +# define VIRUSFILTER_DEFAULT_SOCKET_PATH SOPHOS_DEFAULT_SOCKET_PATH +#else +# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/savdi/sssp.sock" +#endif + +static void virusfilter_sophos_scan_end(struct virusfilter_config *config); + +/* Python's urllib.quote(string[, safe]) clone */ +static int virusfilter_url_quote(const char *src, char *dst, int dst_size) +{ + char *dst_c = dst; + static char hex[] = "0123456789ABCDEF"; + + for (; *src != '\0'; src++) { + if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') || + (*src > '9' && *src < 'A') || + (*src > 'Z' && *src < 'a' && *src != '_') || + (*src > 'z')) + { + if (dst_size < 4) { + return -1; + } + *dst_c++ = '%'; + *dst_c++ = hex[(*src >> 4) & 0x0F]; + *dst_c++ = hex[*src & 0x0F]; + dst_size -= 3; + } else { + if (dst_size < 2) { + return -1; + } + *dst_c++ = *src; + dst_size--; + } + } + + *dst_c = '\0'; + + return (dst_c - dst); +} + +static int virusfilter_sophos_connect( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const char *svc, + const char *user) +{ + virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2); + + return 0; +} + +static virusfilter_result virusfilter_sophos_scan_ping( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + bool ok; + int ret; + + /* SSSP/1.0 has no "PING" command */ + ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17); + if (!ok) { + return VIRUSFILTER_RESULT_ERROR; + } + + for (;;) { + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + return VIRUSFILTER_RESULT_ERROR; + } + ret = strcmp(reply, ""); + if (ret == 0) { + break; + } + TALLOC_FREE(reply); + } + + TALLOC_FREE(reply); + return VIRUSFILTER_RESULT_OK; +} + +static virusfilter_result virusfilter_sophos_scan_init( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + char *reply = NULL; + int ret; + bool ok; + + if (io_h->stream != NULL) { + DBG_DEBUG("SSSP: Checking if connection is alive\n"); + + ret = virusfilter_sophos_scan_ping(config); + if (ret == VIRUSFILTER_RESULT_OK) + { + DBG_DEBUG("SSSP: Re-using existent connection\n"); + return VIRUSFILTER_RESULT_OK; + } + + DBG_INFO("SSSP: Closing dead connection\n"); + virusfilter_sophos_scan_end(config); + } + + + DBG_INFO("SSSP: Connecting to socket: %s\n", + config->socket_path); + + become_root(); + ok = virusfilter_io_connect_path(io_h, config->socket_path); + unbecome_root(); + + if (!ok) { + DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n", + config->socket_path, strerror(errno)); + return VIRUSFILTER_RESULT_ERROR; + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: Reading greeting message failed: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "OK SSSP/1.0", 11); + if (ret != 0) { + DBG_ERR("SSSP: Invalid greeting message: %s\n", + reply); + goto virusfilter_sophos_scan_init_failed; + } + + DBG_DEBUG("SSSP: Connected\n"); + + DBG_INFO("SSSP: Configuring\n"); + + TALLOC_FREE(reply); + + ok = virusfilter_io_writefl_readl(io_h, &reply, + "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n", + config->scan_archive ? 1 : 0); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "ACC ", 4); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strncmp(reply, "DONE OK ", 8); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS failed: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + TALLOC_FREE(reply); + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_init_failed; + } + ret = strcmp(reply, ""); + if (ret != 0) { + DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply); + goto virusfilter_sophos_scan_init_failed; + } + + DBG_DEBUG("SSSP: Configured\n"); + + return VIRUSFILTER_RESULT_OK; + +virusfilter_sophos_scan_init_failed: + + TALLOC_FREE(reply); + + virusfilter_sophos_scan_end(config); + + return VIRUSFILTER_RESULT_ERROR; +} + +static void virusfilter_sophos_scan_end( + struct virusfilter_config *config) +{ + struct virusfilter_io_handle *io_h = config->io_h; + + DBG_INFO("SSSP: Disconnecting\n"); + + virusfilter_io_disconnect(io_h); +} + +static virusfilter_result virusfilter_sophos_scan( + struct vfs_handle_struct *handle, + struct virusfilter_config *config, + const struct files_struct *fsp, + char **reportp) +{ + char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name; + const char *fname = fsp->fsp_name->base_name; + char fileurl[VIRUSFILTER_IO_URL_MAX+1]; + int fileurl_len, fileurl_len2; + struct virusfilter_io_handle *io_h = config->io_h; + virusfilter_result result = VIRUSFILTER_RESULT_ERROR; + char *report = NULL; + char *reply = NULL; + char *reply_token = NULL, *reply_saveptr = NULL; + int ret; + bool ok; + + DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname); + + fileurl_len = virusfilter_url_quote(cwd_fname, fileurl, + VIRUSFILTER_IO_URL_MAX); + if (fileurl_len < 0) { + DBG_ERR("virusfilter_url_quote failed: File path too long: " + "%s/%s\n", cwd_fname, fname); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "File path too long"); + goto virusfilter_sophos_scan_return; + } + fileurl[fileurl_len] = '/'; + fileurl_len++; + + fileurl_len += fileurl_len2 = virusfilter_url_quote(fname, + fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len); + if (fileurl_len2 < 0) { + DBG_ERR("virusfilter_url_quote failed: File path too long: " + "%s/%s\n", cwd_fname, fname); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "File path too long"); + goto virusfilter_sophos_scan_return; + } + fileurl_len += fileurl_len2; + + ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl, + fileurl_len, NULL); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Write error: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + ret = strncmp(reply, "ACC ", 4); + if (ret != 0) { + DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n", + reply); + result = VIRUSFILTER_RESULT_ERROR; + goto virusfilter_sophos_scan_return; + } + + TALLOC_FREE(reply); + + result = VIRUSFILTER_RESULT_CLEAN; + for (;;) { + ok = virusfilter_io_readl(talloc_tos(), io_h, &reply); + if (!ok) { + DBG_ERR("SSSP: SCANFILE: Read error: %s\n", + strerror(errno)); + goto virusfilter_sophos_scan_io_error; + } + + ret = strcmp(reply, ""); + if (ret == 0) { + break; + } + + reply_token = strtok_r(reply, " ", &reply_saveptr); + + if (strcmp(reply_token, "VIRUS") == 0) { + result = VIRUSFILTER_RESULT_INFECTED; + reply_token = strtok_r(NULL, " ", &reply_saveptr); + if (reply_token != NULL) { + report = talloc_strdup(talloc_tos(), + reply_token); + } else { + report = talloc_asprintf(talloc_tos(), + "UNKNOWN INFECTION"); + } + } else if (strcmp(reply_token, "OK") == 0) { + + /* Ignore */ + } else if (strcmp(reply_token, "DONE") == 0) { + reply_token = strtok_r(NULL, "", &reply_saveptr); + if (reply_token != NULL && + + /* Succeed */ + strncmp(reply_token, "OK 0000 ", 8) != 0 && + + /* Infected */ + strncmp(reply_token, "OK 0203 ", 8) != 0) + { + DBG_ERR("SSSP: SCANFILE: Error: %s\n", + reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), + "Scanner error: %s\n", + reply_token); + } + } else { + DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n", + reply_token); + result = VIRUSFILTER_RESULT_ERROR; + report = talloc_asprintf(talloc_tos(), "Scanner " + "communication error"); + } + + TALLOC_FREE(reply); + } + +virusfilter_sophos_scan_return: + TALLOC_FREE(reply); + + if (report == NULL) { + *reportp = talloc_asprintf(talloc_tos(), + "Scanner report memory error"); + } else { + *reportp = report; + } + + return result; + +virusfilter_sophos_scan_io_error: + *reportp = talloc_asprintf(talloc_tos(), + "Scanner I/O error: %s\n", strerror(errno)); + + return result; +} + +static struct virusfilter_backend_fns virusfilter_backend_sophos ={ + .connect = virusfilter_sophos_connect, + .disconnect = NULL, + .scan_init = virusfilter_sophos_scan_init, + .scan = virusfilter_sophos_scan, + .scan_end = virusfilter_sophos_scan_end, +}; + +int virusfilter_sophos_init(struct virusfilter_config *config) +{ + struct virusfilter_backend *backend = NULL; + + if (config->socket_path == NULL) { + config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH; + } + + backend = talloc_zero(config, struct virusfilter_backend); + if (backend == NULL) { + return -1; + } + + backend->fns = &virusfilter_backend_sophos; + backend->name = "sophos"; + + config->backend = backend; + return 0; +} diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c new file mode 100644 index 0000000..b467779 --- /dev/null +++ b/source3/modules/vfs_virusfilter_utils.c @@ -0,0 +1,1036 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + Copyright (C) 2016-2017 Trever L. Adams + + 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 "modules/vfs_virusfilter_common.h" +#include "modules/vfs_virusfilter_utils.h" + +struct iovec; + +#include "lib/util/iov_buf.h" +#include +#include "lib/tsocket/tsocket.h" +#include "source3/lib/substitute.h" + +int virusfilter_debug_class = DBGC_VFS; + +/* ====================================================================== */ + +char *virusfilter_string_sub( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *str) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + return talloc_sub_full(mem_ctx, + lp_servicename(mem_ctx, lp_sub, SNUM(conn)), + conn->session_info->unix_info->unix_name, + conn->connectpath, + conn->session_info->unix_token->gid, + conn->session_info->unix_info->sanitized_username, + conn->session_info->info->domain_name, + str); +} + +int virusfilter_vfs_next_move( + struct vfs_handle_struct *vfs_h, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + int result; + + result = SMB_VFS_NEXT_RENAMEAT(vfs_h, + vfs_h->conn->cwd_fsp, + smb_fname_src, + vfs_h->conn->cwd_fsp, + smb_fname_dst); + if (result == 0 || errno != EXDEV) { + return result; + } + + /* + * For now, do not handle EXDEV as poking around violates + * stackability. Return -1, simply refuse access. + */ + return -1; +} + +/* Line-based socket I/O + * ====================================================================== + */ + +struct virusfilter_io_handle *virusfilter_io_new( + TALLOC_CTX *mem_ctx, + int connect_timeout, + int io_timeout) +{ + struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx, + struct virusfilter_io_handle); + + if (io_h == NULL) { + return NULL; + } + + io_h->stream = NULL; + io_h->r_len = 0; + + virusfilter_io_set_connect_timeout(io_h, connect_timeout); + virusfilter_io_set_io_timeout(io_h, io_timeout); + virusfilter_io_set_writel_eol(io_h, "\x0A", 1); + virusfilter_io_set_readl_eol(io_h, "\x0A", 1); + + return io_h; +} + +int virusfilter_io_set_connect_timeout( + struct virusfilter_io_handle *io_h, + int timeout) +{ + int timeout_old = io_h->connect_timeout; + + /* timeout <= 0 means infinite */ + io_h->connect_timeout = (timeout > 0) ? timeout : -1; + + return timeout_old; +} + +int virusfilter_io_set_io_timeout( + struct virusfilter_io_handle *io_h, + int timeout) +{ + int timeout_old = io_h->io_timeout; + + /* timeout <= 0 means infinite */ + io_h->io_timeout = (timeout > 0) ? timeout : -1; + + return timeout_old; +} + +void virusfilter_io_set_writel_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size) +{ + if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) { + return; + } + + memcpy(io_h->w_eol, eol, eol_size); + io_h->w_eol_size = eol_size; +} + +void virusfilter_io_set_readl_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size) +{ + if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) { + return; + } + + memcpy(io_h->r_eol, eol, eol_size); + io_h->r_eol_size = eol_size; +} + +bool virusfilter_io_connect_path( + struct virusfilter_io_handle *io_h, + const char *path) +{ + struct sockaddr_un addr; + NTSTATUS status; + int socket, ret; + size_t len; + bool ok; + + ZERO_STRUCT(addr); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + io_h->stream = NULL; + return false; + } + + status = open_socket_out((struct sockaddr_storage *)&addr, 0, + io_h->connect_timeout, + &socket); + if (!NT_STATUS_IS_OK(status)) { + io_h->stream = NULL; + return false; + } + + /* We must not block */ + ret = set_blocking(socket, false); + if (ret == -1) { + close(socket); + io_h->stream = NULL; + return false; + } + + ok = smb_set_close_on_exec(socket); + if (!ok) { + close(socket); + io_h->stream = NULL; + return false; + } + + ret = tstream_bsd_existing_socket(io_h, socket, &io_h->stream); + if (ret == -1) { + close(socket); + DBG_ERR("Could not convert socket to tstream: %s.\n", + strerror(errno)); + io_h->stream = NULL; + return false; + } + + return true; +} + +static void disconnect_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_disconnect_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +bool virusfilter_io_disconnect( + struct virusfilter_io_handle *io_h) +{ + struct tevent_req *req; + struct tevent_context *ev; + uint64_t *perror = NULL; + bool ok = true; + TALLOC_CTX *frame = talloc_stackframe(); + + if (io_h->stream == NULL) { + io_h->r_len = 0; + TALLOC_FREE(frame); + return VIRUSFILTER_RESULT_OK; + } + + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + ok = false; + goto fail; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto fail; + } + + req = tstream_disconnect_send(io_h, ev, io_h->stream); + + /* Callback when disconnect is done. */ + tevent_req_set_callback(req, disconnect_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec( + io_h->connect_timeout)); + if (!ok) { + DBG_ERR("Can't set endtime\n"); + goto fail; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto fail; + } + + /* Emit debug error if failed. */ + if (*perror != 0) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + goto fail; + } + + /* Here we know we disconnected. */ + + io_h->stream = NULL; + io_h->r_len = 0; + + fail: + TALLOC_FREE(frame); + return ok; +} + +static void writev_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_writev_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +/**************************************************************************** + Write all data from an iov array, with msec timeout (per write) + NB. This can be called with a non-socket fd, don't add dependencies + on socket calls. +****************************************************************************/ + +bool write_data_iov_timeout( + struct tstream_context *stream, + const struct iovec *iov, + size_t iovcnt, + int ms_timeout) +{ + struct tevent_context *ev = NULL; + struct tevent_req *req = NULL; + uint64_t *perror = NULL; + bool ok = false; + TALLOC_CTX *frame = talloc_stackframe(); + + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + goto fail; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto fail; + } + + /* Send the data. */ + req = tstream_writev_send(frame, ev, stream, iov, iovcnt); + if (req == NULL) { + DBG_ERR("Out of memory.\n"); + goto fail; + } + + /* Callback when *all* data sent. */ + tevent_req_set_callback(req, writev_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, + timeval_current_ofs_msec(ms_timeout)); + if (!ok) { + DBG_ERR("Can't set endtime\n"); + goto fail; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto fail; + } + + /* Done with req - freed by the callback. */ + req = NULL; + + /* Emit debug error if failed. */ + if (*perror != 0) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + goto fail; + } + + /* Here we know we correctly wrote all data. */ + TALLOC_FREE(frame); + return true; + + fail: + TALLOC_FREE(frame); + return false; +} + +bool virusfilter_io_write( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size) +{ + struct iovec iov; + + if (data_size == 0) { + return VIRUSFILTER_RESULT_OK; + } + + iov.iov_base = discard_const_p(void, data); + iov.iov_len = data_size; + + return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout); +} + +bool virusfilter_io_writel( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size) +{ + bool ok; + + ok = virusfilter_io_write(io_h, data, data_size); + if (!ok) { + return ok; + } + + return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size); +} + +bool PRINTF_ATTRIBUTE(2, 3) virusfilter_io_writefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, ...) +{ + va_list ap; + char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE]; + int data_size; + + va_start(ap, data_fmt); + data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap); + va_end(ap); + + if (unlikely (data_size < 0)) { + DBG_ERR("vsnprintf failed: %s\n", strerror(errno)); + return false; + } + + memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size); + data_size += io_h->w_eol_size; + + return virusfilter_io_write(io_h, data, data_size); +} + +bool PRINTF_ATTRIBUTE(2, 0) virusfilter_io_vwritefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, va_list ap) +{ + char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE]; + int data_size; + + data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap); + + if (unlikely (data_size < 0)) { + DBG_ERR("vsnprintf failed: %s\n", strerror(errno)); + return false; + } + + memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size); + data_size += io_h->w_eol_size; + + return virusfilter_io_write(io_h, data, data_size); +} + +bool virusfilter_io_writev( + struct virusfilter_io_handle *io_h, ...) +{ + va_list ap; + struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p; + int iov_n; + + va_start(ap, io_h); + for (iov_p = iov, iov_n = 0; + iov_n < VIRUSFILTER_IO_IOV_MAX; + iov_p++, iov_n++) + { + iov_p->iov_base = va_arg(ap, void *); + if (iov_p->iov_base == NULL) { + break; + } + iov_p->iov_len = va_arg(ap, int); + } + va_end(ap); + + return write_data_iov_timeout(io_h->stream, iov, iov_n, + io_h->io_timeout); +} + +bool virusfilter_io_writevl( + struct virusfilter_io_handle *io_h, ...) +{ + va_list ap; + struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p; + int iov_n; + + va_start(ap, io_h); + for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX; + iov_p++, iov_n++) + { + iov_p->iov_base = va_arg(ap, void *); + if (iov_p->iov_base == NULL) { + break; + } + iov_p->iov_len = va_arg(ap, int); + } + va_end(ap); + + iov_p->iov_base = io_h->r_eol; + iov_p->iov_len = io_h->r_eol_size; + iov_n++; + + return write_data_iov_timeout(io_h->stream, iov, iov_n, + io_h->io_timeout); +} + +static bool return_existing_line(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line) +{ + size_t read_line_len = 0; + char *end_p = NULL; + char *eol = NULL; + + eol = memmem(io_h->r_buffer, io_h->r_len, + io_h->r_eol, io_h->r_eol_size); + if (eol == NULL) { + return false; + } + end_p = eol + io_h->r_eol_size; + + *eol = '\0'; + read_line_len = strlen(io_h->r_buffer) + 1; + *read_line = talloc_memdup(ctx, + io_h->r_buffer, + read_line_len); + if (*read_line == NULL) { + return false; + } + + /* + * Copy the remaining buffer over the line + * we returned. + */ + memmove(io_h->r_buffer, + end_p, + io_h->r_len - (end_p - io_h->r_buffer)); + + /* And reduce the size left in the buffer. */ + io_h->r_len -= (end_p - io_h->r_buffer); + return true; +} + +static void readv_done(struct tevent_req *req) +{ + uint64_t *perr = tevent_req_callback_data(req, uint64_t); + int ret; + int err_ret; + + ret = tstream_readv_recv(req, &err_ret); + TALLOC_FREE(req); + if (ret == -1) { + *perr = err_ret; + } +} + +bool virusfilter_io_readl(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line) +{ + struct tevent_context *ev = NULL; + bool ok = false; + uint64_t *perror = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + /* Search for an existing complete line. */ + ok = return_existing_line(ctx, io_h, read_line); + if (ok) { + goto finish; + } + + /* + * No complete line in the buffer. We must read more + * from the server. + */ + ev = tevent_context_init(frame); + if (ev == NULL) { + DBG_ERR("Failed to setup event context.\n"); + goto finish; + } + + /* Error return - must be talloc'ed. */ + perror = talloc_zero(frame, uint64_t); + if (perror == NULL) { + goto finish; + } + + for (;;) { + ssize_t pending = 0; + size_t read_size = 0; + struct iovec iov; + struct tevent_req *req = NULL; + + /* + * How much can we read ? + */ + pending = tstream_pending_bytes(io_h->stream); + if (pending < 0) { + DBG_ERR("tstream_pending_bytes failed (%s).\n", + strerror(errno)); + goto finish; + } + + read_size = pending; + /* Must read at least one byte. */ + read_size = MIN(read_size, 1); + + /* And max remaining buffer space. */ + read_size = MAX(read_size, + (sizeof(io_h->r_buffer) - io_h->r_len)); + + if (read_size == 0) { + /* Buffer is full with no EOL. Error out. */ + DBG_ERR("Line buffer full.\n"); + goto finish; + } + + iov.iov_base = io_h->r_buffer + io_h->r_len; + iov.iov_len = read_size; + + /* Read the data. */ + req = tstream_readv_send(frame, + ev, + io_h->stream, + &iov, + 1); + if (req == NULL) { + DBG_ERR("out of memory.\n"); + goto finish; + } + + /* Callback when *all* data read. */ + tevent_req_set_callback(req, readv_done, perror); + + /* Set timeout. */ + ok = tevent_req_set_endtime(req, ev, + timeval_current_ofs_msec(io_h->io_timeout)); + if (!ok) { + DBG_ERR("can't set endtime\n"); + goto finish; + } + + /* Loop waiting for req to finish. */ + ok = tevent_req_poll(req, ev); + if (!ok) { + DBG_ERR("tevent_req_poll failed\n"); + goto finish; + } + + /* Done with req - freed by the callback. */ + req = NULL; + + /* + * Emit debug error if failed. + * EPIPE may be success so, don't exit. + */ + if (*perror != 0 && *perror != EPIPE) { + DBG_DEBUG("Error %s\n", strerror((int)*perror)); + errno = (int)*perror; + goto finish; + } + + /* + * We read read_size bytes. Extend the usable + * buffer length. + */ + io_h->r_len += read_size; + + /* Paranoia... */ + SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer)); + + /* Exit if we have a line to return. */ + ok = return_existing_line(ctx, io_h, read_line); + if (ok) { + goto finish; + } + /* No eol - keep reading. */ + } + + finish: + + TALLOC_FREE(frame); + return ok; +} + +bool PRINTF_ATTRIBUTE(3, 4) virusfilter_io_writefl_readl( + struct virusfilter_io_handle *io_h, + char **read_line, + const char *fmt, ...) +{ + bool ok; + + if (fmt) { + va_list ap; + + va_start(ap, fmt); + ok = virusfilter_io_vwritefl(io_h, fmt, ap); + va_end(ap); + + if (!ok) { + return ok; + } + } + + ok = virusfilter_io_readl(talloc_tos(), io_h, read_line); + if (!ok) { + DBG_ERR("virusfilter_io_readl not OK: %d\n", ok); + return false; + } + if (io_h->r_len == 0) { /* EOF */ + DBG_ERR("virusfilter_io_readl EOF\n"); + return false; + } + + return true; +} + +struct virusfilter_cache *virusfilter_cache_new( + TALLOC_CTX *ctx, + int entry_limit, + time_t time_limit) +{ + struct virusfilter_cache *cache; + + if (time_limit == 0) { + return NULL; + } + + cache = talloc_zero(ctx, struct virusfilter_cache); + if (cache == NULL) { + DBG_ERR("talloc_zero failed.\n"); + return NULL; + } + + cache->cache = memcache_init(cache->ctx, entry_limit * + (sizeof(struct virusfilter_cache_entry) + + VIRUSFILTER_CACHE_BUFFER_SIZE)); + if (cache->cache == NULL) { + DBG_ERR("memcache_init failed.\n"); + return NULL; + } + cache->ctx = ctx; + cache->time_limit = time_limit; + + return cache; +} + +bool virusfilter_cache_entry_add( + struct virusfilter_cache *cache, + const char *directory, + const char *fname, + virusfilter_result result, + char *report) +{ + int blob_size = sizeof(struct virusfilter_cache_entry); + struct virusfilter_cache_entry *cache_e = + talloc_zero_size(NULL, blob_size); + int fname_len = 0; + + if (fname == NULL || directory == NULL) { + TALLOC_FREE(report); + return false; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + TALLOC_FREE(report); + return false; + } + + fname_len = strlen(fname); + + if (cache_e == NULL|| cache->time_limit == 0) { + TALLOC_FREE(report); + return false; + } + + cache_e->result = result; + if (report != NULL) { + cache_e->report = talloc_steal(cache_e, report); + } + if (cache->time_limit > 0) { + cache_e->time = time(NULL); + } + + memcache_add_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, fname_len), &cache_e); + + return true; +} + +bool virusfilter_cache_entry_rename( + struct virusfilter_cache *cache, + const char *directory, + char *old_fname, + char *new_fname) +{ + int old_fname_len = 0; + int new_fname_len = 0; + struct virusfilter_cache_entry *new_data = NULL; + struct virusfilter_cache_entry *old_data = NULL; + + if (old_fname == NULL || new_fname == NULL || directory == NULL) { + return false; + } + + old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname); + new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname); + + if (old_fname == NULL || new_fname == NULL) { + TALLOC_FREE(old_fname); + TALLOC_FREE(new_fname); + return false; + } + + old_fname_len = strlen(old_fname); + new_fname_len = strlen(new_fname); + + old_data = memcache_lookup_talloc( + cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(old_fname, old_fname_len)); + + if (old_data == NULL) { + return false; + } + + new_data = talloc_memdup(cache->ctx, old_data, + sizeof(struct virusfilter_cache_entry)); + if (new_data == NULL) { + return false; + } + new_data->report = talloc_strdup(new_data, old_data->report); + + memcache_add_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(new_fname, new_fname_len), &new_data); + + memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(old_fname, old_fname_len)); + + return true; +} + +void virusfilter_cache_purge(struct virusfilter_cache *cache) +{ + memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC); +} + +struct virusfilter_cache_entry *virusfilter_cache_get( + struct virusfilter_cache *cache, + const char *directory, + const char *fname) +{ + int fname_len = 0; + struct virusfilter_cache_entry *cache_e = NULL; + struct virusfilter_cache_entry *data = NULL; + + if (fname == NULL || directory == NULL) { + return 0; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + return 0; + } + + fname_len = strlen(fname); + + data = memcache_lookup_talloc(cache->cache, + VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, fname_len)); + + if (data == NULL) { + return cache_e; + } + + if (cache->time_limit > 0) { + if (time(NULL) - data->time > cache->time_limit) { + DBG_DEBUG("Cache entry is too old: %s\n", + fname); + virusfilter_cache_remove(cache, directory, fname); + return cache_e; + } + } + cache_e = talloc_memdup(cache->ctx, data, + sizeof(struct virusfilter_cache_entry)); + if (cache_e == NULL) { + return NULL; + } + if (data->report != NULL) { + cache_e->report = talloc_strdup(cache_e, data->report); + } else { + cache_e->report = NULL; + } + + return cache_e; +} + +void virusfilter_cache_remove(struct virusfilter_cache *cache, + const char *directory, + const char *fname) +{ + DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname); + + if (fname == NULL || directory == NULL) { + return; + } + + fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname); + + if (fname == NULL) { + return; + } + + memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC, + data_blob_const(fname, strlen(fname))); +} + +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e) +{ + if (cache_e != NULL) { + TALLOC_FREE(cache_e->report); + cache_e->report = NULL; + } + TALLOC_FREE(cache_e); +} + +/* Shell scripting + * ====================================================================== + */ + +int virusfilter_env_set( + TALLOC_CTX *mem_ctx, + char **env_list, + const char *name, + const char *value) +{ + char *env_new; + int ret; + + env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value); + if (env_new == NULL) { + DBG_ERR("talloc_asprintf failed\n"); + return -1; + } + + ret = strv_add(mem_ctx, env_list, env_new); + + TALLOC_FREE(env_new); + + return ret; +} + +/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */ +int virusfilter_shell_set_conn_env( + TALLOC_CTX *mem_ctx, + char **env_list, + connection_struct *conn) +{ + int snum = SNUM(conn); + char *server_addr_p; + char *client_addr_p; + const char *local_machine_name = get_local_machine_name(); + fstring pidstr; + int ret; + + server_addr_p = tsocket_address_inet_addr_string( + conn->sconn->local_address, talloc_tos()); + + if (server_addr_p != NULL) { + ret = strncmp("::ffff:", server_addr_p, 7); + if (ret == 0) { + server_addr_p += 7; + } + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP", + server_addr_p); + } + TALLOC_FREE(server_addr_p); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME", + myhostname()); + virusfilter_env_set(mem_ctx, env_list, + "VIRUSFILTER_SERVER_NETBIOS_NAME", + local_machine_name); + slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid()); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID", + pidstr); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME", + lp_const_servicename(snum)); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH", + conn->cwd_fsp->fsp_name->base_name); + + client_addr_p = tsocket_address_inet_addr_string( + conn->sconn->remote_address, talloc_tos()); + + if (client_addr_p != NULL) { + ret = strncmp("::ffff:", client_addr_p, 7); + if (ret == 0) { + client_addr_p += 7; + } + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP", + client_addr_p); + } + TALLOC_FREE(client_addr_p); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME", + conn->sconn->remote_hostname); + virusfilter_env_set(mem_ctx, env_list, + "VIRUSFILTER_CLIENT_NETBIOS_NAME", + get_remote_machine_name()); + + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME", + get_current_username()); + virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN", + get_current_user_info_domain()); + + return 0; +} + +/* Wrapper to Samba's smbrun() in smbrun.c */ +int virusfilter_shell_run( + TALLOC_CTX *mem_ctx, + const char *cmd, + char **env_list, + connection_struct *conn, + bool sanitize) +{ + int ret; + + if (conn != NULL) { + ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn); + if (ret == -1) { + return -1; + } + } + + if (sanitize) { + return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list)); + } else { + return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(), + *env_list)); + } +} diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h new file mode 100644 index 0000000..69754aa --- /dev/null +++ b/source3/modules/vfs_virusfilter_utils.h @@ -0,0 +1,177 @@ +/* + Samba-VirusFilter VFS modules + Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan + + 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 . +*/ + +#ifndef _VIRUSFILTER_UTILS_H +#define _VIRUSFILTER_UTILS_H + +#include "modules/vfs_virusfilter_common.h" +#include "../lib/util/memcache.h" +#include "../lib/util/strv.h" + +/*#define str_eq(s1, s2) \ + ((strcmp((s1), (s2)) == 0) ? true : false) +#define strn_eq(s1, s2, n) \ + ((strncmp((s1), (s2), (n)) == 0) ? true : false) */ + +/* "* 3" is for %-encoding */ +#define VIRUSFILTER_IO_URL_MAX (PATH_MAX * 3) +#define VIRUSFILTER_IO_BUFFER_SIZE (VIRUSFILTER_IO_URL_MAX + 128) +#define VIRUSFILTER_IO_EOL_SIZE 1 +#define VIRUSFILTER_IO_IOV_MAX 16 +#define VIRUSFILTER_CACHE_BUFFER_SIZE (PATH_MAX + 128) + +struct virusfilter_io_handle { + struct tstream_context *stream; + int connect_timeout; /* msec */ + int io_timeout; /* msec */ + + /* end-of-line character(s) */ + char w_eol[VIRUSFILTER_IO_EOL_SIZE]; + int w_eol_size; + + /* end-of-line character(s) */ + char r_eol[VIRUSFILTER_IO_EOL_SIZE]; + int r_eol_size; + + /* buffer */ + char r_buffer[VIRUSFILTER_IO_BUFFER_SIZE]; + size_t r_len; +}; + +struct virusfilter_cache_entry { + time_t time; + virusfilter_result result; + char *report; +}; + +struct virusfilter_cache { + struct memcache *cache; + TALLOC_CTX *ctx; + time_t time_limit; +}; + +/* ====================================================================== */ + +char *virusfilter_string_sub( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *str); +int virusfilter_vfs_next_move( + vfs_handle_struct *handle, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst); + +/* Line-based socket I/O */ +struct virusfilter_io_handle *virusfilter_io_new( + TALLOC_CTX *mem_ctx, + int connect_timeout, + int timeout); +int virusfilter_io_set_connect_timeout( + struct virusfilter_io_handle *io_h, + int timeout); +int virusfilter_io_set_io_timeout( + struct virusfilter_io_handle *io_h, int timeout); +void virusfilter_io_set_writel_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size); +void virusfilter_io_set_readl_eol( + struct virusfilter_io_handle *io_h, + const char *eol, + int eol_size); +bool virusfilter_io_connect_path( + struct virusfilter_io_handle *io_h, + const char *path); +bool virusfilter_io_disconnect( + struct virusfilter_io_handle *io_h); +bool write_data_iov_timeout( + struct tstream_context *stream, + const struct iovec *iov, + size_t iovcnt, + int ms_timeout); +bool virusfilter_io_write( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size); +bool virusfilter_io_writel( + struct virusfilter_io_handle *io_h, + const char *data, + size_t data_size); +bool virusfilter_io_writefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, ...); +bool virusfilter_io_vwritefl( + struct virusfilter_io_handle *io_h, + const char *data_fmt, va_list ap); +bool virusfilter_io_writev( + struct virusfilter_io_handle *io_h, ...); +bool virusfilter_io_writevl( + struct virusfilter_io_handle *io_h, ...); +bool virusfilter_io_readl(TALLOC_CTX *ctx, + struct virusfilter_io_handle *io_h, + char **read_line); +bool virusfilter_io_writefl_readl( + struct virusfilter_io_handle *io_h, + char **read_line, + const char *fmt, ...); + +/* Scan result cache */ +struct virusfilter_cache *virusfilter_cache_new( + TALLOC_CTX *ctx, + int entry_limit, + time_t time_limit); +bool virusfilter_cache_entry_add( + struct virusfilter_cache *cache, + const char *directory, + const char *fname, + virusfilter_result result, + char *report); +bool virusfilter_cache_entry_rename( + struct virusfilter_cache *cache, + const char *directory, + char *old_fname, + char *new_fname); +void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e); +struct virusfilter_cache_entry *virusfilter_cache_get( + struct virusfilter_cache *cache, + const char *directory, + const char *fname); +void virusfilter_cache_remove( + struct virusfilter_cache *cache, + const char *directory, + const char *fname); +void virusfilter_cache_purge(struct virusfilter_cache *cache); + +/* Shell scripting */ +int virusfilter_env_set( + TALLOC_CTX *mem_ctx, + char **env_list, + const char *name, + const char *value); +int virusfilter_shell_set_conn_env( + TALLOC_CTX *mem_ctx, + char **env_list, + connection_struct *conn); +int virusfilter_shell_run( + TALLOC_CTX *mem_ctx, + const char *cmd, + char **env_list, + connection_struct *conn, + bool sanitize); + +#endif /* _VIRUSFILTER_UTILS_H */ diff --git a/source3/modules/vfs_vxfs.c b/source3/modules/vfs_vxfs.c new file mode 100644 index 0000000..aae2ca1 --- /dev/null +++ b/source3/modules/vfs_vxfs.c @@ -0,0 +1,736 @@ +/* +Unix SMB/CIFS implementation. +Wrap VxFS calls in vfs functions. +This module is for ACL and XATTR handling. + +Copyright (C) Symantec Corporation 2014 +Copyright (C) Veritas Technologies LLC 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 . +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "librpc/gen_ndr/ndr_xattr.h" +#include "../libcli/security/security.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "system/filesys.h" +#include "vfs_vxfs.h" + +#undef strcasecmp + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define MODULE_NAME "vxfs" + +/* + * WARNING !! WARNING !! + * + * DO NOT CHANGE THIS FROM "system." space to + * "user." space unless you are shipping a product + * that RESTRICTS access to extended attributes + * to smbd-only. "system." space is restricted + * to root access only, "user." space is available + * to ANY USER. + * + * If this is changed to "user." and access + * to extended attributes is available via + * local processes or other remote file system + * (e.g. NFS) then the security of the system + * WILL BE COMPROMISED. i.e. non-root users + * WILL be able to overwrite Samba ACLs on + * the file system. + * + * If you need to modify this define, do + * so using CFLAGS on your build command + * line. + * e.g. CFLAGS=-DXATTR_USER_NTACL="user.NTACL" + * + * Added by: 17 Sept. 2014. + * + */ + +/* + * Note: + * XATTR_USER_NTACL: This extended attribute is used + * to store Access Control List system objects by VxFS from DLV11 onwards. + * XATTR_USER_NTACL_V0: This extended attribute was used + * to store Access Control List system objects by VxFS till DLV10. + */ +#ifndef XATTR_USER_NTACL +#define XATTR_USER_NTACL "system.NTACL" +#define XATTR_USER_NTACL_V0 "user.NTACL" +#endif + +/* type values */ +#define VXFS_ACL_UNDEFINED_TYPE 0 +#define VXFS_ACL_USER_OBJ 1 +#define VXFS_ACL_GROUP_OBJ 2 +#define VXFS_ACL_USER 3 +#define VXFS_ACL_GROUP 4 +#define VXFS_ACL_OTHER 5 +#define VXFS_ACL_MASK 6 + +/* + * Helper function for comparing two strings + */ +static int vxfs_strcasecmp(const char *str1, const char *str2) +{ + bool match = strequal_m(str1, str2); + if (match) { + return 0; + } + return 1; +} + +/* + * Compare aces + * This will compare two ace entries for sorting + * each entry contains: type, perms and id + * Sort by type first, if type is same sort by id. + */ +static int vxfs_ace_cmp(const void *ace1, const void *ace2) +{ + int ret = 0; + uint16_t type_a1, type_a2; + uint32_t id_a1, id_a2; + + /* Type must be compared first */ + type_a1 = SVAL(ace1, 0); + type_a2 = SVAL(ace2, 0); + + ret = (type_a1 - type_a2); + if (!ret) { + /* Compare ID under type */ + /* skip perm thus take offset as 4*/ + id_a1 = IVAL(ace1, 4); + id_a2 = IVAL(ace2, 4); + ret = id_a1 - id_a2; + } + + return ret; +} + +static void vxfs_print_ace_buf(char *buf, int count) { + + int i, offset = 0; + uint16_t type, perm; + uint32_t id; + + DEBUG(10, ("vfs_vxfs: Printing aces:\n")); + for (i = 0; i < count; i++) { + type = SVAL(buf, offset); + offset += 2; + perm = SVAL(buf, offset); + offset += 2; + id = IVAL(buf, offset); + offset += 4; + + DEBUG(10, ("vfs_vxfs: type = %u, perm = %u, id = %u\n", + (unsigned int)type, (unsigned int)perm, + (unsigned int)id)); + } +} + +/* + * Sort aces so that comparing 2 ACLs will be straight forward. + * This function will fill buffer as follows: + * For each ace: + * 1. ace->a_type will be filled as first 2 bytes in buf. + * 2. ace->a_perm will be filled as next 2 bytes. + * 3. ace->xid will be filled as next 4 bytes. + * Thus each ace entry in buf is equal to 8 bytes. + * Also a_type is mapped to VXFS_ACL_* so that ordering aces + * becomes easy. + */ +static char * vxfs_sort_acl(SMB_ACL_T theacl, TALLOC_CTX *mem_ctx, + uint32_t o_uid, + uint32_t o_gid) { + + struct smb_acl_entry *smb_ace; + int i, count; + uint16_t type, perm; + uint32_t id; + int offset = 0; + char *buf = NULL; + + count = theacl->count; + + buf = talloc_zero_size(mem_ctx, count * 8); + if (!buf) { + return NULL; + } + + smb_ace = theacl->acl; + + for (i = 0; i < count; i++) { + /* Calculate type */ + /* Map type to SMB_ACL_* to VXFS_ACL_* */ + switch(smb_ace->a_type) { + case SMB_ACL_USER: + type = VXFS_ACL_USER; + break; + case SMB_ACL_USER_OBJ: + type = VXFS_ACL_USER_OBJ; + break; + case SMB_ACL_GROUP: + type = VXFS_ACL_GROUP; + break; + case SMB_ACL_GROUP_OBJ: + type = VXFS_ACL_GROUP_OBJ; + break; + case SMB_ACL_OTHER: + type = VXFS_ACL_OTHER; + break; + case SMB_ACL_MASK: + type = VXFS_ACL_MASK; + break; + default: + type = -1; + talloc_free(buf); + return NULL; + } + + type = type & 0xff; + + /* Calculate id: + * We get owner uid and owner group gid in o_uid and o_gid + * Put these ids instead of -1 + */ + switch(smb_ace->a_type) { + case SMB_ACL_USER: + id = smb_ace->info.user.uid; + break; + case SMB_ACL_GROUP: + id = smb_ace->info.group.gid; + break; + case SMB_ACL_USER_OBJ: + id = o_uid; + break; + case SMB_ACL_GROUP_OBJ: + id = o_gid; + break; + case SMB_ACL_MASK: + case SMB_ACL_OTHER: + id = -1; + break; + default: + /* Can't happen.. */ + id = -1; + break; + } + + /* Calculate perm */ + perm = smb_ace->a_perm & 0xff; + + /* TYPE is the first 2 bytes of an entry */ + SSVAL(buf, offset, type); + offset += 2; + + /* PERM is the next 2 bytes of an entry */ + SSVAL(buf, offset, perm); + offset += 2; + + /* ID is the last 4 bytes of an entry */ + SIVAL(buf, offset, id); + offset += 4; + + smb_ace++; + } + + qsort(buf, count, 8, vxfs_ace_cmp); + + DEBUG(10, ("vfs_vxfs: Print sorted aces:\n")); + vxfs_print_ace_buf(buf, count); + + return buf; +} + +/* This function gets e_buf as an arg which is sorted and created out of + * existing ACL. This function will compact this e_buf to c_buf where USER + * and GROUP aces matching with USER_OBJ and GROUP_OBJ will be merged + * respectively. + * This is similar to what posix_acls.c does. This will make sure existing + * acls are converted much similar to what posix_acls calculates. + */ + +static char * vxfs_compact_buf(char *e_buf, int *new_count, int count, + TALLOC_CTX *mem_ctx) +{ + int i, e_offset = 0, c_offset = 0; + uint16_t type, perm, o_perm; + uint32_t id, owner_id, group_id; + char *c_buf = NULL; + + + if (count < 2) { + return NULL; + } + + c_buf = talloc_zero_size(mem_ctx, count * 8); + if (!c_buf) { + return NULL; + } + + /*Copy first two enries from e_buf to c_buf + *These are USER_OBJ and GROUP_OBJ + */ + + memcpy(c_buf, e_buf, 16); + + (*new_count) = 2; + + owner_id = IVAL(e_buf, 4); + group_id = IVAL(e_buf, 12); + + c_offset = e_offset = 16; + + /* Start comparing other entries */ + for (i = 2; i < count; i++) { + + type = SVAL(e_buf, e_offset); + e_offset += 2; + perm = SVAL(e_buf, e_offset); + e_offset += 2; + id = IVAL(e_buf, e_offset); + e_offset += 4; + + switch(type) { + case VXFS_ACL_USER: + if (id == owner_id) { + o_perm = SVAL(c_buf, 2); + o_perm |= perm; + SSVAL(c_buf, 2, o_perm); + DEBUG(10, ("vfs_vxfs: merging with owner" + "e_type = %u," + "e_perm = %u," + "e_id = %u\n", (unsigned int)type, + (unsigned int)perm, + (unsigned int)id)); + continue; + } + break; + case VXFS_ACL_GROUP: + if (id == group_id) { + o_perm = SVAL(c_buf, 10); + o_perm |= perm; + SSVAL(c_buf, 10, o_perm); + DEBUG(10, ("vfs_vxfs: merging with owner group" + "e_type = %u," + "e_perm = %u," + "e_id = %u\n", (unsigned int)type, + (unsigned int)perm, + (unsigned int)id)); + continue; + } + break; + } + + SSVAL(c_buf, c_offset, type); + c_offset += 2; + + SSVAL(c_buf, c_offset, perm); + c_offset += 2; + + SIVAL(c_buf, c_offset, id); + c_offset += 4; + + (*new_count)++; + } + DEBUG(10, ("vfs_vxfs: new_count is %d\n", *new_count)); + return c_buf; +} + +/* Actually compare New ACL and existing ACL buf */ +static bool vxfs_compare_acls(char *e_buf, char *n_buf, int n_count, + int e_count) { + + uint16_t e_type, n_type; + int offset = 0; + + if (!e_buf && !n_buf) { + DEBUG(10, ("vfs_vxfs: Empty buffers!\n")); + return false; + } + + if ((e_count < 2) || (n_count < 2)) { + return false; + } + /*Get type from last entry from both buffers. + * It may or may not be ACL_MASK + */ + n_type = SVAL(n_buf, offset + (8 * (n_count-1))); + e_type = SVAL(e_buf, offset + (8 * (e_count-1))); + + /* Check for ACL_MASK entry properly. Handle all 4 cases*/ + + /* If ACL_MASK entry is present in any of the buffers, + * it will be always the last one. Calculate count to compare + * based on if ACL_MASK is present on new and existing ACL + */ + if ((n_type != VXFS_ACL_MASK) && (e_type == VXFS_ACL_MASK)){ + DEBUG(10, ("vfs_vxfs: New ACL does not have mask entry," + "reduce count by 1 and compare\n")); + e_count = e_count -1; + } + if ((n_type == VXFS_ACL_MASK) && (e_type != VXFS_ACL_MASK)){ + DEBUG(10, ("vfs_vxfs: new ACL to be set contains mask" + "existing ACL does not have mask entry\n" + "Need to set New ACL\n")); + return false; + } + + if (memcmp(e_buf, n_buf, (e_count * 8)) != 0) { + DEBUG(10, ("vfs_vxfs: Compare with memcmp," + "buffers not same!\n")); + return false; + } + + return true; +} + +/* In VxFS, POSIX ACLs are pointed by separate inode for each file/dir. + * However, files/dir share same POSIX ACL inode if ACLs are inherited + * from parent. + * To retain this behaviour, below function avoids ACL set call if + * underlying ACLs are already same and thus saves creating extra inode. + * + * This function will execute following steps: + * 1. Get existing ACL + * 2. Sort New ACL and existing ACL into buffers + * 3. Compact existing ACL buf + * 4. Finally compare New ACL buf and Compact buf + * 5. If same, return true + * 6. Else need to set New ACL + */ + +static bool vxfs_compare(struct files_struct *fsp, + SMB_ACL_T the_acl, + SMB_ACL_TYPE_T the_acl_type) +{ + SMB_ACL_T existing_acl = NULL; + bool ret = false; + int count = 0; + TALLOC_CTX *mem_ctx = talloc_tos(); + char *existing_buf = NULL, *new_buf = NULL, *compact_buf = NULL; + int status; + NTSTATUS ntstatus; + + DEBUG(10, ("vfs_vxfs: Getting existing ACL for %s\n", fsp_str_dbg(fsp))); + + existing_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, the_acl_type, mem_ctx); + if (existing_acl == NULL) { + DEBUG(10, ("vfs_vxfs: Failed to get ACL\n")); + goto out; + } + + DEBUG(10, ("vfs_vxfs: Existing ACL count=%d\n", existing_acl->count)); + DEBUG(10, ("vfs_vxfs: New ACL count=%d\n", the_acl->count)); + + if (existing_acl->count == 0) { + DEBUG(10, ("vfs_vxfs: ACL count is 0, Need to set\n")); + goto out; + } + + ntstatus = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(ntstatus)) { + DEBUG(10, ("vfs_vxfs: stat failed!\n")); + errno = map_errno_from_nt_status(ntstatus); + goto out; + } + + DEBUG(10, ("vfs_vxfs: Sorting existing ACL\n")); + existing_buf = vxfs_sort_acl(existing_acl, mem_ctx, + fsp->fsp_name->st.st_ex_uid, + fsp->fsp_name->st.st_ex_gid); + if (!existing_buf) + goto out; + + DEBUG(10, ("vfs_vxfs: Sorting new ACL\n")); + new_buf = vxfs_sort_acl(the_acl, mem_ctx, fsp->fsp_name->st.st_ex_uid, + fsp->fsp_name->st.st_ex_gid); + if (!new_buf) { + goto out; + } + + DEBUG(10, ("vfs_vxfs: Compact existing buf\n")); + compact_buf = vxfs_compact_buf(existing_buf, &count, + existing_acl->count, + mem_ctx); + if (!compact_buf) { + goto out; + } + + vxfs_print_ace_buf(compact_buf, count); + + /* COmpare ACLs only if count is same or mismatch by 1 */ + if ((count == the_acl->count) || + (count == the_acl->count + 1) || + (count+1 == the_acl->count)) { + + if (vxfs_compare_acls(compact_buf, new_buf, the_acl->count, + count)) { + DEBUG(10, ("vfs_vxfs: ACLs matched. Not setting.\n")); + ret = true; + goto out; + } else + DEBUG(10, ("vfs_vxfs: ACLs NOT matched. Setting\n")); + } else { + DEBUG(10, ("vfs_vxfs: ACLs count does not match. Setting\n")); + } + +out: + + TALLOC_FREE(existing_acl); + TALLOC_FREE(existing_buf); + TALLOC_FREE(compact_buf); + TALLOC_FREE(new_buf); + + return ret; +} + +#ifdef VXFS_ACL_SHARE +static int vxfs_sys_acl_set_fd(vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + + if (vxfs_compare(fsp, theacl, type)) { + return 0; + } + + return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); +} +#endif + +static int vxfs_fset_xattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name, + const void *value, size_t size, int flags){ + int ret = 0; + int tmp_ret = 0; + + DBG_DEBUG("In vxfs_fset_xattr\n"); + + ret = vxfs_setxattr_fd(fsp_get_io_fd(fsp), name, value, size, flags); + if ((ret == 0) || + ((ret == -1) && (errno != ENOTSUP) && (errno != ENOSYS))) { + /* + * version 1: user.NTACL xattr without inheritance supported + * version 2: user.NTACL xattr with inheritance supported + * version 3: new styled xattr security.NTACL with inheritance supported + * Hence, the old styled xattr user.NTACL should be removed + */ + tmp_ret = vxfs_strcasecmp(name, XATTR_NTACL_NAME); + if (tmp_ret == 0) { + SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, XATTR_USER_NTACL_V0); + DBG_DEBUG("Old style xattr %s removed...\n", XATTR_USER_NTACL_V0); + } + + return ret; + } + + DBG_DEBUG("Fallback to xattr\n"); + if (strcmp(name, XATTR_NTACL_NAME) == 0) { + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, XATTR_USER_NTACL, + value, size, flags); + } + + /* Clients can't set XATTR_USER_NTACL directly. */ + if (vxfs_strcasecmp(name, XATTR_USER_NTACL) == 0) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); +} + +static ssize_t vxfs_fget_xattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name, + void *value, size_t size){ + int ret; + + DEBUG(10, ("In vxfs_fget_xattr\n")); + + ret = vxfs_getxattr_fd(fsp_get_io_fd(fsp), name, value, size); + if ((ret != -1) || ((errno != ENOTSUP) && + (errno != ENOSYS) && (errno != ENODATA))) { + return ret; + } + + DEBUG(10, ("Fallback to xattr\n")); + if (strcmp(name, XATTR_NTACL_NAME) == 0) { + return SMB_VFS_NEXT_FGETXATTR(handle, fsp, XATTR_USER_NTACL, + value, size); + } + + /* Clients can't see XATTR_USER_NTACL directly. */ + if (vxfs_strcasecmp(name, XATTR_USER_NTACL) == 0) { + errno = ENOATTR; + return -1; + } + + return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); +} + +static int vxfs_fremove_xattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name){ + int ret = 0, ret_new = 0, old_errno; + + DEBUG(10, ("In vxfs_fremove_xattr\n")); + + /* Remove with old way */ + if (strcmp(name, XATTR_NTACL_NAME) == 0) { + ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, + XATTR_USER_NTACL); + } else { + /* Clients can't remove XATTR_USER_NTACL directly. */ + if (vxfs_strcasecmp(name, XATTR_USER_NTACL) != 0) { + ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, + name); + } + } + old_errno = errno; + + /* Remove with new way */ + ret_new = vxfs_removexattr_fd(fsp_get_io_fd(fsp), name); + /* + * If both fail, return failure else return whichever succeeded + */ + if (errno == ENOTSUP || errno == ENOSYS) { + errno = old_errno; + } + if ((ret_new != -1) && (ret == -1)) { + ret = ret_new; + } + + return ret; + +} + +static size_t vxfs_filter_list(char *list, size_t size) +{ + char *str = list; + + while (str - list < size) { + size_t element_len = strlen(str) + 1; + if (vxfs_strcasecmp(str, XATTR_USER_NTACL) == 0) { + memmove(str, + str + element_len, + size - (str - list) - element_len); + size -= element_len; + continue; + } + str += element_len; + } + return size; +} + +static ssize_t vxfs_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + ssize_t result; + + result = vxfs_listxattr_fd(fsp_get_io_fd(fsp), list, size); + if (result >= 0 || ((errno != ENOTSUP) && (errno != ENOSYS))) { + return result; + } + + result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); + + if (result <= 0) { + return result; + } + + /* Remove any XATTR_USER_NTACL elements from the returned list. */ + result = vxfs_filter_list(list, result); + + return result; +} + +static NTSTATUS vxfs_fset_ea_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + NTSTATUS err; + int ret = 0; + + DBG_DEBUG("Entered function\n"); + + err = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); + if (!NT_STATUS_IS_OK(err)) { + DBG_DEBUG("err:%d\n", err); + return err; + } + if (!(dosmode & FILE_ATTRIBUTE_READONLY)) { + ret = vxfs_checkwxattr_fd(fsp_get_io_fd(fsp)); + if (ret == -1) { + DBG_DEBUG("ret:%d\n", ret); + if ((errno != EOPNOTSUPP) && (errno != ENOENT)) { + return map_nt_error_from_unix(errno); + } + } + } + if (dosmode & FILE_ATTRIBUTE_READONLY) { + ret = vxfs_setwxattr_fd(fsp_get_io_fd(fsp)); + DBG_DEBUG("ret:%d\n", ret); + if (ret == -1) { + if ((errno != EOPNOTSUPP) && (errno != EINVAL)) { + return map_nt_error_from_unix(errno); + } + } + } + return NT_STATUS_OK; +} + +static int vfs_vxfs_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + vxfs_init(); + + return 0; +} + +static struct vfs_fn_pointers vfs_vxfs_fns = { + .connect_fn = vfs_vxfs_connect, + +#ifdef VXFS_ACL_SHARE + .sys_acl_set_fd_fn = vxfs_sys_acl_set_fd, +#endif + + .fset_dos_attributes_fn = vxfs_fset_ea_dos_attributes, + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fgetxattr_fn = vxfs_fget_xattr, + .flistxattr_fn = vxfs_flistxattr, + .fremovexattr_fn = vxfs_fremove_xattr, + .fsetxattr_fn = vxfs_fset_xattr, +}; + +static_decl_vfs; +NTSTATUS vfs_vxfs_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "vxfs", + &vfs_vxfs_fns); +} diff --git a/source3/modules/vfs_vxfs.h b/source3/modules/vfs_vxfs.h new file mode 100644 index 0000000..9194b8b --- /dev/null +++ b/source3/modules/vfs_vxfs.h @@ -0,0 +1,36 @@ +/* +Unix SMB/CIFS implementation. +Wrap VxFS xattr calls in vfs functions. + +Copyright (C) Veritas Technologies LLC 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 . +*/ + +int vxfs_setxattr_fd(int, const char *, const void *, size_t, int); + +int vxfs_getxattr_path(const char *, const char *, void *, size_t); +int vxfs_getxattr_fd(int, const char *, void *, size_t); + +int vxfs_removexattr_fd(int, const char *); + +int vxfs_listxattr_fd(int, char *, size_t); + +int vxfs_setwxattr_path(const char *, bool); +int vxfs_setwxattr_fd(int); + +int vxfs_checkwxattr_path(const char *); +int vxfs_checkwxattr_fd(int); + +void vxfs_init(void); diff --git a/source3/modules/vfs_widelinks.c b/source3/modules/vfs_widelinks.c new file mode 100644 index 0000000..c5b5084 --- /dev/null +++ b/source3/modules/vfs_widelinks.c @@ -0,0 +1,418 @@ +/* + * Widelinks VFS module. Causes smbd not to see symlinks. + * + * Copyright (C) Jeremy Allison, 2020 + * + * 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 . + */ + +/* + What does this module do ? It implements the explicitly insecure + "widelinks = yes" functionality that used to be in the core smbd + code. + + Now this is implemented here, the insecure share-escape code that + explicitly allows escape from an exported share path can be removed + from smbd, leaving it a cleaner and more maintainable code base. + + The smbd code can now always return ACCESS_DENIED if a path + leads outside a share. + + How does it do that ? There are 2 features. + + 1). When the upper layer code does a chdir() call to a pathname, + this module stores the requested pathname inside config->cwd. + + When the upper layer code does a getwd() or realpath(), we return + the absolute path of the value stored in config->cwd, *not* the + position on the underlying filesystem. + + This hides symlinks as if the chdir pathname contains a symlink, + normally doing a realpath call on it would return the real + position on the filesystem. For widelinks = yes, this isn't what + you want. You want the position you think is underneath the share + definition - the symlink path you used to go outside the share, + not the contents of the symlink itself. + + That way, the upper layer smbd code can strictly enforce paths + being underneath a share definition without the knowledge that + "widelinks = yes" has moved us outside the share definition. + + 1a). Note that when setting up a share, smbd may make calls such + as realpath and stat/lstat in order to set up the share definition. + These calls are made *before* smbd calls chdir() to move the working + directory below the exported share definition. In order to allow + this, all the vfs_widelinks functions are coded to just pass through + the vfs call to the next module in the chain if (a). The widelinks + module was loaded in error by an administrator and widelinks is + set to "no". This is the: + + if (!config->active) { + Module not active. + SMB_VFS_NEXT_XXXXX(...) + } + + idiom in the vfs functions. + + 1b). If the module was correctly active, but smbd has yet + to call chdir(), then config->cwd == NULL. In that case + the correct action (to match the previous widelinks behavior + in the code inside smbd) is to pass through the vfs call to + the next module in the chain. That way, any symlinks in the + pathname are still exposed to smbd, which will restrict them to + be under the exported share definition. This allows the module + to "fail safe" for any vfs call made when setting up the share + structure definition, rather than fail unsafe by hiding symlinks + before chdir is called. This is the: + + if (config->cwd == NULL) { + XXXXX syscall before chdir - see note 1b above. + return SMB_VFS_NEXT_XXXXX() + } + + idiom in the vfs functions. + + 2). The module hides the existence of symlinks by inside + lstat(), open(), and readdir() so long as it's not a POSIX + pathname request (those requests *must* be aware of symlinks + and the POSIX client has to follow them, it's expected that + a server will always fail to follow symlinks). + + It does this by: + + 2a). lstat -> stat + 2b). open removes any O_NOFOLLOW from flags. + 2c). The optimization in readdir that returns a stat + struct is removed as this could return a symlink mode + bit, causing smbd to always call stat/lstat itself on + a pathname (which we'll then use to hide symlinks). + +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "lib/util_path.h" + +struct widelinks_config { + bool active; + bool is_dfs_share; + char *cwd; +}; + +static int widelinks_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + struct widelinks_config *config; + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, + service, + user); + if (ret != 0) { + return ret; + } + + config = talloc_zero(handle->conn, + struct widelinks_config); + if (!config) { + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + config->active = lp_widelinks(SNUM(handle->conn)); + if (!config->active) { + DBG_ERR("vfs_widelinks module loaded with " + "widelinks = no\n"); + } + config->is_dfs_share = + (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn))); + SMB_VFS_HANDLE_SET_DATA(handle, + config, + NULL, /* free_fn */ + struct widelinks_config, + return -1); + return 0; +} + +static int widelinks_chdir(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int ret = -1; + struct widelinks_config *config = NULL; + char *new_cwd = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); + } + + /* + * We know we never get a path containing + * DOT or DOTDOT. + */ + + if (smb_fname->base_name[0] == '/') { + /* Absolute path - replace. */ + new_cwd = talloc_strdup(config, + smb_fname->base_name); + } else { + if (config->cwd == NULL) { + /* + * Relative chdir before absolute one - + * see note 1b above. + */ + struct smb_filename *current_dir_fname = + SMB_VFS_NEXT_GETWD(handle, + config); + if (current_dir_fname == NULL) { + return -1; + } + /* Paranoia.. */ + if (current_dir_fname->base_name[0] != '/') { + DBG_ERR("SMB_VFS_NEXT_GETWD returned " + "non-absolute path |%s|\n", + current_dir_fname->base_name); + TALLOC_FREE(current_dir_fname); + return -1; + } + config->cwd = talloc_strdup(config, + current_dir_fname->base_name); + TALLOC_FREE(current_dir_fname); + if (config->cwd == NULL) { + return -1; + } + } + new_cwd = talloc_asprintf(config, + "%s/%s", + config->cwd, + smb_fname->base_name); + } + if (new_cwd == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname); + if (ret == -1) { + TALLOC_FREE(new_cwd); + return ret; + } + /* Replace the cache we use for realpath/getwd. */ + TALLOC_FREE(config->cwd); + config->cwd = new_cwd; + DBG_DEBUG("config->cwd now |%s|\n", config->cwd); + return 0; +} + +static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + struct widelinks_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return NULL); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_GETWD(handle, ctx); + } + if (config->cwd == NULL) { + /* getwd before chdir. See note 1b above. */ + return SMB_VFS_NEXT_GETWD(handle, ctx); + } + return synthetic_smb_fname(ctx, + config->cwd, + NULL, + NULL, + 0, + 0); +} + +static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname_in) +{ + struct widelinks_config *config = NULL; + char *pathname = NULL; + char *resolved_pathname = NULL; + struct smb_filename *smb_fname; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return NULL); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_REALPATH(handle, + ctx, + smb_fname_in); + } + + if (config->cwd == NULL) { + /* realpath before chdir. See note 1b above. */ + return SMB_VFS_NEXT_REALPATH(handle, + ctx, + smb_fname_in); + } + + if (smb_fname_in->base_name[0] == '/') { + /* Absolute path - process as-is. */ + pathname = talloc_strdup(config, + smb_fname_in->base_name); + } else { + /* Relative path - most commonly "." */ + pathname = talloc_asprintf(config, + "%s/%s", + config->cwd, + smb_fname_in->base_name); + } + + SMB_ASSERT(pathname[0] == '/'); + + resolved_pathname = canonicalize_absolute_path(config, pathname); + if (resolved_pathname == NULL) { + TALLOC_FREE(pathname); + return NULL; + } + + DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n", + smb_fname_in->base_name, + pathname, + resolved_pathname); + + smb_fname = synthetic_smb_fname(ctx, + resolved_pathname, + NULL, + NULL, + 0, + 0); + TALLOC_FREE(pathname); + TALLOC_FREE(resolved_pathname); + return smb_fname; +} + +static int widelinks_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct widelinks_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + if (config->cwd == NULL) { + /* lstat before chdir. See note 1b above. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) { + /* POSIX sees symlinks. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + /* Replace with STAT. */ + return SMB_VFS_NEXT_STAT(handle, smb_fname); +} + +static int widelinks_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *_how) +{ + struct vfs_open_how how = *_how; + struct widelinks_config *config = NULL; + int ret; + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (config->active && + (config->cwd != NULL) && + !(smb_fname->flags & SMB_FILENAME_POSIX_PATH)) + { + /* + * Module active, openat after chdir (see note 1b above) and not + * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW. + */ + how.flags = (how.flags & ~O_NOFOLLOW); + } + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + if (config->is_dfs_share && ret == -1 && errno == ENOENT) { + struct smb_filename *full_fname = NULL; + int lstat_ret; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + lstat_ret = SMB_VFS_NEXT_LSTAT(handle, + full_fname); + if (lstat_ret != -1 && + VALID_STAT(full_fname->st) && + S_ISLNK(full_fname->st.st_ex_mode)) { + fsp->fsp_name->st = full_fname->st; + } + TALLOC_FREE(full_fname); + errno = ELOOP; + } + return ret; +} + +static struct vfs_fn_pointers vfs_widelinks_fns = { + .connect_fn = widelinks_connect, + + .openat_fn = widelinks_openat, + .lstat_fn = widelinks_lstat, + /* + * NB. We don't need an lchown function as this + * is only called (a) on directory create and + * (b) on POSIX extensions names. + */ + .chdir_fn = widelinks_chdir, + .getwd_fn = widelinks_getwd, + .realpath_fn = widelinks_realpath, +}; + +static_decl_vfs; +NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "widelinks", + &vfs_widelinks_fns); +} diff --git a/source3/modules/vfs_worm.c b/source3/modules/vfs_worm.c new file mode 100644 index 0000000..5c1bc6d --- /dev/null +++ b/source3/modules/vfs_worm.c @@ -0,0 +1,359 @@ +/* + * VFS module to disallow writes for older files + * + * Copyright (C) 2013, Volker Lendecke + * Copyright (C) 2023-2024, Björn Jacke + * + * 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 "smbd/smbd.h" +#include "system/filesys.h" +#include "libcli/security/security.h" + +struct worm_config_data { + double grace_period; +}; +static const uint32_t write_access_flags = FILE_WRITE_DATA | FILE_APPEND_DATA | + FILE_WRITE_ATTRIBUTES | + DELETE_ACCESS | WRITE_DAC_ACCESS | + WRITE_OWNER_ACCESS | FILE_WRITE_EA; + +static int vfs_worm_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct worm_config_data *config = NULL; + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) { + return 0; + } + + config = talloc_zero(handle->conn, struct worm_config_data); + if (config == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + config->grace_period = lp_parm_int(SNUM(handle->conn), "worm", + "grace_period", 3600); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct worm_config_data, + return -1); + return 0; + +} + +static bool is_readonly(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + double age; + struct worm_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct worm_config_data, + return true); + + if (!VALID_STAT(smb_fname->st)) { + goto out; + } + + age = timespec_elapsed(&smb_fname->st.st_ex_ctime); + + if (age > config->grace_period) { + return true; + } + +out: + return false; +} +static bool fsp_is_readonly(vfs_handle_struct *handle, files_struct *fsp) +{ + double age; + struct worm_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct worm_config_data, + return true); + + if (!VALID_STAT(fsp->fsp_name->st)) { + goto out; + } + + age = timespec_elapsed(&fsp->fsp_name->st.st_ex_ctime); + + if (age > config->grace_period) { + return true; + } + +out: + return false; +} + +static NTSTATUS vfs_worm_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS status; + bool readonly; + + readonly = is_readonly(handle, smb_fname); + + if (readonly && (access_mask & write_access_flags)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = SMB_VFS_NEXT_CREATE_FILE( + handle, req, dirfsp, smb_fname, access_mask, + share_access, create_disposition, create_options, + file_attributes, oplock_request, lease, allocation_size, + private_flags, sd, ea_list, result, pinfo, + in_context_blobs, out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Access via MAXIMUM_ALLOWED_ACCESS? + */ + if (readonly && ((*result)->access_mask & write_access_flags)) { + close_file_free(req, result, NORMAL_CLOSE); + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static int vfs_worm_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + if (is_readonly(handle, smb_fname) && + (fsp->access_mask & write_access_flags)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how); +} + +static int vfs_worm_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int vfs_worm_fchmod(vfs_handle_struct *handle, + files_struct *fsp, + mode_t mode) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); +} + +static int vfs_worm_fchown(vfs_handle_struct *handle, + files_struct *fsp, + uid_t uid, + gid_t gid) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid); +} + +static int vfs_worm_renameat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + if (is_readonly(handle, smb_fname_src)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_RENAMEAT( + handle, srcfsp, smb_fname_src, dstfsp, smb_fname_dst); +} + +static int vfs_worm_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, + const void *value, + size_t size, + int flags) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); +} + +static int vfs_worm_fremotexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); +} + +static int vfs_worm_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct smb_filename *full_fname = NULL; + bool readonly; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + readonly = is_readonly(handle, full_fname); + + TALLOC_FREE(full_fname); + + if (readonly) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags); +} + +static NTSTATUS vfs_worm_fset_dos_attributes(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t dosmode) +{ + if (fsp_is_readonly(handle, fsp)) { + return NT_STATUS_ACCESS_DENIED; + } + + return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); +} + +static NTSTATUS vfs_worm_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + if (fsp_is_readonly(handle, fsp)) { + return NT_STATUS_ACCESS_DENIED; + } + + return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); +} + +static int vfs_worm_sys_acl_set_fd(vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl); +} + +static int vfs_worm_sys_acl_delete_def_fd(vfs_handle_struct *handle, + struct files_struct *fsp) +{ + if (fsp_is_readonly(handle, fsp)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp); +} + +static struct vfs_fn_pointers vfs_worm_fns = { + .connect_fn = vfs_worm_connect, + .create_file_fn = vfs_worm_create_file, + .openat_fn = vfs_worm_openat, + .fntimes_fn = vfs_worm_fntimes, + .fchmod_fn = vfs_worm_fchmod, + .fchown_fn = vfs_worm_fchown, + .renameat_fn = vfs_worm_renameat, + .fsetxattr_fn = vfs_worm_fsetxattr, + .fremovexattr_fn = vfs_worm_fremotexattr, + .unlinkat_fn = vfs_worm_unlinkat, + .fset_dos_attributes_fn = vfs_worm_fset_dos_attributes, + .fset_nt_acl_fn = vfs_worm_fset_nt_acl, + .sys_acl_set_fd_fn = vfs_worm_sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = vfs_worm_sys_acl_delete_def_fd, +}; + +static_decl_vfs; +NTSTATUS vfs_worm_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "worm", + &vfs_worm_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + return ret; +} diff --git a/source3/modules/vfs_xattr_tdb.c b/source3/modules/vfs_xattr_tdb.c new file mode 100644 index 0000000..447d868 --- /dev/null +++ b/source3/modules/vfs_xattr_tdb.c @@ -0,0 +1,702 @@ +/* + * Store posix-level xattrs in a tdb + * + * Copyright (C) Volker Lendecke, 2007 + * Copyright (C) Andrew Bartlett, 2012 + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "source3/lib/xattr_tdb.h" +#include "lib/util/tevent_unix.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +struct xattr_tdb_config { + struct db_context *db; + bool ignore_user_xattr; +}; + +static bool xattr_tdb_init(struct vfs_handle_struct *handle, + struct xattr_tdb_config **_config); + +static bool is_user_xattr(const char *xattr_name) +{ + int match; + + match = strncmp(xattr_name, "user.", strlen("user.")); + return (match == 0); +} + +static int xattr_tdb_get_file_id(struct vfs_handle_struct *handle, + const char *path, struct file_id *id) +{ + int ret; + TALLOC_CTX *frame = talloc_stackframe(); + struct smb_filename *smb_fname; + + smb_fname = synthetic_smb_fname(frame, + path, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + TALLOC_FREE(frame); + errno = ENOMEM; + return -1; + } + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + + if (ret == -1) { + TALLOC_FREE(frame); + return -1; + } + + *id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &smb_fname->st); + TALLOC_FREE(frame); + return 0; +} + +struct xattr_tdb_getxattrat_state { + struct vfs_aio_state vfs_aio_state; + ssize_t xattr_size; + uint8_t *xattr_value; +}; + +static void xattr_tdb_getxattrat_done(struct tevent_req *subreq); + +static struct tevent_req *xattr_tdb_getxattrat_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *dir_fsp, + const struct smb_filename *smb_fname, + const char *xattr_name, + size_t alloc_hint) +{ + struct xattr_tdb_config *config = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct xattr_tdb_getxattrat_state *state = NULL; + struct smb_filename *cwd = NULL; + struct file_id id; + int ret; + int error; + int cwd_ret; + DATA_BLOB xattr_blob; + + if (!xattr_tdb_init(handle, &config)) { + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, + struct xattr_tdb_getxattrat_state); + if (req == NULL) { + return NULL; + } + state->xattr_size = -1; + + if (config->ignore_user_xattr && is_user_xattr(xattr_name)) { + subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state, + ev, + handle, + dir_fsp, + smb_fname, + xattr_name, + alloc_hint); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, xattr_tdb_getxattrat_done, req); + return req; + } + + cwd = SMB_VFS_GETWD(dir_fsp->conn, state); + if (tevent_req_nomem(cwd, req)) { + return tevent_req_post(req, ev); + } + + ret = SMB_VFS_CHDIR(dir_fsp->conn, dir_fsp->fsp_name); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + ret = xattr_tdb_get_file_id(handle, smb_fname->base_name, &id); + error = errno; + + cwd_ret = SMB_VFS_CHDIR(dir_fsp->conn, cwd); + SMB_ASSERT(cwd_ret == 0); + + if (ret == -1) { + tevent_req_error(req, error); + return tevent_req_post(req, ev); + } + + state->xattr_size = xattr_tdb_getattr(config->db, + state, + &id, + xattr_name, + &xattr_blob); + if (state->xattr_size == -1) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + if (alloc_hint == 0) { + /* + * The caller only wants to know the size. + */ + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (state->xattr_size == 0) { + /* + * There's no data. + */ + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (xattr_blob.length > alloc_hint) { + /* + * The data doesn't fit. + */ + state->xattr_size = -1; + tevent_req_error(req, ERANGE); + return tevent_req_post(req, ev); + } + + /* + * take the whole blob. + */ + state->xattr_value = xattr_blob.data; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void xattr_tdb_getxattrat_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct xattr_tdb_getxattrat_state *state = tevent_req_data( + req, struct xattr_tdb_getxattrat_state); + + state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq, + &state->vfs_aio_state, + state, + &state->xattr_value); + TALLOC_FREE(subreq); + if (state->xattr_size == -1) { + tevent_req_error(req, state->vfs_aio_state.error); + return; + } + + tevent_req_done(req); +} + + +static ssize_t xattr_tdb_getxattrat_recv(struct tevent_req *req, + struct vfs_aio_state *aio_state, + TALLOC_CTX *mem_ctx, + uint8_t **xattr_value) +{ + struct xattr_tdb_getxattrat_state *state = tevent_req_data( + req, struct xattr_tdb_getxattrat_state); + ssize_t xattr_size; + + if (tevent_req_is_unix_error(req, &aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *aio_state = state->vfs_aio_state; + xattr_size = state->xattr_size; + if (xattr_value != NULL) { + *xattr_value = talloc_move(mem_ctx, &state->xattr_value); + } + + tevent_req_received(req); + return xattr_size; +} + +static ssize_t xattr_tdb_fgetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, void *value, size_t size) +{ + struct xattr_tdb_config *config = NULL; + SMB_STRUCT_STAT sbuf; + struct file_id id; + ssize_t xattr_size; + DATA_BLOB blob; + TALLOC_CTX *frame = NULL; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + if (config->ignore_user_xattr && is_user_xattr(name)) { + return SMB_VFS_NEXT_FGETXATTR( + handle, fsp, name, value, size); + } + + if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) { + return -1; + } + + frame = talloc_stackframe(); + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf); + + xattr_size = xattr_tdb_getattr(config->db, frame, &id, name, &blob); + if (xattr_size < 0) { + errno = ENOATTR; + TALLOC_FREE(frame); + return -1; + } + + if (size == 0) { + TALLOC_FREE(frame); + return xattr_size; + } + + if (blob.length > size) { + TALLOC_FREE(frame); + errno = ERANGE; + return -1; + } + memcpy(value, blob.data, xattr_size); + TALLOC_FREE(frame); + return xattr_size; +} + +static int xattr_tdb_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *name, const void *value, + size_t size, int flags) +{ + struct xattr_tdb_config *config = NULL; + SMB_STRUCT_STAT sbuf; + struct file_id id; + int ret; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + if (config->ignore_user_xattr && is_user_xattr(name)) { + return SMB_VFS_NEXT_FSETXATTR( + handle, fsp, name, value, size, flags); + } + + if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) { + return -1; + } + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf); + + ret = xattr_tdb_setattr(config->db, &id, name, value, size, flags); + return ret; + +} + +static ssize_t xattr_tdb_flistxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, char *list, + size_t size) +{ + struct xattr_tdb_config *config = NULL; + SMB_STRUCT_STAT sbuf; + struct file_id id; + ssize_t backend_size; + ssize_t ret; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) { + return -1; + } + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf); + + ret = xattr_tdb_listattr(config->db, &id, list, size); + if (ret == -1) { + return -1; + } + if (ret == size) { + return ret; + } + if (!config->ignore_user_xattr) { + return ret; + } + SMB_ASSERT(ret < size); + + backend_size = SMB_VFS_NEXT_FLISTXATTR( + handle, fsp, list + ret, size - ret); + if (backend_size == -1) { + return -1; + } + + return ret + backend_size; +} + +static int xattr_tdb_fremovexattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, const char *name) +{ + struct xattr_tdb_config *config = NULL; + SMB_STRUCT_STAT sbuf; + struct file_id id; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + if (config->ignore_user_xattr && is_user_xattr(name)) { + return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); + } + + if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) { + return -1; + } + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf); + + return xattr_tdb_removeattr(config->db, &id, name); +} + +/* + * Destructor for the VFS private data + */ + +static void config_destructor(void **data) +{ + struct xattr_tdb_config **config = (struct xattr_tdb_config **)data; + TALLOC_FREE((*config)->db); +} + +/* + * Open the tdb file upon VFS_CONNECT + */ + +static bool xattr_tdb_init(struct vfs_handle_struct *handle, + struct xattr_tdb_config **_config) +{ + struct xattr_tdb_config *config = NULL; + const char *dbname; + char *def_dbname; + + if (SMB_VFS_HANDLE_TEST_DATA(handle)) { + SMB_VFS_HANDLE_GET_DATA(handle, config, struct xattr_tdb_config, + return false); + if (_config != NULL) { + *_config = config; + } + return true; + } + + config = talloc_zero(handle->conn, struct xattr_tdb_config); + if (config == NULL) { + errno = ENOMEM; + goto error; + } + + def_dbname = state_path(talloc_tos(), "xattr.tdb"); + if (def_dbname == NULL) { + errno = ENOSYS; + goto error; + } + + dbname = lp_parm_const_string(SNUM(handle->conn), + "xattr_tdb", + "file", + def_dbname); + + /* now we know dbname is not NULL */ + + become_root(); + config->db = db_open(handle, dbname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600, + DBWRAP_LOCK_ORDER_2, DBWRAP_FLAG_NONE); + unbecome_root(); + + if (config->db == NULL) { +#if defined(ENOTSUP) + errno = ENOTSUP; +#else + errno = ENOSYS; +#endif + TALLOC_FREE(def_dbname); + goto error; + } + TALLOC_FREE(def_dbname); + + config->ignore_user_xattr = lp_parm_bool( + SNUM(handle->conn), "xattr_tdb", "ignore_user_xattr", false); + + SMB_VFS_HANDLE_SET_DATA(handle, config, config_destructor, + struct xattr_tdb_config, return false); + + if (_config != NULL) { + *_config = config; + } + return true; + +error: + DBG_WARNING("Failed to initialize config: %s\n", strerror(errno)); + lp_do_parameter(SNUM(handle->conn), "ea support", "False"); + return false; +} + +static int xattr_tdb_openat(struct vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + struct files_struct *fsp, + const struct vfs_open_how *how) +{ + struct xattr_tdb_config *config = NULL; + SMB_STRUCT_STAT sbuf; + int fd; + int ret; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + if (fd == -1) { + return -1; + } + + if ((how->flags & (O_CREAT|O_EXCL)) != (O_CREAT|O_EXCL)) { + return fd; + } + + /* + * We know we used O_CREAT|O_EXCL and it worked. + * We must have created the file. + */ + + fsp_set_fd(fsp, fd); + ret = SMB_VFS_FSTAT(fsp, &sbuf); + fsp_set_fd(fsp, -1); + if (ret == -1) { + /* Can't happen... */ + DBG_WARNING("SMB_VFS_FSTAT failed on file %s (%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno)); + return -1; + } + + fsp->file_id = SMB_VFS_FILE_ID_CREATE(fsp->conn, &sbuf); + + xattr_tdb_remove_all_attrs(config->db, &fsp->file_id); + + return fd; +} + +static int xattr_tdb_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + mode_t mode) +{ + struct xattr_tdb_config *config = NULL; + struct file_id fileid; + struct stat_ex sbuf = { .st_ex_nlink = 0, }; + int ret; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + ret = SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + smb_fname, + mode); + if (ret < 0) { + return ret; + } + + ret = SMB_VFS_NEXT_FSTATAT( + handle, dirfsp, smb_fname, &sbuf, AT_SYMLINK_NOFOLLOW); + + if (ret == -1) { + /* Rename race. Let upper level take care of it. */ + return -1; + } + if (!S_ISDIR(sbuf.st_ex_mode)) { + /* Rename race. Let upper level take care of it. */ + return -1; + } + + fileid = SMB_VFS_FILE_ID_CREATE(handle->conn, &sbuf); + + xattr_tdb_remove_all_attrs(config->db, &fileid); + return 0; +} + +/* + * On unlink we need to delete the tdb record + */ +static int xattr_tdb_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct xattr_tdb_config *config = NULL; + struct smb_filename *smb_fname_tmp = NULL; + struct smb_filename *full_fname = NULL; + struct file_id id; + int ret = -1; + bool remove_record = false; + TALLOC_CTX *frame = NULL; + + if (!xattr_tdb_init(handle, &config)) { + return -1; + } + + frame = talloc_stackframe(); + + smb_fname_tmp = cp_smb_filename(frame, smb_fname); + if (smb_fname_tmp == NULL) { + TALLOC_FREE(frame); + errno = ENOMEM; + return -1; + } + + /* + * TODO: use SMB_VFS_STATX() once we have that + */ + + full_fname = full_path_from_dirfsp_atname(frame, + dirfsp, + smb_fname); + if (full_fname == NULL) { + goto out; + } + + if (full_fname->flags & SMB_FILENAME_POSIX_PATH) { + ret = SMB_VFS_NEXT_LSTAT(handle, full_fname); + } else { + ret = SMB_VFS_NEXT_STAT(handle, full_fname); + if (ret == -1 && (errno == ENOENT || errno == ELOOP)) { + if (VALID_STAT(smb_fname->st) && + S_ISLNK(smb_fname->st.st_ex_mode)) { + /* + * Original name was a link - Could be + * trying to remove a dangling symlink. + */ + ret = SMB_VFS_NEXT_LSTAT(handle, full_fname); + } + } + } + if (ret == -1) { + goto out; + } + smb_fname_tmp->st = full_fname->st; + + if (flags & AT_REMOVEDIR) { + /* Always remove record when removing a directory succeeds. */ + remove_record = true; + } else { + if (smb_fname_tmp->st.st_ex_nlink == 1) { + /* Only remove record on last link to file. */ + remove_record = true; + } + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname_tmp, + flags); + + if (ret == -1) { + goto out; + } + + if (!remove_record) { + goto out; + } + + id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &smb_fname_tmp->st); + + xattr_tdb_remove_all_attrs(config->db, &id); + + out: + TALLOC_FREE(frame); + return ret; +} + +static int xattr_tdb_connect(vfs_handle_struct *handle, const char *service, + const char *user) +{ + char *sname = NULL; + int res, snum; + + res = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (res < 0) { + return res; + } + + snum = find_service(talloc_tos(), service, &sname); + if (snum == -1 || sname == NULL) { + /* + * Should not happen, but we should not fail just *here*. + */ + return 0; + } + + if (!xattr_tdb_init(handle, NULL)) { + DEBUG(5, ("Could not init xattr tdb\n")); + lp_do_parameter(snum, "ea support", "False"); + return 0; + } + + lp_do_parameter(snum, "ea support", "True"); + + return 0; +} + +static struct vfs_fn_pointers vfs_xattr_tdb_fns = { + .getxattrat_send_fn = xattr_tdb_getxattrat_send, + .getxattrat_recv_fn = xattr_tdb_getxattrat_recv, + .fgetxattr_fn = xattr_tdb_fgetxattr, + .fsetxattr_fn = xattr_tdb_fsetxattr, + .flistxattr_fn = xattr_tdb_flistxattr, + .fremovexattr_fn = xattr_tdb_fremovexattr, + .openat_fn = xattr_tdb_openat, + .mkdirat_fn = xattr_tdb_mkdirat, + .unlinkat_fn = xattr_tdb_unlinkat, + .connect_fn = xattr_tdb_connect, +}; + +static_decl_vfs; +NTSTATUS vfs_xattr_tdb_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "xattr_tdb", + &vfs_xattr_tdb_fns); +} diff --git a/source3/modules/vfs_zfsacl.c b/source3/modules/vfs_zfsacl.c new file mode 100644 index 0000000..695abf1 --- /dev/null +++ b/source3/modules/vfs_zfsacl.c @@ -0,0 +1,507 @@ +/* + * Convert ZFS/NFSv4 acls to NT acls and vice versa. + * + * Copyright (C) Jiri Sasek, 2007 + * based on the foobar.c module which is copyrighted by Volker Lendecke + * + * Many thanks to Axel Apitz for help to fix the special ace's handling + * issues. + * + * 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/filesys.h" +#include "smbd/smbd.h" +#include "nfs4_acls.h" + +#ifdef HAVE_FREEBSD_SUNACL_H +#include "sunacl.h" +#endif + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +#define ZFSACL_MODULE_NAME "zfsacl" + +struct zfsacl_config_data { + struct smbacl4_vfs_params nfs4_params; + bool zfsacl_map_dacl_protected; + bool zfsacl_denymissingspecial; + bool zfsacl_block_special; +}; + +/* zfs_get_nt_acl() + * read the local file's acls and return it in NT form + * using the NFSv4 format conversion + */ +static NTSTATUS zfs_get_nt_acl_common(struct connection_struct *conn, + TALLOC_CTX *mem_ctx, + const struct smb_filename *smb_fname, + const ace_t *acebuf, + int naces, + struct SMB4ACL_T **ppacl, + struct zfsacl_config_data *config) +{ + int i; + struct SMB4ACL_T *pacl; + SMB_STRUCT_STAT sbuf; + const SMB_STRUCT_STAT *psbuf = NULL; + int ret; + bool inherited_is_present = false; + bool is_dir; + + if (VALID_STAT(smb_fname->st)) { + psbuf = &smb_fname->st; + } + + if (psbuf == NULL) { + ret = vfs_stat_smb_basename(conn, smb_fname, &sbuf); + if (ret != 0) { + DBG_INFO("stat [%s]failed: %s\n", + smb_fname_str_dbg(smb_fname), strerror(errno)); + return map_nt_error_from_unix(errno); + } + psbuf = &sbuf; + } + is_dir = S_ISDIR(psbuf->st_ex_mode); + + mem_ctx = talloc_tos(); + + /* create SMB4ACL data */ + if((pacl = smb_create_smb4acl(mem_ctx)) == NULL) { + return NT_STATUS_NO_MEMORY; + } + for(i=0; izfsacl_block_special && + (aceprop.aceMask == 0) && + (aceprop.aceFlags & ACE_EVERYONE) && + (aceprop.aceFlags & ACE_INHERITED_ACE)) + { + continue; + } + /* + * Windows clients expect SYNC on acls to correctly allow + * rename, cf bug #7909. But not on DENY ace entries, cf bug + * #8442. + */ + if (aceprop.aceType == SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) { + aceprop.aceMask |= SMB_ACE4_SYNCHRONIZE; + } + + special = acebuf[i].a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE); + + if (is_dir && + (aceprop.aceMask & SMB_ACE4_ADD_FILE) && + (special != 0)) + { + aceprop.aceMask |= SMB_ACE4_DELETE_CHILD; + } + +#ifdef ACE_INHERITED_ACE + if (aceprop.aceFlags & ACE_INHERITED_ACE) { + inherited_is_present = true; + } +#endif + switch(special) { + case(ACE_OWNER): + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_OWNER; + break; + case(ACE_GROUP): + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_GROUP; + break; + case(ACE_EVERYONE): + aceprop.flags = SMB_ACE4_ID_SPECIAL; + aceprop.who.special_id = SMB_ACE4_WHO_EVERYONE; + break; + default: + aceprop.flags = 0; + } + if (smb_add_ace4(pacl, &aceprop) == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + +#ifdef ACE_INHERITED_ACE + if (!inherited_is_present && config->zfsacl_map_dacl_protected) { + DBG_DEBUG("Setting SEC_DESC_DACL_PROTECTED on [%s]\n", + smb_fname_str_dbg(smb_fname)); + smbacl4_set_controlflags(pacl, + SEC_DESC_DACL_PROTECTED | + SEC_DESC_SELF_RELATIVE); + } +#endif + *ppacl = pacl; + return NT_STATUS_OK; +} + +/* call-back function processing the NT acl -> ZFS acl using NFSv4 conv. */ +static bool zfs_process_smbacl(vfs_handle_struct *handle, files_struct *fsp, + struct SMB4ACL_T *smbacl) +{ + int naces = smb_get_naces(smbacl), i, rv; + ace_t *acebuf; + struct SMB4ACE_T *smbace; + TALLOC_CTX *mem_ctx; + bool have_special_id = false; + bool must_add_empty_ace = false; + struct zfsacl_config_data *config = NULL; + int fd; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct zfsacl_config_data, + return False); + + if (config->zfsacl_block_special && S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { + naces++; + must_add_empty_ace = true; + } + /* allocate the field of ZFS aces */ + mem_ctx = talloc_tos(); + acebuf = (ace_t *) talloc_size(mem_ctx, sizeof(ace_t)*naces); + if(acebuf == NULL) { + errno = ENOMEM; + return False; + } + /* handle all aces */ + for(smbace = smb_first_ace4(smbacl), i = 0; + smbace!=NULL; + smbace = smb_next_ace4(smbace), i++) { + SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace); + + acebuf[i].a_type = aceprop->aceType; + acebuf[i].a_flags = aceprop->aceFlags; + acebuf[i].a_access_mask = aceprop->aceMask; + /* SYNC on acls is a no-op on ZFS. + See bug #7909. */ + acebuf[i].a_access_mask &= ~SMB_ACE4_SYNCHRONIZE; + acebuf[i].a_who = aceprop->who.id; + if(aceprop->flags & SMB_ACE4_ID_SPECIAL) { + switch(aceprop->who.special_id) { + case SMB_ACE4_WHO_EVERYONE: + acebuf[i].a_flags |= ACE_EVERYONE; + break; + case SMB_ACE4_WHO_OWNER: + acebuf[i].a_flags |= ACE_OWNER; + break; + case SMB_ACE4_WHO_GROUP: + acebuf[i].a_flags |= ACE_GROUP|ACE_IDENTIFIER_GROUP; + break; + default: + DEBUG(8, ("unsupported special_id %d\n", \ + aceprop->who.special_id)); + continue; /* don't add it !!! */ + } + have_special_id = true; + } + } + if (must_add_empty_ace) { + acebuf[i].a_type = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE; + acebuf[i].a_flags = SMB_ACE4_DIRECTORY_INHERIT_ACE | + SMB_ACE4_FILE_INHERIT_ACE | + ACE_EVERYONE | + ACE_INHERITED_ACE; + acebuf[i].a_access_mask = 0; + i++; + } + + if (!have_special_id && config->zfsacl_denymissingspecial) { + errno = EACCES; + return false; + } + + SMB_ASSERT(i == naces); + + /* store acl */ + fd = fsp_get_pathref_fd(fsp); + if (fd == -1) { + errno = EBADF; + return false; + } + rv = facl(fd, ACE_SETACL, naces, acebuf); + if (rv != 0) { + if(errno == ENOSYS) { + DEBUG(9, ("acl(ACE_SETACL, %s): Operation is not " + "supported on the filesystem where the file " + "resides\n", fsp_str_dbg(fsp))); + } else { + DEBUG(9, ("acl(ACE_SETACL, %s): %s\n", fsp_str_dbg(fsp), + strerror(errno))); + } + return false; + } + + return True; +} + +/* zfs_set_nt_acl() + * set the local file's acls obtaining it in NT form + * using the NFSv4 format conversion + */ +static NTSTATUS zfs_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + struct zfsacl_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct zfsacl_config_data, + return NT_STATUS_INTERNAL_ERROR); + + return smb_set_nt_acl_nfs4(handle, + fsp, + &config->nfs4_params, + security_info_sent, + psd, + zfs_process_smbacl); +} + +static int fget_zfsacl(TALLOC_CTX *mem_ctx, + struct files_struct *fsp, + ace_t **outbuf) +{ + int naces, rv; + ace_t *acebuf = NULL; + int fd; + + fd = fsp_get_pathref_fd(fsp); + if (fd == -1) { + errno = EBADF; + return -1; + } + naces = facl(fd, ACE_GETACLCNT, 0, NULL); + if (naces == -1) { + int dbg_level = 10; + + if (errno == ENOSYS) { + dbg_level = 1; + } + DEBUG(dbg_level, ("facl(ACE_GETACLCNT, %s): %s\n", + fsp_str_dbg(fsp), strerror(errno))); + return naces; + } + + acebuf = talloc_size(mem_ctx, sizeof(ace_t)*naces); + if (acebuf == NULL) { + errno = ENOMEM; + return -1; + } + + rv = facl(fd, ACE_GETACL, naces, acebuf); + if (rv == -1) { + DBG_DEBUG("acl(ACE_GETACL, %s): %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return -1; + } + + *outbuf = acebuf; + return naces; +} + +static NTSTATUS zfsacl_fget_nt_acl(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + TALLOC_CTX *frame = NULL; + struct SMB4ACL_T *pacl; + NTSTATUS status; + struct zfsacl_config_data *config = NULL; + ace_t *acebuf = NULL; + int naces; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct zfsacl_config_data, + return NT_STATUS_INTERNAL_ERROR); + + frame = talloc_stackframe(); + + naces = fget_zfsacl(talloc_tos(), fsp, &acebuf); + if (naces == -1) { + status = map_nt_error_from_unix(errno); + TALLOC_FREE(frame); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + return status; + } + + status = make_default_filesystem_acl(mem_ctx, + DEFAULT_ACL_POSIX, + fsp->fsp_name->base_name, + &fsp->fsp_name->st, + ppdesc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*ppdesc)->type |= SEC_DESC_DACL_PROTECTED; + return NT_STATUS_OK; + } + + status = zfs_get_nt_acl_common(handle->conn, + frame, + fsp->fsp_name, + acebuf, + naces, + &pacl, + config); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + status = smb_fget_nt_acl_nfs4(fsp, NULL, security_info, mem_ctx, + ppdesc, pacl); + TALLOC_FREE(frame); + return status; +} + +static NTSTATUS zfsacl_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + return zfs_set_nt_acl(handle, fsp, security_info_sent, psd); +} + +/* nils.goroll@hamburg.de 2008-06-16 : + + See also + - https://bugzilla.samba.org/show_bug.cgi?id=5446 + - http://bugs.opensolaris.org/view_bug.do?bug_id=6688240 + + Solaris supports NFSv4 and ZFS ACLs through a common system call, acl(2) + with ACE_SETACL / ACE_GETACL / ACE_GETACLCNT, which is being wrapped for + use by samba in this module. + + As the acl(2) interface is identical for ZFS and for NFS, this module, + vfs_zfsacl, can not only be used for ZFS, but also for sharing NFSv4 + mounts on Solaris. + + But while "traditional" POSIX DRAFT ACLs (using acl(2) with SETACL + / GETACL / GETACLCNT) fail for ZFS, the Solaris NFS client + implements a compatibility wrapper, which will make calls to + traditional ACL calls though vfs_solarisacl succeed. As the + compatibility wrapper's implementation is (by design) incomplete, + we want to make sure that it is never being called. + + As long as Samba does not support an explicit method for a module + to define conflicting vfs methods, we should override all conflicting + methods here. + + For this to work, we need to make sure that this module is initialised + *after* vfs_solarisacl + + Function declarations taken from vfs_solarisacl +*/ + +static SMB_ACL_T zfsacl_fail__sys_acl_get_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + TALLOC_CTX *mem_ctx) +{ + return (SMB_ACL_T)NULL; +} + +static int zfsacl_fail__sys_acl_set_fd(vfs_handle_struct *handle, + files_struct *fsp, + SMB_ACL_TYPE_T type, + SMB_ACL_T theacl) +{ + return -1; +} + +static int zfsacl_fail__sys_acl_delete_def_fd(vfs_handle_struct *handle, + files_struct *fsp) +{ + return -1; +} + +static int zfsacl_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) +{ + return -1; +} + +static int zfsacl_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) +{ + struct zfsacl_config_data *config = NULL; + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (ret < 0) { + return ret; + } + + config = talloc_zero(handle->conn, struct zfsacl_config_data); + if (!config) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + config->zfsacl_map_dacl_protected = lp_parm_bool(SNUM(handle->conn), + "zfsacl", "map_dacl_protected", false); + + config->zfsacl_denymissingspecial = lp_parm_bool(SNUM(handle->conn), + "zfsacl", "denymissingspecial", false); + + config->zfsacl_block_special = lp_parm_bool(SNUM(handle->conn), + "zfsacl", "block_special", true); + + ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params); + if (ret < 0) { + TALLOC_FREE(config); + return ret; + } + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct zfsacl_config_data, + return -1); + + return 0; +} + +/* VFS operations structure */ + +static struct vfs_fn_pointers zfsacl_fns = { + .connect_fn = zfsacl_connect, + .stat_fn = nfs4_acl_stat, + .fstat_fn = nfs4_acl_fstat, + .lstat_fn = nfs4_acl_lstat, + .fstatat_fn = nfs4_acl_fstatat, + .sys_acl_get_fd_fn = zfsacl_fail__sys_acl_get_fd, + .sys_acl_blob_get_fd_fn = zfsacl_fail__sys_acl_blob_get_fd, + .sys_acl_set_fd_fn = zfsacl_fail__sys_acl_set_fd, + .sys_acl_delete_def_fd_fn = zfsacl_fail__sys_acl_delete_def_fd, + .fget_nt_acl_fn = zfsacl_fget_nt_acl, + .fset_nt_acl_fn = zfsacl_fset_nt_acl, +}; + +static_decl_vfs; +NTSTATUS vfs_zfsacl_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "zfsacl", + &zfsacl_fns); +} diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build new file mode 100644 index 0000000..1f0aa44 --- /dev/null +++ b/source3/modules/wscript_build @@ -0,0 +1,639 @@ +#!/usr/bin/env python + +bld.SAMBA3_SUBSYSTEM('NFS4_ACLS', + source='nfs4_acls.c', + deps='samba-util tdb') + +bld.SAMBA3_BINARY('test_nfs4_acls', + source='test_nfs4_acls.c', + deps='smbd_base cmocka', + for_selftest=True) + +bld.SAMBA3_SUBSYSTEM('vfs_acl_common', + source='vfs_acl_common.c', + deps='gnutls') + +bld.SAMBA3_SUBSYSTEM('POSIXACL_XATTR', + source='posixacl_xattr.c', + enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs')), + deps='acl attr') + +bld.SAMBA3_SUBSYSTEM('non_posix_acls', + source='non_posix_acls.c', + deps='samba-util vfs') + +bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS', + source='vfs_virusfilter_utils.c', + deps='strv', + enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))) + +bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL', + source='vfs_aixacl_util.c', + enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2'))) + +bld.SAMBA3_SUBSYSTEM('vfs', + source='', + deps='smbd_base') + +bld.SAMBA3_SUBSYSTEM('OFFLOAD_TOKEN', + source='offload_token.c', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('UTIL_REPARSE', + source='util_reparse.c', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('HASH_INODE', + source='hash_inode.c', + deps='gnutls') + +# +# This is always be static, see +# source3/wscript: required_static_modules +# +bld.SAMBA3_MODULE('vfs_default', + subsystem='vfs', + source='vfs_default.c', + deps='samba-util NDR_DFSBLOBS OFFLOAD_TOKEN UTIL_REPARSE', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_default'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_default')) + +# +# This is always be static, see +# source3/wscript: required_static_modules +# +bld.SAMBA3_MODULE('vfs_not_implemented', + subsystem='vfs', + source='vfs_not_implemented.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_not_implemented'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_not_implemented')) + +bld.SAMBA3_MODULE('vfs_audit', + subsystem='vfs', + source='vfs_audit.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_audit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_audit')) + +bld.SAMBA3_MODULE('vfs_extd_audit', + subsystem='vfs', + source='vfs_extd_audit.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_extd_audit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_extd_audit')) + +bld.SAMBA3_MODULE('vfs_full_audit', + subsystem='vfs', + source='vfs_full_audit.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_full_audit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_full_audit')) + +bld.SAMBA3_BINARY('test_vfs_full_audit', + source='test_vfs_full_audit.c', + deps='smbd_base cmocka', + for_selftest=True) + +bld.SAMBA3_MODULE('vfs_fake_perms', + subsystem='vfs', + source='vfs_fake_perms.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_perms'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_perms')) + +bld.SAMBA3_MODULE('vfs_fake_acls', + subsystem='vfs', + source='vfs_fake_acls.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_acls'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_acls'), + install=False) + +bld.SAMBA3_MODULE('vfs_recycle', + subsystem='vfs', + source='vfs_recycle.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_recycle'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_recycle')) + +bld.SAMBA3_MODULE('vfs_fruit', + subsystem='vfs', + source='vfs_fruit.c', + deps='samba-util OFFLOAD_TOKEN STRING_REPLACE HASH_INODE ADOUBLE', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fruit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fruit')) + +bld.SAMBA3_MODULE('vfs_default_quota', + subsystem='vfs', + source='vfs_default_quota.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_default_quota'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_default_quota')) + +module_cflags='' +if bld.CONFIG_SET('HAVE_WNO_STRICT_OVERFLOW'): + module_cflags += ' -Wno-strict-overflow' + +if bld.CONFIG_SET('HAVE_WNO_UNUSED_BUT_SET_VARIABLE'): + module_cflags += ' -Wno-unused-but-set-variable' + +bld.SAMBA3_MODULE('vfs_readonly', + subsystem='vfs', + source='vfs_readonly.c getdate.c', + deps='samba-util', + cflags_end=module_cflags, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_readonly'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_readonly')) + +bld.SAMBA3_MODULE('vfs_cap', + subsystem='vfs', + source='vfs_cap.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_cap'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_cap')) + +bld.SAMBA3_MODULE('vfs_expand_msdfs', + subsystem='vfs', + source='vfs_expand_msdfs.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_expand_msdfs'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_expand_msdfs')) + +bld.SAMBA3_MODULE('vfs_shadow_copy', + subsystem='vfs', + source='vfs_shadow_copy.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shadow_copy'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shadow_copy')) + +bld.SAMBA3_MODULE('vfs_shadow_copy2', + subsystem='vfs', + source='vfs_shadow_copy2.c', + allow_warnings=True, + deps='samba-util tdb', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shadow_copy2'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shadow_copy2')) + +bld.SAMBA3_MODULE('vfs_afsacl', + subsystem='vfs', + source='vfs_afsacl.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_afsacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_afsacl')) + +bld.SAMBA3_MODULE('vfs_xattr_tdb', + subsystem='vfs', + source='vfs_xattr_tdb.c', + deps='dbwrap xattr_tdb', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_xattr_tdb'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_xattr_tdb')) + +bld.SAMBA3_MODULE('vfs_posix_eadb', + subsystem='vfs', + source='vfs_posix_eadb.c', + deps='tdb-wrap posix_eadb', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_posix_eadb') and bld.AD_DC_BUILD_IS_ENABLED(), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_posix_eadb') and bld.AD_DC_BUILD_IS_ENABLED()) + +bld.SAMBA3_MODULE('vfs_posixacl', + subsystem='vfs', + source='vfs_posixacl.c', + deps='acl attr', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_posixacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_posixacl')) + +bld.SAMBA3_BINARY('test_vfs_posixacl', + source='test_vfs_posixacl.c', + deps='smbd_base cmocka', + for_selftest=True) + +bld.SAMBA3_MODULE('vfs_aixacl', + subsystem='vfs', + source='vfs_aixacl.c', + deps='VFS_AIXACL_UTIL', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aixacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl')) + +bld.SAMBA3_MODULE('vfs_aixacl2', + subsystem='vfs', + source='vfs_aixacl2.c', + deps='NFS4_ACLS VFS_AIXACL_UTIL', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aixacl2'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2')) + +bld.SAMBA3_MODULE('vfs_solarisacl', + subsystem='vfs', + source='vfs_solarisacl.c', + init_function='', + deps='sec', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_solarisacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_solarisacl')) + +bld.SAMBA3_MODULE('vfs_zfsacl', + subsystem='vfs', + source='vfs_zfsacl.c', + deps='NFS4_ACLS sunacl', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_zfsacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_zfsacl')) + +if bld.SAMBA3_IS_ENABLED_MODULE('vfs_nfs4acl_xattr'): + bld.SAMBA_GENERATOR('nfs41acl-h', + source='nfs41acl.x', + target='nfs41acl.h', + rule='rpcgen -h ${SRC} > ${TGT}') + + if bld.CONFIG_SET("HAVE_RPC_XDR_H"): + xdr_buf_hack = r'sed -e "s@^\([ \t]*register int32_t \*buf\);@\\1 = buf;@"' + + # By default rpcgen assumes that the input file, generated header and + # source file are located in the same directory, which is extracted from + # the provided path to the input file. + # However if the build directory is not under the source tree, ${SRC} will + # be a long relative path through a common parent directory, resulting + # in an invalid path used in #include for the header. + # In order to fix that, the input file is first copied to the output build + # directory and then rpcgen is called with the proper path. + bld.SAMBA_GENERATOR('nfs41acl-xdr-c', + source='nfs41acl.x', + target='nfs41acl_xdr.c', + rule='cp -f ${SRC} ${TGT[0].parent} && rpcgen -c ' \ + '${TGT[0].path_from(tsk.get_cwd())[:-len(tsk.outputs[0].name)] + tsk.inputs[0].name} | ' + \ + xdr_buf_hack + ' > ${TGT}') + + bld.SAMBA_SUBSYSTEM('VFS_NFS4_XDR', + source='nfs41acl_xdr.c', + deps='NFS4_ACLS NDR_NFS4ACL tirpc') + else: + bld.SET_TARGET_TYPE('VFS_NFS4_XDR', 'EMPTY') + + bld.SAMBA3_MODULE('vfs_nfs4acl_xattr', + subsystem='vfs', + source = ''' + vfs_nfs4acl_xattr.c + nfs4acl_xattr_ndr.c + nfs4acl_xattr_xdr.c + nfs4acl_xattr_nfs.c + nfs4acl_xattr_util.c + ''', + deps='NFS4_ACLS sunacl NDR_NFS4ACL VFS_NFS4_XDR', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_nfs4acl_xattr')) + +bld.SAMBA3_MODULE('vfs_hpuxacl', + subsystem='vfs', + source='vfs_hpuxacl.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_hpuxacl'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_hpuxacl')) + +bld.SAMBA3_MODULE('vfs_catia', + subsystem='vfs', + source='vfs_catia.c', + deps='samba-util STRING_REPLACE', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_catia'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_catia')) + +bld.SAMBA3_MODULE('vfs_streams_xattr', + subsystem='vfs', + source='vfs_streams_xattr.c', + deps='samba-util HASH_INODE', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_streams_xattr'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_streams_xattr')) + +bld.SAMBA3_MODULE('vfs_streams_depot', + subsystem='vfs', + source='vfs_streams_depot.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_streams_depot'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_streams_depot')) + +bld.SAMBA3_MODULE('vfs_cacheprime', + subsystem='vfs', + source='vfs_cacheprime.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_cacheprime'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_cacheprime')) + +bld.SAMBA3_MODULE('vfs_prealloc', + subsystem='vfs', + source='vfs_prealloc.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_prealloc'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_prealloc')) + +bld.SAMBA3_MODULE('vfs_commit', + subsystem='vfs', + source='vfs_commit.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_commit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_commit')) + +bld.SAMBA3_MODULE('vfs_gpfs', + subsystem='vfs', + source='vfs_gpfs.c', + deps='NFS4_ACLS non_posix_acls gpfswrap', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_gpfs'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_gpfs'), + includes=bld.CONFIG_GET('CPPPATH_GPFS')) + +bld.SAMBA3_BINARY('test_vfs_gpfs', + source='test_vfs_gpfs.c', + deps='NFS4_ACLS non_posix_acls gpfswrap cmocka', + for_selftest=True, + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_gpfs'), + includes=bld.CONFIG_GET('CPPPATH_GPFS')) + +bld.SAMBA3_MODULE('vfs_readahead', + subsystem='vfs', + source='vfs_readahead.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_readahead'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_readahead')) + +bld.SAMBA3_MODULE('vfs_tsmsm', + subsystem='vfs', + source='vfs_tsmsm.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_tsmsm'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_tsmsm')) + +bld.SAMBA3_MODULE('vfs_fileid', + subsystem='vfs', + source='vfs_fileid.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fileid'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fileid')) + +bld.SAMBA3_MODULE('vfs_aio_fork', + subsystem='vfs', + source='vfs_aio_fork.c', + deps='samba-util tevent', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aio_fork'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aio_fork')) + +bld.SAMBA3_MODULE('vfs_aio_pthread', + subsystem='vfs', + source='vfs_aio_pthread.c', + deps='samba-util tevent', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aio_pthread'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aio_pthread')) + +bld.SAMBA3_MODULE('vfs_io_uring', + subsystem='vfs', + source='vfs_io_uring.c', + deps='samba-util tevent uring', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_io_uring'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_io_uring')) + +bld.SAMBA3_MODULE('vfs_preopen', + subsystem='vfs', + source='vfs_preopen.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_preopen'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_preopen')) + +bld.SAMBA3_MODULE('vfs_syncops', + subsystem='vfs', + source='vfs_syncops.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_syncops'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_syncops')) + +bld.SAMBA3_MODULE('vfs_acl_xattr', + subsystem='vfs', + source='vfs_acl_xattr.c', + deps='samba-util vfs_acl_common', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_acl_xattr'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_acl_xattr')) + +bld.SAMBA3_MODULE('vfs_acl_tdb', + subsystem='vfs', + source='vfs_acl_tdb.c', + deps='samba-util vfs_acl_common', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_acl_tdb'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_acl_tdb')) + +bld.SAMBA3_MODULE('vfs_dirsort', + subsystem='vfs', + source='vfs_dirsort.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_dirsort'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_dirsort')) + +bld.SAMBA3_MODULE('vfs_crossrename', + subsystem='vfs', + source='vfs_crossrename.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_crossrename'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_crossrename')) + +bld.SAMBA3_MODULE('vfs_linux_xfs_sgid', + subsystem='vfs', + source='vfs_linux_xfs_sgid.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_linux_xfs_sgid'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_linux_xfs_sgid')) + +bld.SAMBA3_MODULE('vfs_time_audit', + subsystem='vfs', + source='vfs_time_audit.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_time_audit'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_time_audit')) + +bld.SAMBA3_MODULE('vfs_media_harmony', + subsystem='vfs', + source='vfs_media_harmony.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_media_harmony'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_media_harmony')) + +bld.SAMBA3_MODULE('vfs_unityed_media', + subsystem='vfs', + source='vfs_unityed_media.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_unityed_media'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_unityed_media')) + +bld.SAMBA3_MODULE('vfs_dfs_samba4', + subsystem='vfs', + source='vfs_dfs_samba4.c', + deps='samba-util dfs_server_ad samdb tevent', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_dfs_samba4') and bld.AD_DC_BUILD_IS_ENABLED(), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_dfs_samba4') and bld.AD_DC_BUILD_IS_ENABLED()) + +bld.SAMBA3_MODULE('vfs_btrfs', + subsystem='vfs', + source='vfs_btrfs.c', + deps='samba-util OFFLOAD_TOKEN', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_btrfs'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_btrfs')) + +bld.SAMBA3_MODULE('vfs_shell_snap', + subsystem='vfs', + source='vfs_shell_snap.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shell_snap'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shell_snap')) + +bld.SAMBA3_SUBSYSTEM('perfcount', + source='', + deps='smbd_base') + +bld.SAMBA3_MODULE('vfs_ceph', + subsystem='vfs', + source='vfs_ceph.c', + deps='POSIXACL_XATTR samba-util cephfs', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_ceph'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph'), + cflags=bld.CONFIG_GET('CFLAGS_CEPHFS'), + includes=bld.CONFIG_GET('CPPPATH_CEPHFS')) + +bld.SAMBA3_MODULE('vfs_ceph_snapshots', + subsystem='vfs', + source='vfs_ceph_snapshots.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_ceph_snapshots'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph_snapshots')) + +bld.SAMBA3_MODULE('vfs_glusterfs', + subsystem='vfs', + source='vfs_glusterfs.c', + deps='POSIXACL_XATTR samba-util gfapi', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_glusterfs'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs')) + +bld.SAMBA3_MODULE('vfs_glusterfs_fuse', + subsystem='vfs', + source='vfs_glusterfs_fuse.c', + deps='', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_glusterfs_fuse'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs_fuse')) + +bld.SAMBA3_MODULE('vfs_worm', + subsystem='vfs', + source='vfs_worm.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_worm'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_worm')) + +bld.SAMBA3_MODULE('vfs_snapper', + subsystem='vfs', + source='vfs_snapper.c', + deps='samba-util dbus-1', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper')) + +bld.SAMBA3_MODULE('vfs_virusfilter', + subsystem='vfs', + source=''' + vfs_virusfilter.c + vfs_virusfilter_sophos.c + vfs_virusfilter_fsav.c + vfs_virusfilter_clamav.c + vfs_virusfilter_dummy.c + ''', + deps='samba-util VFS_VIRUSFILTER_UTILS', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')) + +bld.SAMBA3_MODULE('vfs_vxfs', + subsystem='vfs', + source='lib_vxfs.c vfs_vxfs.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_vxfs'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_vxfs')) + +bld.SAMBA3_MODULE('vfs_offline', + subsystem='vfs', + source='vfs_offline.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_offline'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_offline')) + +bld.SAMBA3_MODULE('vfs_fake_dfq', + subsystem='vfs', + source='vfs_fake_dfq.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_dfq'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_dfq'), + install=False) + +bld.SAMBA3_MODULE('vfs_error_inject', + subsystem='vfs', + source='vfs_error_inject.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_error_inject'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_error_inject'), + install=False) + +bld.SAMBA3_MODULE('vfs_delay_inject', + subsystem='vfs', + source='vfs_delay_inject.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_delay_inject'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_delay_inject'), + install=False) + +bld.SAMBA3_MODULE('vfs_widelinks', + subsystem='vfs', + source='vfs_widelinks.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_widelinks'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_widelinks')) -- cgit v1.2.3