summaryrefslogtreecommitdiffstats
path: root/source3/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/modules
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/modules')
-rw-r--r--source3/modules/README-gpfs-acl.txt82
-rw-r--r--source3/modules/README.nfs4acls.txt89
-rw-r--r--source3/modules/The_New_VFS.org469
-rw-r--r--source3/modules/The_New_VFS.txt607
-rw-r--r--source3/modules/getdate.c2742
-rw-r--r--source3/modules/getdate.h45
-rw-r--r--source3/modules/getdate.y1118
-rw-r--r--source3/modules/hash_inode.c87
-rw-r--r--source3/modules/hash_inode.h25
-rw-r--r--source3/modules/lib_vxfs.c279
-rw-r--r--source3/modules/nfs41acl.x111
-rw-r--r--source3/modules/nfs4_acls.c1223
-rw-r--r--source3/modules/nfs4_acls.h184
-rw-r--r--source3/modules/nfs4acl_xattr.h40
-rw-r--r--source3/modules/nfs4acl_xattr_ndr.c300
-rw-r--r--source3/modules/nfs4acl_xattr_ndr.h44
-rw-r--r--source3/modules/nfs4acl_xattr_nfs.c894
-rw-r--r--source3/modules/nfs4acl_xattr_nfs.h36
-rw-r--r--source3/modules/nfs4acl_xattr_util.c73
-rw-r--r--source3/modules/nfs4acl_xattr_util.h25
-rw-r--r--source3/modules/nfs4acl_xattr_xdr.c401
-rw-r--r--source3/modules/nfs4acl_xattr_xdr.h34
-rw-r--r--source3/modules/non_posix_acls.c63
-rw-r--r--source3/modules/non_posix_acls.h24
-rw-r--r--source3/modules/offload_token.c348
-rw-r--r--source3/modules/offload_token.h44
-rw-r--r--source3/modules/posixacl_xattr.c423
-rw-r--r--source3/modules/posixacl_xattr.h36
-rw-r--r--source3/modules/test_nfs4_acls.c1898
-rw-r--r--source3/modules/test_vfs_full_audit.c49
-rw-r--r--source3/modules/test_vfs_gpfs.c101
-rw-r--r--source3/modules/test_vfs_posixacl.c171
-rw-r--r--source3/modules/util_reparse.c84
-rw-r--r--source3/modules/util_reparse.h40
-rw-r--r--source3/modules/vfs_acl_common.c1181
-rw-r--r--source3/modules/vfs_acl_common.h91
-rw-r--r--source3/modules/vfs_acl_tdb.c387
-rw-r--r--source3/modules/vfs_acl_xattr.c552
-rw-r--r--source3/modules/vfs_afsacl.c1085
-rw-r--r--source3/modules/vfs_aio_fork.c936
-rw-r--r--source3/modules/vfs_aio_pthread.c538
-rw-r--r--source3/modules/vfs_aixacl.c131
-rw-r--r--source3/modules/vfs_aixacl.h34
-rw-r--r--source3/modules/vfs_aixacl2.c480
-rw-r--r--source3/modules/vfs_aixacl_util.c288
-rw-r--r--source3/modules/vfs_aixacl_util.h22
-rw-r--r--source3/modules/vfs_audit.c355
-rw-r--r--source3/modules/vfs_btrfs.c887
-rw-r--r--source3/modules/vfs_cacheprime.c181
-rw-r--r--source3/modules/vfs_cap.c1004
-rw-r--r--source3/modules/vfs_catia.c1952
-rw-r--r--source3/modules/vfs_ceph.c1960
-rw-r--r--source3/modules/vfs_ceph_snapshots.c1478
-rw-r--r--source3/modules/vfs_commit.c400
-rw-r--r--source3/modules/vfs_crossrename.c194
-rw-r--r--source3/modules/vfs_default.c4098
-rw-r--r--source3/modules/vfs_default_quota.c236
-rw-r--r--source3/modules/vfs_delay_inject.c440
-rw-r--r--source3/modules/vfs_dfs_samba4.c162
-rw-r--r--source3/modules/vfs_dirsort.c267
-rw-r--r--source3/modules/vfs_error_inject.c219
-rw-r--r--source3/modules/vfs_expand_msdfs.c270
-rw-r--r--source3/modules/vfs_extd_audit.c418
-rw-r--r--source3/modules/vfs_fake_acls.c704
-rw-r--r--source3/modules/vfs_fake_dfq.c281
-rw-r--r--source3/modules/vfs_fake_perms.c108
-rw-r--r--source3/modules/vfs_fileid.c723
-rw-r--r--source3/modules/vfs_fruit.c5478
-rw-r--r--source3/modules/vfs_full_audit.c3035
-rw-r--r--source3/modules/vfs_glusterfs.c2673
-rw-r--r--source3/modules/vfs_glusterfs_fuse.c273
-rw-r--r--source3/modules/vfs_gpfs.c2546
-rw-r--r--source3/modules/vfs_hpuxacl.c1174
-rw-r--r--source3/modules/vfs_hpuxacl.h56
-rw-r--r--source3/modules/vfs_io_uring.c822
-rw-r--r--source3/modules/vfs_linux_xfs_sgid.c128
-rw-r--r--source3/modules/vfs_media_harmony.c1873
-rw-r--r--source3/modules/vfs_nfs4acl_xattr.c580
-rw-r--r--source3/modules/vfs_not_implemented.c1193
-rw-r--r--source3/modules/vfs_offline.c50
-rw-r--r--source3/modules/vfs_posix_eadb.c450
-rw-r--r--source3/modules/vfs_posixacl.c389
-rw-r--r--source3/modules/vfs_posixacl.h38
-rw-r--r--source3/modules/vfs_prealloc.c222
-rw-r--r--source3/modules/vfs_preopen.c757
-rw-r--r--source3/modules/vfs_readahead.c186
-rw-r--r--source3/modules/vfs_readonly.c113
-rw-r--r--source3/modules/vfs_recycle.c737
-rw-r--r--source3/modules/vfs_shadow_copy.c274
-rw-r--r--source3/modules/vfs_shadow_copy2.c3303
-rw-r--r--source3/modules/vfs_shell_snap.c201
-rw-r--r--source3/modules/vfs_snapper.c2647
-rw-r--r--source3/modules/vfs_solarisacl.c699
-rw-r--r--source3/modules/vfs_solarisacl.h44
-rw-r--r--source3/modules/vfs_streams_depot.c1218
-rw-r--r--source3/modules/vfs_streams_xattr.c1614
-rw-r--r--source3/modules/vfs_syncops.c418
-rw-r--r--source3/modules/vfs_time_audit.c2812
-rw-r--r--source3/modules/vfs_tsmsm.c573
-rw-r--r--source3/modules/vfs_unityed_media.c1544
-rw-r--r--source3/modules/vfs_virusfilter.c1677
-rw-r--r--source3/modules/vfs_virusfilter_clamav.c195
-rw-r--r--source3/modules/vfs_virusfilter_common.h154
-rw-r--r--source3/modules/vfs_virusfilter_dummy.c58
-rw-r--r--source3/modules/vfs_virusfilter_fsav.c451
-rw-r--r--source3/modules/vfs_virusfilter_sophos.c391
-rw-r--r--source3/modules/vfs_virusfilter_utils.c1036
-rw-r--r--source3/modules/vfs_virusfilter_utils.h177
-rw-r--r--source3/modules/vfs_vxfs.c736
-rw-r--r--source3/modules/vfs_vxfs.h36
-rw-r--r--source3/modules/vfs_widelinks.c418
-rw-r--r--source3/modules/vfs_worm.c359
-rw-r--r--source3/modules/vfs_xattr_tdb.c702
-rw-r--r--source3/modules/vfs_zfsacl.c507
-rw-r--r--source3/modules/wscript_build639
115 files changed, 79907 insertions, 0 deletions
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 <<Disk>>
+ - 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 <<fsp>>
+ - 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 <<NsC>>
+
+ - 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 <<Path>>
+ 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 <<Symlink>>
+
+ - 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 <<Enum>>
+ - SMB_VFS_GET_DOS_ATTRIBUTES_SEND()
+ - SMB_VFS_GETXATTRAT_SEND()
+*** Handle based VFS functions not allowed on O_PATH opened handles <<xpathref>>
+ - 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 <<P2px>>
+ - SMB_VFS_CONNECTPATH()
+ - SMB_VFS_GET_REAL_FILENAME()
+ - SMB_VFS_REALPATH()
+ - SMB_VFS_TRANSLATE_NAME()
+
+ No changes needed.
+*** Special cases <<Special>>
+ - 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 <http://www.gnu.org/licenses/>. */
+
+/* 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 <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+
+ Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ the right thing about local DST. Unlike previous versions, this
+ version is reentrant. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# 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 <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h> /* 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 <string.h>
+#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 <stddef.h> /* 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 <libintl.h> /* 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 <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* 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 <stdlib.h> /* 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 <stdio.h> /* 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 <stdio.h>
+
+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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifndef PARAMS
+# if defined PROTOTYPES || (defined __STDC__ && __STDC__)
+# define PARAMS(Args) Args
+# else
+# define PARAMS(Args) ()
+# endif
+#endif
+
+#ifdef vms
+# include <types.h>
+# include <time.h>
+#else
+# include <sys/types.h>
+# if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+# else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# 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 <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+
+ Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ the right thing about local DST. Unlike previous versions, this
+ version is reentrant. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# 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 <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h> /* 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 <string.h>
+#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 <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
+%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+
+%type <intval> 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 <stdio.h>
+
+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 <asn@samba.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "hash_inode.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#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 <asn@samba.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _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 <www.veritas.com> 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "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 <http://www.gnu.org/licenses/>.
+ */
+
+#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, &params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ 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, &params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ 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; i<dacl->num_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, &params)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#ifdef HAVE_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+#include <rpc/xdr.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "smbd/proto.h"
+#include "libcli/security/security_descriptor.h"
+
+#ifdef HAVE_RPC_XDR_H
+/* <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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#ifdef HAVE_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+#include <rpc/xdr.h>
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <slow@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _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 <avati@redhat.com>
+ Copyright (c) 2016 Yan, Zheng <zyan@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/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 <avati@redhat.com>
+ Copyright (c) 2016 Yan, Zheng <zyan@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfs4_acls.c"
+#include "librpc/gen_ndr/idmap.h"
+#include "idmap_cache.h"
+#include <cmocka.h>
+
+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, &params, 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, &params, 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, &params, 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, &params,
+ 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, &params, 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, &params,
+ 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, &params, 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, &params,
+ 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, &params, 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, &params, 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, &params,
+ 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, &params, 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, &params, 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, &params,
+ 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, &params,
+ 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, &params, 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, &params, 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, &params, 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, &params,
+ 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, &params, 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* 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 <cmocka.h>
+
+static void test_full_audit_array(void **state)
+{
+ unsigned i;
+
+ for (i=0; i<SMB_VFS_OP_LAST; i++) {
+ assert_non_null(vfs_op_names[i].name);
+ assert_int_equal(vfs_op_names[i].type, i);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_full_audit_array),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/modules/test_vfs_gpfs.c b/source3/modules/test_vfs_gpfs.c
new file mode 100644
index 0000000..44454f8
--- /dev/null
+++ b/source3/modules/test_vfs_gpfs.c
@@ -0,0 +1,101 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Unit test for vfs_gpfs module.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_gpfs.c"
+#include <cmocka.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_posixacl.c"
+#include <cmocka.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <afs/stds.h>
+#include <afs/afs_args.h>
+#include <afs/venus.h>
+#include <afs/prs_fs.h>
+
+#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 = "<UNKNOWN>";
+ 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; i<num_fds; i++) {
+ close(fds[i]);
+ }
+
+ *recvfd = -1;
+ return n;
+ }
+
+ *recvfd = fds[0];
+ }
+
+ return(n);
+}
+
+static ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
+{
+ struct msghdr msg = {0};
+ size_t bufsize = msghdr_prep_fds(NULL, NULL, 0, &sendfd, 1);
+ uint8_t buf[bufsize];
+ struct iovec iov;
+ ssize_t sent;
+
+ msghdr_prep_fds(&msg, buf, bufsize, &sendfd, 1);
+
+ iov.iov_base = (void *)ptr;
+ iov.iov_len = nbytes;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ sent = sendmsg(fd, &msg, 0);
+ } while ((sent == -1) && (errno == EINTR));
+
+ return sent;
+}
+
+static void aio_child_cleanup(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct aio_child_list *list = talloc_get_type_abort(
+ private_data, struct aio_child_list);
+ struct aio_child *child, *next;
+
+ TALLOC_FREE(list->cleanup_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 <linux/falloc.h>
+#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 <jmcd@us.ibm.com> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <bjacke@samba.org> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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; i<jfs2_acl->aclEntryN; 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; i<acl_type_list.num_entries; i++) {
+ if (acl_type_list.entries[i].u64==aclType) {
+ memcpy(pacl_type_info, acl_type_list.entries + i, sizeof(acl_type_t));
+ DEBUG(10, ("found %s ACL support for %s\n",
+ pacl_type_info->acl_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 <jmcd@us.ibm.com> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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; i<theacl->count; 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 <jmcd@us.ibm.com> 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 <http://www.gnu.org/licenses/>.
+*/
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <linux/ioctl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <libgen.h>
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#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 <bchrisman@gmail.com>
+ Copyright (C) Richard Sharpe 2011 <realrichardsharpe@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * 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 <dirent.h>
+#include <sys/statvfs.h>
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <libgen.h>
+#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,
+ &timestamp_src, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname_dst,
+ &timestamp_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,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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
+ <warrenb@hpcvscdp.cv.hp.com>
+**********************************************************/
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#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; i<shadow_data->num_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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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)
+ *
+ * "<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
+ *
+ * "<myprefix>: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 "<prefix>:uid" parameter.
+ *
+ * - default value: yes (that means to report NO_LIMIT)
+ * - e.g.: default_quota:uid nolimit = no
+ *
+ * "<myprefix>:gid" parameter takes a integer argument,
+ * it's just like "<prefix>: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
+ *
+ * "<myprefix>:gid nolimit" parameter takes a boolean argument,
+ * it's just like "<prefix>: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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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, &current_mtime) == false) {
+ return NULL;
+ }
+
+ /* throw away cache and re-read the directory if we've changed */
+ if (timespec_compare(&current_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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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;i<data->num_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;i<data->num_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 <http://www.gnu.org/licenses/>.
+ */
+
+#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:]]*<key>band-size</key>$", 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:]]*"
+ "<integer>\\([[:digit:]]*\\)</integer>$",
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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; i++) {
+ bitmap_set(bm, i);
+ }
+ continue;
+ }
+
+ if (strequal(*ops, "none")) {
+ break;
+ }
+
+ op = ops[0];
+ if (op[0] == '!') {
+ neg = true;
+ op += 1;
+ }
+
+ for (i=0; i<SMB_VFS_OP_LAST; i++) {
+ if ((vfs_op_names[i].name == NULL)
+ || (vfs_op_names[i].type != i)) {
+ smb_panic("vfs_full_audit.c: name table not "
+ "in sync with vfs_op_type enums\n");
+ }
+ if (strequal(op, vfs_op_names[i].name)) {
+ if (neg) {
+ bitmap_clear(bm, i);
+ } else {
+ bitmap_set(bm, i);
+ }
+ break;
+ }
+ }
+ if (i == SMB_VFS_OP_LAST) {
+ DBG_ERR("Could not find opname %s\n", *ops);
+ TALLOC_FREE(bm);
+ return NULL;
+ }
+ }
+ return bm;
+}
+
+static const char *audit_opname(vfs_op_type op)
+{
+ if (op >= 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 <avati@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file vfs_glusterfs.c
+ * @author Anand Avati <avati@redhat.com>
+ * @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 <stdio.h>
+#include <glusterfs/api/glfs.h>
+#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(&times[0],
+ &fsp->fsp_name->st.st_ex_atime) == 0) &&
+ (timespec_compare(&times[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 <gd@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#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 <cambach1@de.ibm.com> 2006
+ * Copyright (C) Christof Schmitt 2015
+ * Major code contributions by Chetan Shringarpure <chetan.sh@in.ibm.com>
+ * and Gomati Mohanan <gomati.mohanan@in.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "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 <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#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; i<gacl->acl_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; i<gacl->acl_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; i<pacl->acl_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; i<pacl->count; 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <obnox@samba.org>
+ *
+ * ================================================================= */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "modules/vfs_hpuxacl.h"
+
+
+/*
+ * including standard header <sys/aclv.h>
+ *
+ * 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 <sys/aclv.h>, while the deprecated hfs acls
+ * are defined inside <sys/acl.h>.
+ *
+ */
+/* GROUP is defined somewhere else so undef it here... */
+#undef GROUP
+#include <sys/aclv.h>
+/* dl.h: needed to check for acl call via shl_findsym */
+#include <dl.h>
+
+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;i<acl_count;i++) {
+ switch(aclp[i].a_type) {
+ case USER:
+ acl_type_count->n_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<acl_count;i++) {
+ for (j=i+1; j<acl_count; j++) {
+ if( aclp[i].a_type > 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<acl_count;i++) {
+ n_class_obj_perm |= hpux_get_needed_class_perm((aclp+i));
+
+ if(aclp[i].a_type == CLASS_OBJ)
+ n_class_obj_index = i;
+ }
+ aclp[n_class_obj_index].a_perm = n_class_obj_perm;
+ }
+
+ return 0;
+}
+
+
+/*
+ * hpux_acl_call_present:
+ *
+ * This checks if the POSIX ACL system call is defined
+ * which basically corresponds to whether JFS 3.3 or
+ * higher is installed. If acl() was called when it
+ * isn't defined, it causes the process to core dump
+ * so it is important to check this and avoid acl()
+ * calls if it isn't there.
+ */
+
+static bool hpux_acl_call_present(void)
+{
+
+ shl_t handle = NULL;
+ void *value;
+ int ret_val=0;
+ static bool already_checked = False;
+
+ if(already_checked)
+ return True;
+
+ errno = 0;
+
+ ret_val = shl_findsym(&handle, "acl", TYPE_PROCEDURE, &value);
+
+ if(ret_val != 0) {
+ DEBUG(5, ("hpux_acl_call_present: shl_findsym() returned %d, errno = %d, error %s\n",
+ ret_val, errno, strerror(errno)));
+ DEBUG(5,("hpux_acl_call_present: acl() system call is not present. Check if you have JFS 3.3 and above?\n"));
+ errno = ENOSYS;
+ return False;
+ }
+
+ DEBUG(10,("hpux_acl_call_present: acl() system call is present. We have JFS 3.3 or above \n"));
+
+ already_checked = True;
+ return True;
+}
+
+/*
+ * runtime check for presence of aclsort library call.
+ * same code as for acl call. if there are more of these,
+ * a dispatcher function could be handy...
+ */
+
+static bool hpux_aclsort_call_present(void)
+{
+ shl_t handle = NULL;
+ void *value;
+ int ret_val = 0;
+ static bool already_checked = False;
+
+ if (already_checked) {
+ return True;
+ }
+
+ errno = 0;
+ ret_val = shl_findsym(&handle, "aclsort", TYPE_PROCEDURE, &value);
+ if (ret_val != 0) {
+ DEBUG(5, ("hpux_aclsort_call_present: shl_findsym "
+ "returned %d, errno = %d, error %s",
+ ret_val, errno, strerror(errno)));
+ DEBUG(5, ("hpux_aclsort_call_present: "
+ "aclsort() function not available.\n"));
+ return False;
+ }
+ DEBUG(10,("hpux_aclsort_call_present: aclsort() function present.\n"));
+ already_checked = True;
+ 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 hpux_acl_check(HPUX_ACL_T hpux_acl, int count)
+{
+ int check_rc;
+ int check_which;
+
+ check_rc = aclcheck(hpux_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",
+ hpux_acl[check_which].a_type));
+ DEBUGADD(10, (" * id: %d\n",
+ hpux_acl[check_which].a_id));
+ DEBUGADD(10, (" * perm: 0o%o\n",
+ hpux_acl[check_which].a_perm));
+ }
+ return False;
+ }
+ return True;
+}
+#endif
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers hpuxacl_fns = {
+ .sys_acl_get_fd_fn = hpuxacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ sys_acl_set_fd_fn = hpuxacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = hpuxacl_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_hpuxacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "hpuxacl",
+ &hpuxacl_fns);
+}
+
+/* ENTE */
diff --git a/source3/modules/vfs_hpuxacl.h b/source3/modules/vfs_hpuxacl.h
new file mode 100644
index 0000000..26562da
--- /dev/null
+++ b/source3/modules/vfs_hpuxacl.h
@@ -0,0 +1,56 @@
+/*
+ * Unix SMB/Netbios implementation.
+ * VFS module to get and set HP-UX 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <liburing/compat.h>
+#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 <liburing.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <philipn@users.sourceforge.net>
+ * Copyright (C) 2012 Andrew Klaassen <clawsoon@yahoo.com>
+ *
+ * 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 "_<ip address>_<user name>" 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <tdb.h>
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#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; i<acl->count; 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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: <ext> 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 <xfs/libxfs.h>
+#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: <extension> = <size>
+ * where
+ * <extension> is the file extension in lower case
+ * <size> 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; helper<state->num_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; i<c->num_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; i<num_helpers; i++) {
+ result->helpers[i].state = result;
+ result->helpers[i].fd = -1;
+ }
+
+ talloc_set_destructor(result, preopen_helpers_destructor);
+
+ for (i=0; i<num_helpers; i++) {
+ preopen_init_helper(&result->helpers[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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+
+ 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], &current_time);
+ time_t end_period = get_date(period[1], &current_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, <bbstone@amherst.edu>.
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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,
+ &timestamp_src, NULL, &snappath_src,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
+ smb_fname_dst,
+ &timestamp_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,
+ &timestamp_old,
+ NULL,
+ &snappath_old,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp_old,
+ NULL,
+ &snappath_old,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ &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,
+ &timestamp,
+ 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,
+ &timestamp,
+ &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,
+ &timestamp, 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,
+ &timestamp, &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. <prefix><delimiter><time_format>
+ */
+ 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, &timestamp_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(&timestamp_t, &timestamp);
+ } else {
+ if (strptime(name, fmt, &timestamp) == 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(&timestamp);
+ gmtime_r(&timestamp_t, &timestamp);
+ }
+ }
+
+ strftime(gmt, gmt_len, GMT_FORMAT, &timestamp);
+ 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ &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,
+ &timestamp, &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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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,
+ &timestamp,
+ &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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <dbus/dbus.h>
+#ifdef HAVE_LINUX_IOCTL_H
+#include <linux/ioctl.h>
+#endif
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <libgen.h>
+#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,
+ &timestamp_src, NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname_dst,
+ &timestamp_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,
+ &timestamp_old,
+ NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp_old,
+ NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp, &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,
+ &timestamp, &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,
+ &timestamp, &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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp,
+ &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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, &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,
+ &timestamp, 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, &timestamp, 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,
+ &timestamp,
+ 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,&timestamp, &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, &timestamp, &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, &timestamp, &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,
+ &timestamp,
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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; i<num_names; i++) {
+ struct ea_struct ea;
+
+ /*
+ * We want to check with samba_private_attr_name()
+ * whether the xattr name is a private one,
+ * unfortunately it flags xattrs that begin with the
+ * default streams prefix as private.
+ *
+ * By only calling samba_private_attr_name() in case
+ * the xattr does NOT begin with the default prefix,
+ * we know that if it returns 'true' it definitely one
+ * of our internal xattr like "user.DOSATTRIB".
+ */
+ if (strncasecmp_m(names[i], SAMBA_XATTR_DOSSTREAM_PREFIX,
+ strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) != 0) {
+ if (samba_private_attr_name(names[i])) {
+ continue;
+ }
+ }
+
+ if (strncmp(names[i], config->prefix,
+ 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 = <bytes>. 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 <achirmul@in.ibm.com> 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 = <NULL>",
+ connectpath);
+ } else if (connectpath == NULL && base_name != NULL) {
+ msg = talloc_asprintf(talloc_tos(), "connectpath = <NULL>, "
+ "base_name = \"%s\"",
+ base_name);
+ } else { /* connectpath == NULL && base_name == NULL */
+ msg = talloc_asprintf(talloc_tos(), "connectpath = <NULL>, "
+ "base_name = <NULL>");
+ }
+ 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), "<getcwd() error %d>", errno);
+ }
+ if (fname != NULL) {
+ msg = talloc_asprintf(talloc_tos(),
+ "cwd = \"%s\", filename = \"%s\"",
+ cwd, fname);
+ } else {
+ msg = talloc_asprintf(talloc_tos(),
+ "cwd = \"%s\", filename = <NULL>",
+ 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 = <NULL>");
+ }
+}
+
+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 <http://www.gnu.org/licenses/>.
+ */
+/*
+ This VFS module accepts following options:
+ tsmsm: hsm script = <path to hsm script> (default does nothing)
+ hsm script should point to a shell script which accepts two arguments:
+ <operation> <filepath>
+ where <operation> is currently 'offline' to set offline status of the <filepath>
+
+ 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 <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+#ifndef _ISOC99_SOURCE
+#define _ISOC99_SOURCE
+#endif
+
+#include <math.h>
+
+/* 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 <philipn@users.sourceforge.net>
+ * Copyright (C) 2012 Andrew Klaassen <clawsoon@yahoo.com>
+ * Copyright (C) 2013 Milos Lukacek
+ * Copyright (C) 2013 Ralph Boehme <slow@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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 <libgen.h>
+#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 "_<remote_name>.<number>" 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 <slow@samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+/* 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) {
+
+ /* <FILEPATH>: OK */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = talloc_asprintf(talloc_tos(), "Clean");
+ } else if (strcmp(reply_token, "FOUND") == 0) {
+
+ /* <FILEPATH>: <REPORT> FOUND */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ report = talloc_strdup(talloc_tos(), reply_msg);
+ } else if (strcmp(reply_token, "ERROR") == 0) {
+
+ /* <FILEPATH>: <REPORT> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include <stdint.h>
+#include <time.h>
+
+/* 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ý <pfilipen@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "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 <http://www.gnu.org/licenses/>.
+*/
+
+#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<FILEPATH> */
+ 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<FILEPATH>\t<REPORT>\t<ENGINE> */
+ 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<FILEPATH>\t0x<CODE>\t<REPORT> [<ENGINE>] */
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+struct iovec;
+
+#include "lib/util/iov_buf.h"
+#include <tevent.h>
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <www.symantec.com> 2014
+Copyright (C) Veritas Technologies LLC <www.veritas.com> 2016
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "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: <jra@samba.org> 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 <www.veritas.com> 2016
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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; i<naces; i++) {
+ SMB_ACE4PROP_T aceprop;
+ uint16_t special = 0;
+
+ aceprop.aceType = (uint32_t) acebuf[i].a_type;
+ aceprop.aceFlags = (uint32_t) acebuf[i].a_flags;
+ aceprop.aceMask = (uint32_t) acebuf[i].a_access_mask;
+ aceprop.who.id = (uint32_t) acebuf[i].a_who;
+
+ if (config->zfsacl_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'))