aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2021-08-31 09:11:55 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2021-08-31 09:11:55 -0700
commite24c567b7ecff1c8b6023a10d7f78256cef742c4 (patch)
tree8f2e29d1bd8107ba4cb0d25ac7f4dfca4cbd2603
parentb91db6a0b52e019b6bdabea3f1dbe36d85c7e52c (diff)
parent7d5d8d7156892f82cf40b63228ce788248cc57a3 (diff)
Merge tag '5.15-rc-first-ksmbd-merge' of git://git.samba.org/ksmbd
Pull initial ksmbd implementation from Steve French: "Initial merge of kernel smb3 file server, ksmbd. The SMB family of protocols is the most widely deployed network filesystem protocol, the default on Windows and Macs (and even on many phones and tablets), with clients and servers on all major operating systems, but lacked a kernel server for Linux. For many cases the current userspace server choices were suboptimal either due to memory footprint, performance or difficulty integrating well with advanced Linux features. ksmbd is a new kernel module which implements the server-side of the SMB3 protocol. The target is to provide optimized performance, GPLv2 SMB server, and better lease handling (distributed caching). The bigger goal is to add new features more rapidly (e.g. RDMA aka "smbdirect", and recent encryption and signing improvements to the protocol) which are easier to develop on a smaller, more tightly optimized kernel server than for example in Samba. The Samba project is much broader in scope (tools, security services, LDAP, Active Directory Domain Controller, and a cross platform file server for a wider variety of purposes) but the user space file server portion of Samba has proved hard to optimize for some Linux workloads, including for smaller devices. This is not meant to replace Samba, but rather be an extension to allow better optimizing for Linux, and will continue to integrate well with Samba user space tools and libraries where appropriate. Working with the Samba team we have already made sure that the configuration files and xattrs are in a compatible format between the kernel and user space server. Various types of functional and regression tests are regularly run against it. One example is the automated 'buildbot' regression tests which use the Linux client to test against ksmbd, e.g. http://smb3-test-rhel-75.southcentralus.cloudapp.azure.com/#/builders/8/builds/56 but other test suites, including Samba's smbtorture functional test suite are also used regularly" * tag '5.15-rc-first-ksmbd-merge' of git://git.samba.org/ksmbd: (219 commits) ksmbd: fix __write_overflow warning in ndr_read_string MAINTAINERS: ksmbd: add cifs_common directory to ksmbd entry MAINTAINERS: ksmbd: update my email address ksmbd: fix permission check issue on chown and chmod ksmbd: don't set FILE DELETE and FILE_DELETE_CHILD in access mask by default MAINTAINERS: add git adddress of ksmbd ksmbd: update SMB3 multi-channel support in ksmbd.rst ksmbd: smbd: fix kernel oops during server shutdown ksmbd: remove select FS_POSIX_ACL in Kconfig ksmbd: use proper errno instead of -1 in smb2_get_ksmbd_tcon() ksmbd: update the comment for smb2_get_ksmbd_tcon() ksmbd: change int data type to boolean ksmbd: Fix multi-protocol negotiation ksmbd: fix an oops in error handling in smb2_open() ksmbd: add ipv6_addr_v4mapped check to know if connection from client is ipv4 ksmbd: fix missing error code in smb2_lock ksmbd: use channel signingkey for binding SMB2 session setup ksmbd: don't set RSS capable in FSCTL_QUERY_NETWORK_INTERFACE_INFO ksmbd: Return STATUS_OBJECT_PATH_NOT_FOUND if smb2_creat() returns ENOENT ksmbd: fix -Wstringop-truncation warnings ...
-rw-r--r--Documentation/filesystems/cifs/index.rst10
-rw-r--r--Documentation/filesystems/cifs/ksmbd.rst165
-rw-r--r--Documentation/filesystems/index.rst2
-rw-r--r--MAINTAINERS13
-rw-r--r--fs/Kconfig1
-rw-r--r--fs/Makefile1
-rw-r--r--fs/ksmbd/Kconfig68
-rw-r--r--fs/ksmbd/Makefile20
-rw-r--r--fs/ksmbd/asn1.c343
-rw-r--r--fs/ksmbd/asn1.h21
-rw-r--r--fs/ksmbd/auth.c1364
-rw-r--r--fs/ksmbd/auth.h67
-rw-r--r--fs/ksmbd/connection.c413
-rw-r--r--fs/ksmbd/connection.h213
-rw-r--r--fs/ksmbd/crypto_ctx.c282
-rw-r--r--fs/ksmbd/crypto_ctx.h74
-rw-r--r--fs/ksmbd/glob.h49
-rw-r--r--fs/ksmbd/ksmbd_netlink.h395
-rw-r--r--fs/ksmbd/ksmbd_spnego_negtokeninit.asn131
-rw-r--r--fs/ksmbd/ksmbd_spnego_negtokentarg.asn119
-rw-r--r--fs/ksmbd/ksmbd_work.c80
-rw-r--r--fs/ksmbd/ksmbd_work.h117
-rw-r--r--fs/ksmbd/mgmt/ksmbd_ida.c46
-rw-r--r--fs/ksmbd/mgmt/ksmbd_ida.h34
-rw-r--r--fs/ksmbd/mgmt/share_config.c238
-rw-r--r--fs/ksmbd/mgmt/share_config.h81
-rw-r--r--fs/ksmbd/mgmt/tree_connect.c121
-rw-r--r--fs/ksmbd/mgmt/tree_connect.h56
-rw-r--r--fs/ksmbd/mgmt/user_config.c69
-rw-r--r--fs/ksmbd/mgmt/user_config.h66
-rw-r--r--fs/ksmbd/mgmt/user_session.c369
-rw-r--r--fs/ksmbd/mgmt/user_session.h106
-rw-r--r--fs/ksmbd/misc.c338
-rw-r--r--fs/ksmbd/misc.h35
-rw-r--r--fs/ksmbd/ndr.c345
-rw-r--r--fs/ksmbd/ndr.h22
-rw-r--r--fs/ksmbd/nterr.h543
-rw-r--r--fs/ksmbd/ntlmssp.h169
-rw-r--r--fs/ksmbd/oplock.c1709
-rw-r--r--fs/ksmbd/oplock.h131
-rw-r--r--fs/ksmbd/server.c633
-rw-r--r--fs/ksmbd/server.h70
-rw-r--r--fs/ksmbd/smb2misc.c438
-rw-r--r--fs/ksmbd/smb2ops.c312
-rw-r--r--fs/ksmbd/smb2pdu.c8373
-rw-r--r--fs/ksmbd/smb2pdu.h1698
-rw-r--r--fs/ksmbd/smb_common.c674
-rw-r--r--fs/ksmbd/smb_common.h542
-rw-r--r--fs/ksmbd/smbacl.c1366
-rw-r--r--fs/ksmbd/smbacl.h212
-rw-r--r--fs/ksmbd/smbfsctl.h91
-rw-r--r--fs/ksmbd/smbstatus.h1822
-rw-r--r--fs/ksmbd/transport_ipc.c874
-rw-r--r--fs/ksmbd/transport_ipc.h47
-rw-r--r--fs/ksmbd/transport_rdma.c2058
-rw-r--r--fs/ksmbd/transport_rdma.h63
-rw-r--r--fs/ksmbd/transport_tcp.c618
-rw-r--r--fs/ksmbd/transport_tcp.h13
-rw-r--r--fs/ksmbd/unicode.c384
-rw-r--r--fs/ksmbd/unicode.h357
-rw-r--r--fs/ksmbd/uniupr.h268
-rw-r--r--fs/ksmbd/vfs.c1895
-rw-r--r--fs/ksmbd/vfs.h197
-rw-r--r--fs/ksmbd/vfs_cache.c725
-rw-r--r--fs/ksmbd/vfs_cache.h178
-rw-r--r--fs/ksmbd/xattr.h122
66 files changed, 32254 insertions, 2 deletions
diff --git a/Documentation/filesystems/cifs/index.rst b/Documentation/filesystems/cifs/index.rst
new file mode 100644
index 000000000000..1c8597a679ab
--- /dev/null
+++ b/Documentation/filesystems/cifs/index.rst
@@ -0,0 +1,10 @@
+===============================
+CIFS
+===============================
+
+
+.. toctree::
+ :maxdepth: 1
+
+ ksmbd
+ cifsroot
diff --git a/Documentation/filesystems/cifs/ksmbd.rst b/Documentation/filesystems/cifs/ksmbd.rst
new file mode 100644
index 000000000000..a1326157d53f
--- /dev/null
+++ b/Documentation/filesystems/cifs/ksmbd.rst
@@ -0,0 +1,165 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==========================
+KSMBD - SMB3 Kernel Server
+==========================
+
+KSMBD is a linux kernel server which implements SMB3 protocol in kernel space
+for sharing files over network.
+
+KSMBD architecture
+==================
+
+The subset of performance related operations belong in kernelspace and
+the other subset which belong to operations which are not really related with
+performance in userspace. So, DCE/RPC management that has historically resulted
+into number of buffer overflow issues and dangerous security bugs and user
+account management are implemented in user space as ksmbd.mountd.
+File operations that are related with performance (open/read/write/close etc.)
+in kernel space (ksmbd). This also allows for easier integration with VFS
+interface for all file operations.
+
+ksmbd (kernel daemon)
+---------------------
+
+When the server daemon is started, It starts up a forker thread
+(ksmbd/interface name) at initialization time and open a dedicated port 445
+for listening to SMB requests. Whenever new clients make request, Forker
+thread will accept the client connection and fork a new thread for dedicated
+communication channel between the client and the server. It allows for parallel
+processing of SMB requests(commands) from clients as well as allowing for new
+clients to make new connections. Each instance is named ksmbd/1~n(port number)
+to indicate connected clients. Depending on the SMB request types, each new
+thread can decide to pass through the commands to the user space (ksmbd.mountd),
+currently DCE/RPC commands are identified to be handled through the user space.
+To further utilize the linux kernel, it has been chosen to process the commands
+as workitems and to be executed in the handlers of the ksmbd-io kworker threads.
+It allows for multiplexing of the handlers as the kernel take care of initiating
+extra worker threads if the load is increased and vice versa, if the load is
+decreased it destroys the extra worker threads. So, after connection is
+established with client. Dedicated ksmbd/1..n(port number) takes complete
+ownership of receiving/parsing of SMB commands. Each received command is worked
+in parallel i.e., There can be multiple clients commands which are worked in
+parallel. After receiving each command a separated kernel workitem is prepared
+for each command which is further queued to be handled by ksmbd-io kworkers.
+So, each SMB workitem is queued to the kworkers. This allows the benefit of load
+sharing to be managed optimally by the default kernel and optimizing client
+performance by handling client commands in parallel.
+
+ksmbd.mountd (user space daemon)
+--------------------------------
+
+ksmbd.mountd is userspace process to, transfer user account and password that
+are registered using ksmbd.adduser(part of utils for user space). Further it
+allows sharing information parameters that parsed from smb.conf to ksmbd in
+kernel. For the execution part it has a daemon which is continuously running
+and connected to the kernel interface using netlink socket, it waits for the
+requests(dcerpc and share/user info). It handles RPC calls (at a minimum few
+dozen) that are most important for file server from NetShareEnum and
+NetServerGetInfo. Complete DCE/RPC response is prepared from the user space
+and passed over to the associated kernel thread for the client.
+
+
+KSMBD Feature Status
+====================
+
+============================== =================================================
+Feature name Status
+============================== =================================================
+Dialects Supported. SMB2.1 SMB3.0, SMB3.1.1 dialects
+ (intentionally excludes security vulnerable SMB1
+ dialect).
+Auto Negotiation Supported.
+Compound Request Supported.
+Oplock Cache Mechanism Supported.
+SMB2 leases(v1 lease) Supported.
+Directory leases(v2 lease) Planned for future.
+Multi-credits Supported.
+NTLM/NTLMv2 Supported.
+HMAC-SHA256 Signing Supported.
+Secure negotiate Supported.
+Signing Update Supported.
+Pre-authentication integrity Supported.
+SMB3 encryption(CCM, GCM) Supported. (CCM and GCM128 supported, GCM256 in
+ progress)
+SMB direct(RDMA) Partially Supported. SMB3 Multi-channel is
+ required to connect to Windows client.
+SMB3 Multi-channel Partially Supported. Planned to implement
+ replay/retry mechanisms for future.
+SMB3.1.1 POSIX extension Supported.
+ACLs Partially Supported. only DACLs available, SACLs
+ (auditing) is planned for the future. For
+ ownership (SIDs) ksmbd generates random subauth
+ values(then store it to disk) and use uid/gid
+ get from inode as RID for local domain SID.
+ The current acl implementation is limited to
+ standalone server, not a domain member.
+ Integration with Samba tools is being worked on
+ to allow future support for running as a domain
+ member.
+Kerberos Supported.
+Durable handle v1,v2 Planned for future.
+Persistent handle Planned for future.
+SMB2 notify Planned for future.
+Sparse file support Supported.
+DCE/RPC support Partially Supported. a few calls(NetShareEnumAll,
+ NetServerGetInfo, SAMR, LSARPC) that are needed
+ for file server handled via netlink interface
+ from ksmbd.mountd. Additional integration with
+ Samba tools and libraries via upcall is being
+ investigated to allow support for additional
+ DCE/RPC management calls (and future support
+ for Witness protocol e.g.)
+ksmbd/nfsd interoperability Planned for future. The features that ksmbd
+ support are Leases, Notify, ACLs and Share modes.
+============================== =================================================
+
+
+How to run
+==========
+
+1. Download ksmbd-tools and compile them.
+ - https://github.com/cifsd-team/ksmbd-tools
+
+2. Create user/password for SMB share.
+
+ # mkdir /etc/ksmbd/
+ # ksmbd.adduser -a <Enter USERNAME for SMB share access>
+
+3. Create /etc/ksmbd/smb.conf file, add SMB share in smb.conf file
+ - Refer smb.conf.example and
+ https://github.com/cifsd-team/ksmbd-tools/blob/master/Documentation/configuration.txt
+
+4. Insert ksmbd.ko module
+
+ # insmod ksmbd.ko
+
+5. Start ksmbd user space daemon
+ # ksmbd.mountd
+
+6. Access share from Windows or Linux using CIFS
+
+Shutdown KSMBD
+==============
+
+1. kill user and kernel space daemon
+ # sudo ksmbd.control -s
+
+How to turn debug print on
+==========================
+
+Each layer
+/sys/class/ksmbd-control/debug
+
+1. Enable all component prints
+ # sudo ksmbd.control -d "all"
+
+2. Enable one of components(smb, auth, vfs, oplock, ipc, conn, rdma)
+ # sudo ksmbd.control -d "smb"
+
+3. Show what prints are enable.
+ # cat/sys/class/ksmbd-control/debug
+ [smb] auth vfs oplock ipc conn [rdma]
+
+4. Disable prints:
+ If you try the selected component once more, It is disabled without brackets.
diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst
index 246af51b277a..7e1f44c14e6f 100644
--- a/Documentation/filesystems/index.rst
+++ b/Documentation/filesystems/index.rst
@@ -72,7 +72,7 @@ Documentation for filesystem implementations.
befs
bfs
btrfs
- cifs/cifsroot
+ cifs/index
ceph
coda
configfs
diff --git a/MAINTAINERS b/MAINTAINERS
index 142f3a3de93e..424ad2c5e418 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4620,7 +4620,7 @@ F: include/linux/clk/
F: include/linux/of_clk.h
X: drivers/clk/clkdev.c
-COMMON INTERNET FILE SYSTEM (CIFS)
+COMMON INTERNET FILE SYSTEM CLIENT (CIFS)
M: Steve French <sfrench@samba.org>
L: linux-cifs@vger.kernel.org
L: samba-technical@lists.samba.org (moderated for non-subscribers)
@@ -10113,6 +10113,17 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git
F: Documentation/dev-tools/kselftest*
F: tools/testing/selftests/
+KERNEL SMB3 SERVER (KSMBD)
+M: Namjae Jeon <linkinjeon@kernel.org>
+M: Sergey Senozhatsky <senozhatsky@chromium.org>
+M: Steve French <sfrench@samba.org>
+M: Hyunchul Lee <hyc.lee@gmail.com>
+L: linux-cifs@vger.kernel.org
+S: Maintained
+T: git git://git.samba.org/ksmbd.git
+F: fs/cifs_common/
+F: fs/ksmbd/
+
KERNEL UNIT TESTING FRAMEWORK (KUnit)
M: Brendan Higgins <brendanhiggins@google.com>
L: linux-kselftest@vger.kernel.org
diff --git a/fs/Kconfig b/fs/Kconfig
index 949128bf86c9..8d6ac56d6fcd 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -349,6 +349,7 @@ config NFS_V4_2_SSC_HELPER
source "net/sunrpc/Kconfig"
source "fs/ceph/Kconfig"
source "fs/cifs/Kconfig"
+source "fs/ksmbd/Kconfig"
source "fs/coda/Kconfig"
source "fs/afs/Kconfig"
source "fs/9p/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index f98f3e691c37..b7e65c39f98d 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_NLS) += nls/
obj-$(CONFIG_UNICODE) += unicode/
obj-$(CONFIG_SYSV_FS) += sysv/
obj-$(CONFIG_CIFS) += cifs/
+obj-$(CONFIG_SMB_SERVER) += ksmbd/
obj-$(CONFIG_HPFS_FS) += hpfs/
obj-$(CONFIG_NTFS_FS) += ntfs/
obj-$(CONFIG_UFS_FS) += ufs/
diff --git a/fs/ksmbd/Kconfig b/fs/ksmbd/Kconfig
new file mode 100644
index 000000000000..b83cbd756ae5
--- /dev/null
+++ b/fs/ksmbd/Kconfig
@@ -0,0 +1,68 @@
+config SMB_SERVER
+ tristate "SMB3 server support (EXPERIMENTAL)"
+ depends on INET
+ depends on MULTIUSER
+ depends on FILE_LOCKING
+ select NLS
+ select NLS_UTF8
+ select CRYPTO
+ select CRYPTO_MD4
+ select CRYPTO_MD5
+ select CRYPTO_HMAC
+ select CRYPTO_ECB
+ select CRYPTO_LIB_DES
+ select CRYPTO_SHA256
+ select CRYPTO_CMAC
+ select CRYPTO_SHA512
+ select CRYPTO_AEAD2
+ select CRYPTO_CCM
+ select CRYPTO_GCM
+ select ASN1
+ select OID_REGISTRY
+ default n
+ help
+ Choose Y here if you want to allow SMB3 compliant clients
+ to access files residing on this system using SMB3 protocol.
+ To compile the SMB3 server support as a module,
+ choose M here: the module will be called ksmbd.
+
+ You may choose to use a samba server instead, in which
+ case you can choose N here.
+
+ You also need to install user space programs which can be found
+ in ksmbd-tools, available from
+ https://github.com/cifsd-team/ksmbd-tools.
+ More detail about how to run the ksmbd kernel server is
+ available via README file
+ (https://github.com/cifsd-team/ksmbd-tools/blob/master/README).
+
+ ksmbd kernel server includes support for auto-negotiation,
+ Secure negotiate, Pre-authentication integrity, oplock/lease,
+ compound requests, multi-credit, packet signing, RDMA(smbdirect),
+ smb3 encryption, copy-offload, secure per-user session
+ establishment via NTLM or NTLMv2.
+
+config SMB_SERVER_SMBDIRECT
+ bool "Support for SMB Direct protocol"
+ depends on SMB_SERVER=m && INFINIBAND && INFINIBAND_ADDR_TRANS || SMB_SERVER=y && INFINIBAND=y && INFINIBAND_ADDR_TRANS=y
+ select SG_POOL
+ default n
+
+ help
+ Enables SMB Direct support for SMB 3.0, 3.02 and 3.1.1.
+
+ SMB Direct allows transferring SMB packets over RDMA. If unsure,
+ say N.
+
+config SMB_SERVER_CHECK_CAP_NET_ADMIN
+ bool "Enable check network administration capability"
+ depends on SMB_SERVER
+ default y
+
+ help
+ Prevent unprivileged processes to start the ksmbd kernel server.
+
+config SMB_SERVER_KERBEROS5
+ bool "Support for Kerberos 5"
+ depends on SMB_SERVER
+ default n
diff --git a/fs/ksmbd/Makefile b/fs/ksmbd/Makefile
new file mode 100644
index 000000000000..7d6337a7dee4
--- /dev/null
+++ b/fs/ksmbd/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for Linux SMB3 kernel server
+#
+obj-$(CONFIG_SMB_SERVER) += ksmbd.o
+
+ksmbd-y := unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
+ misc.o oplock.o connection.o ksmbd_work.o crypto_ctx.o \
+ mgmt/ksmbd_ida.o mgmt/user_config.o mgmt/share_config.o \
+ mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
+ transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
+ smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
+ ksmbd_spnego_negtokentarg.asn1.o asn1.o
+
+$(obj)/asn1.o: $(obj)/ksmbd_spnego_negtokeninit.asn1.h $(obj)/ksmbd_spnego_negtokentarg.asn1.h
+
+$(obj)/ksmbd_spnego_negtokeninit.asn1.o: $(obj)/ksmbd_spnego_negtokeninit.asn1.c $(obj)/ksmbd_spnego_negtokeninit.asn1.h
+$(obj)/ksmbd_spnego_negtokentarg.asn1.o: $(obj)/ksmbd_spnego_negtokentarg.asn1.c $(obj)/ksmbd_spnego_negtokentarg.asn1.h
+
+ksmbd-$(CONFIG_SMB_SERVER_SMBDIRECT) += transport_rdma.o
diff --git a/fs/ksmbd/asn1.c b/fs/ksmbd/asn1.c
new file mode 100644
index 000000000000..b014f4638610
--- /dev/null
+++ b/fs/ksmbd/asn1.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The ASB.1/BER parsing code is derived from ip_nat_snmp_basic.c which was in
+ * turn derived from the gxsnmp package by Gregory McLean & Jochen Friedrich
+ *
+ * Copyright (c) 2000 RP Internet (www.rpi.net.au).
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/oid_registry.h>
+
+#include "glob.h"
+
+#include "asn1.h"
+#include "connection.h"
+#include "auth.h"
+#include "ksmbd_spnego_negtokeninit.asn1.h"
+#include "ksmbd_spnego_negtokentarg.asn1.h"
+
+#define SPNEGO_OID_LEN 7
+#define NTLMSSP_OID_LEN 10
+#define KRB5_OID_LEN 7
+#define KRB5U2U_OID_LEN 8
+#define MSKRB5_OID_LEN 7
+static unsigned long SPNEGO_OID[7] = { 1, 3, 6, 1, 5, 5, 2 };
+static unsigned long NTLMSSP_OID[10] = { 1, 3, 6, 1, 4, 1, 311, 2, 2, 10 };
+static unsigned long KRB5_OID[7] = { 1, 2, 840, 113554, 1, 2, 2 };
+static unsigned long KRB5U2U_OID[8] = { 1, 2, 840, 113554, 1, 2, 2, 3 };
+static unsigned long MSKRB5_OID[7] = { 1, 2, 840, 48018, 1, 2, 2 };
+
+static char NTLMSSP_OID_STR[NTLMSSP_OID_LEN] = { 0x2b, 0x06, 0x01, 0x04, 0x01,
+ 0x82, 0x37, 0x02, 0x02, 0x0a };
+
+static bool
+asn1_subid_decode(const unsigned char **begin, const unsigned char *end,
+ unsigned long *subid)
+{
+ const unsigned char *ptr = *begin;
+ unsigned char ch;
+
+ *subid = 0;
+
+ do {
+ if (ptr >= end)
+ return false;
+
+ ch = *ptr++;
+ *subid <<= 7;
+ *subid |= ch & 0x7F;
+ } while ((ch & 0x80) == 0x80);
+
+ *begin = ptr;
+ return true;
+}
+
+static bool asn1_oid_decode(const unsigned char *value, size_t vlen,
+ unsigned long **oid, size_t *oidlen)
+{
+ const unsigned char *iptr = value, *end = value + vlen;
+ unsigned long *optr;
+ unsigned long subid;
+
+ vlen += 1;
+ if (vlen < 2 || vlen > UINT_MAX / sizeof(unsigned long))
+ goto fail_nullify;
+
+ *oid = kmalloc(vlen * sizeof(unsigned long), GFP_KERNEL);
+ if (!*oid)
+ return false;
+
+ optr = *oid;
+
+ if (!asn1_subid_decode(&iptr, end, &subid))
+ goto fail;
+
+ if (subid < 40) {
+ optr[0] = 0;
+ optr[1] = subid;
+ } else if (subid < 80) {
+ optr[0] = 1;
+ optr[1] = subid - 40;
+ } else {
+ optr[0] = 2;
+ optr[1] = subid - 80;
+ }
+
+ *oidlen = 2;
+ optr += 2;
+
+ while (iptr < end) {
+ if (++(*oidlen) > vlen)
+ goto fail;
+
+ if (!asn1_subid_decode(&iptr, end, optr++))
+ goto fail;
+ }
+ return true;
+
+fail:
+ kfree(*oid);
+fail_nullify:
+ *oid = NULL;
+ return false;
+}
+
+static bool oid_eq(unsigned long *oid1, unsigned int oid1len,
+ unsigned long *oid2, unsigned int oid2len)
+{
+ if (oid1len != oid2len)
+ return false;
+
+ return memcmp(oid1, oid2, oid1len) == 0;
+}
+
+int
+ksmbd_decode_negTokenInit(unsigned char *security_blob, int length,
+ struct ksmbd_conn *conn)
+{
+ return asn1_ber_decoder(&ksmbd_spnego_negtokeninit_decoder, conn,
+ security_blob, length);
+}
+
+int
+ksmbd_decode_negTokenTarg(unsigned char *security_blob, int length,
+ struct ksmbd_conn *conn)
+{
+ return asn1_ber_decoder(&ksmbd_spnego_negtokentarg_decoder, conn,
+ security_blob, length);
+}
+
+static int compute_asn_hdr_len_bytes(int len)
+{
+ if (len > 0xFFFFFF)
+ return 4;
+ else if (len > 0xFFFF)
+ return 3;
+ else if (len > 0xFF)
+ return 2;
+ else if (len > 0x7F)
+ return 1;
+ else
+ return 0;
+}
+
+static void encode_asn_tag(char *buf, unsigned int *ofs, char tag, char seq,
+ int length)
+{
+ int i;
+ int index = *ofs;
+ char hdr_len = compute_asn_hdr_len_bytes(length);
+ int len = length + 2 + hdr_len;
+
+ /* insert tag */
+ buf[index++] = tag;
+
+ if (!hdr_len) {
+ buf[index++] = len;
+ } else {
+ buf[index++] = 0x80 | hdr_len;
+ for (i = hdr_len - 1; i >= 0; i--)
+ buf[index++] = (len >> (i * 8)) & 0xFF;
+ }
+
+ /* insert seq */
+ len = len - (index - *ofs);
+ buf[index++] = seq;
+
+ if (!hdr_len) {
+ buf[index++] = len;
+ } else {
+ buf[index++] = 0x80 | hdr_len;
+ for (i = hdr_len - 1; i >= 0; i--)
+ buf[index++] = (len >> (i * 8)) & 0xFF;
+ }
+
+ *ofs += (index - *ofs);
+}
+
+int build_spnego_ntlmssp_neg_blob(unsigned char **pbuffer, u16 *buflen,
+ char *ntlm_blob, int ntlm_blob_len)
+{
+ char *buf;
+ unsigned int ofs = 0;
+ int neg_result_len = 4 + compute_asn_hdr_len_bytes(1) * 2 + 1;
+ int oid_len = 4 + compute_asn_hdr_len_bytes(NTLMSSP_OID_LEN) * 2 +
+ NTLMSSP_OID_LEN;
+ int ntlmssp_len = 4 + compute_asn_hdr_len_bytes(ntlm_blob_len) * 2 +
+ ntlm_blob_len;
+ int total_len = 4 + compute_asn_hdr_len_bytes(neg_result_len +
+ oid_len + ntlmssp_len) * 2 +
+ neg_result_len + oid_len + ntlmssp_len;
+
+ buf = kmalloc(total_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* insert main gss header */
+ encode_asn_tag(buf, &ofs, 0xa1, 0x30, neg_result_len + oid_len +
+ ntlmssp_len);
+
+ /* insert neg result */
+ encode_asn_tag(buf, &ofs, 0xa0, 0x0a, 1);
+ buf[ofs++] = 1;
+
+ /* insert oid */
+ encode_asn_tag(buf, &ofs, 0xa1, 0x06, NTLMSSP_OID_LEN);
+ memcpy(buf + ofs, NTLMSSP_OID_STR, NTLMSSP_OID_LEN);
+ ofs += NTLMSSP_OID_LEN;
+
+ /* insert response token - ntlmssp blob */
+ encode_asn_tag(buf, &ofs, 0xa2, 0x04, ntlm_blob_len);
+ memcpy(buf + ofs, ntlm_blob, ntlm_blob_len);
+ ofs += ntlm_blob_len;
+
+ *pbuffer = buf;
+ *buflen = total_len;
+ return 0;
+}
+
+int build_spnego_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen,
+ int neg_result)
+{
+ char *buf;
+ unsigned int ofs = 0;
+ int neg_result_len = 4 + compute_asn_hdr_len_bytes(1) * 2 + 1;
+ int total_len = 4 + compute_asn_hdr_len_bytes(neg_result_len) * 2 +
+ neg_result_len;
+
+ buf = kmalloc(total_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* insert main gss header */
+ encode_asn_tag(buf, &ofs, 0xa1, 0x30, neg_result_len);
+
+ /* insert neg result */
+ encode_asn_tag(buf, &ofs, 0xa0, 0x0a, 1);
+ if (neg_result)
+ buf[ofs++] = 2;
+ else
+ buf[ofs++] = 0;
+
+ *pbuffer = buf;
+ *buflen = total_len;
+ return 0;
+}
+
+int ksmbd_gssapi_this_mech(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ unsigned long *oid;
+ size_t oidlen;
+ int err = 0;
+
+ if (!asn1_oid_decode(value, vlen, &oid, &oidlen)) {
+ err = -EBADMSG;
+ goto out;
+ }
+
+ if (!oid_eq(oid, oidlen, SPNEGO_OID, SPNEGO_OID_LEN))
+ err = -EBADMSG;
+ kfree(oid);
+out:
+ if (err) {
+ char buf[50];
+
+ sprint_oid(value, vlen, buf, sizeof(buf));
+ ksmbd_debug(AUTH, "Unexpected OID: %s\n", buf);
+ }
+ return err;
+}
+
+int ksmbd_neg_token_init_mech_type(void *context, size_t hdrlen,
+ unsigned char tag, const void *value,
+ size_t vlen)
+{
+ struct ksmbd_conn *conn = context;
+ unsigned long *oid;
+ size_t oidlen;
+ int mech_type;
+ char buf[50];
+
+ if (!asn1_oid_decode(value, vlen, &oid, &oidlen))
+ goto fail;
+
+ if (oid_eq(oid, oidlen, NTLMSSP_OID, NTLMSSP_OID_LEN))
+ mech_type = KSMBD_AUTH_NTLMSSP;
+ else if (oid_eq(oid, oidlen, MSKRB5_OID, MSKRB5_OID_LEN))
+ mech_type = KSMBD_AUTH_MSKRB5;
+ else if (oid_eq(oid, oidlen, KRB5_OID, KRB5_OID_LEN))
+ mech_type = KSMBD_AUTH_KRB5;
+ else if (oid_eq(oid, oidlen, KRB5U2U_OID, KRB5U2U_OID_LEN))
+ mech_type = KSMBD_AUTH_KRB5U2U;
+ else
+ goto fail;
+
+ conn->auth_mechs |= mech_type;
+ if (conn->preferred_auth_mech == 0)
+ conn->preferred_auth_mech = mech_type;
+
+ kfree(oid);
+ return 0;
+
+fail:
+ kfree(oid);
+ sprint_oid(value, vlen, buf, sizeof(buf));
+ ksmbd_debug(AUTH, "Unexpected OID: %s\n", buf);
+ return -EBADMSG;
+}
+
+int ksmbd_neg_token_init_mech_token(void *context, size_t hdrlen,
+ unsigned char tag, const void *value,
+ size_t vlen)
+{
+ struct ksmbd_conn *conn = context;
+
+ conn->mechToken = kmalloc(vlen + 1, GFP_KERNEL);
+ if (!conn->mechToken)
+ return -ENOMEM;
+
+ memcpy(conn->mechToken, value, vlen);
+ conn->mechToken[vlen] = '\0';
+ return 0;
+}
+
+int ksmbd_neg_token_targ_resp_token(void *context, size_t hdrlen,
+ unsigned char tag, const void *value,
+ size_t vlen)
+{
+ struct ksmbd_conn *conn = context;
+
+ conn->mechToken = kmalloc(vlen + 1, GFP_KERNEL);
+ if (!conn->mechToken)
+ return -ENOMEM;
+
+ memcpy(conn->mechToken, value, vlen);
+ conn->mechToken[vlen] = '\0';
+ return 0;
+}
diff --git a/fs/ksmbd/asn1.h b/fs/ksmbd/asn1.h
new file mode 100644
index 000000000000..ce105f4ce305
--- /dev/null
+++ b/fs/ksmbd/asn1.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * The ASB.1/BER parsing code is derived from ip_nat_snmp_basic.c which was in
+ * turn derived from the gxsnmp package by Gregory McLean & Jochen Friedrich
+ *
+ * Copyright (c) 2000 RP Internet (www.rpi.net.au).
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __ASN1_H__
+#define __ASN1_H__
+
+int ksmbd_decode_negTokenInit(unsigned char *security_blob, int length,
+ struct ksmbd_conn *conn);
+int ksmbd_decode_negTokenTarg(unsigned char *security_blob, int length,
+ struct ksmbd_conn *conn);
+int build_spnego_ntlmssp_neg_blob(unsigned char **pbuffer, u16 *buflen,
+ char *ntlm_blob, int ntlm_blob_len);
+int build_spnego_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen,
+ int neg_result);
+#endif /* __ASN1_H__ */
diff --git a/fs/ksmbd/auth.c b/fs/ksmbd/auth.c
new file mode 100644
index 000000000000..de36f12070bf
--- /dev/null
+++ b/fs/ksmbd/auth.c
@@ -0,0 +1,1364 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/backing-dev.h>
+#include <linux/writeback.h>
+#include <linux/uio.h>
+#include <linux/xattr.h>
+#include <crypto/hash.h>
+#include <crypto/aead.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+
+#include "auth.h"
+#include "glob.h"
+
+#include <linux/fips.h>
+#include <crypto/des.h>
+
+#include "server.h"
+#include "smb_common.h"
+#include "connection.h"
+#include "mgmt/user_session.h"
+#include "mgmt/user_config.h"
+#include "crypto_ctx.h"
+#include "transport_ipc.h"
+
+/*
+ * Fixed format data defining GSS header and fixed string
+ * "not_defined_in_RFC4178@please_ignore".
+ * So sec blob data in neg phase could be generated statically.
+ */
+static char NEGOTIATE_GSS_HEADER[AUTH_GSS_LENGTH] = {
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+ 0x60, 0x5e, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x02, 0xa0, 0x54, 0x30, 0x52, 0xa0, 0x24,
+ 0x30, 0x22, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02,
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82,
+ 0x37, 0x02, 0x02, 0x0a, 0xa3, 0x2a, 0x30, 0x28,
+ 0xa0, 0x26, 0x1b, 0x24, 0x6e, 0x6f, 0x74, 0x5f,
+ 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x5f,
+ 0x69, 0x6e, 0x5f, 0x52, 0x46, 0x43, 0x34, 0x31,
+ 0x37, 0x38, 0x40, 0x70, 0x6c, 0x65, 0x61, 0x73,
+ 0x65, 0x5f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65
+#else
+ 0x60, 0x48, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x02, 0xa0, 0x3e, 0x30, 0x3c, 0xa0, 0x0e,
+ 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a, 0xa3, 0x2a,
+ 0x30, 0x28, 0xa0, 0x26, 0x1b, 0x24, 0x6e, 0x6f,
+ 0x74, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65,
+ 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x52, 0x46, 0x43,
+ 0x34, 0x31, 0x37, 0x38, 0x40, 0x70, 0x6c, 0x65,
+ 0x61, 0x73, 0x65, 0x5f, 0x69, 0x67, 0x6e, 0x6f,
+ 0x72, 0x65
+#endif
+};
+
+void ksmbd_copy_gss_neg_header(void *buf)
+{
+ memcpy(buf, NEGOTIATE_GSS_HEADER, AUTH_GSS_LENGTH);
+}
+
+static void
+str_to_key(unsigned char *str, unsigned char *key)
+{
+ int i;
+
+ key[0] = str[0] >> 1;
+ key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2);
+ key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3);
+ key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4);
+ key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5);
+ key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6);
+ key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7);
+ key[7] = str[6] & 0x7F;
+ for (i = 0; i < 8; i++)
+ key[i] = (key[i] << 1);
+}
+
+static int
+smbhash(unsigned char *out, const unsigned char *in, unsigned char *key)
+{
+ unsigned char key2[8];
+ struct des_ctx ctx;
+
+ if (fips_enabled) {
+ ksmbd_debug(AUTH, "FIPS compliance enabled: DES not permitted\n");
+ return -ENOENT;
+ }
+
+ str_to_key(key, key2);
+ des_expand_key(&ctx, key2, DES_KEY_SIZE);
+ des_encrypt(&ctx, out, in);
+ memzero_explicit(&ctx, sizeof(ctx));
+ return 0;
+}
+
+static int ksmbd_enc_p24(unsigned char *p21, const unsigned char *c8, unsigned char *p24)
+{
+ int rc;
+
+ rc = smbhash(p24, c8, p21);
+ if (rc)
+ return rc;
+ rc = smbhash(p24 + 8, c8, p21 + 7);
+ if (rc)
+ return rc;
+ return smbhash(p24 + 16, c8, p21 + 14);
+}
+
+/* produce a md4 message digest from data of length n bytes */
+static int ksmbd_enc_md4(unsigned char *md4_hash, unsigned char *link_str,
+ int link_len)
+{
+ int rc;
+ struct ksmbd_crypto_ctx *ctx;
+
+ ctx = ksmbd_crypto_ctx_find_md4();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "Crypto md4 allocation error\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_init(CRYPTO_MD4(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not init md4 shash\n");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_MD4(ctx), link_str, link_len);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not update with link_str\n");
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_MD4(ctx), md4_hash);
+ if (rc)
+ ksmbd_debug(AUTH, "Could not generate md4 hash\n");
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+static int ksmbd_enc_update_sess_key(unsigned char *md5_hash, char *nonce,
+ char *server_challenge, int len)
+{
+ int rc;
+ struct ksmbd_crypto_ctx *ctx;
+
+ ctx = ksmbd_crypto_ctx_find_md5();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "Crypto md5 allocation error\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_init(CRYPTO_MD5(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not init md5 shash\n");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_MD5(ctx), server_challenge, len);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not update with challenge\n");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_MD5(ctx), nonce, len);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not update with nonce\n");
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_MD5(ctx), md5_hash);
+ if (rc)
+ ksmbd_debug(AUTH, "Could not generate md5 hash\n");
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+/**
+ * ksmbd_gen_sess_key() - function to generate session key
+ * @sess: session of connection
+ * @hash: source hash value to be used for find session key
+ * @hmac: source hmac value to be used for finding session key
+ *
+ */
+static int ksmbd_gen_sess_key(struct ksmbd_session *sess, char *hash,
+ char *hmac)
+{
+ struct ksmbd_crypto_ctx *ctx;
+ int rc;
+
+ ctx = ksmbd_crypto_ctx_find_hmacmd5();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx),
+ hash,
+ CIFS_HMAC_MD5_HASH_SIZE);
+ if (rc) {
+ ksmbd_debug(AUTH, "hmacmd5 set key fail error %d\n", rc);
+ goto out;
+ }
+
+ rc = crypto_shash_init(CRYPTO_HMACMD5(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "could not init hmacmd5 error %d\n", rc);
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_HMACMD5(ctx),
+ hmac,
+ SMB2_NTLMV2_SESSKEY_SIZE);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not update with response error %d\n", rc);
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_HMACMD5(ctx), sess->sess_key);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate hmacmd5 hash error %d\n", rc);
+ goto out;
+ }
+
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+static int calc_ntlmv2_hash(struct ksmbd_session *sess, char *ntlmv2_hash,
+ char *dname)
+{
+ int ret, len, conv_len;
+ wchar_t *domain = NULL;
+ __le16 *uniname = NULL;
+ struct ksmbd_crypto_ctx *ctx;
+
+ ctx = ksmbd_crypto_ctx_find_hmacmd5();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "can't generate ntlmv2 hash\n");
+ return -ENOMEM;
+ }
+
+ ret = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx),
+ user_passkey(sess->user),
+ CIFS_ENCPWD_SIZE);
+ if (ret) {
+ ksmbd_debug(AUTH, "Could not set NT Hash as a key\n");
+ goto out;
+ }
+
+ ret = crypto_shash_init(CRYPTO_HMACMD5(ctx));
+ if (ret) {
+ ksmbd_debug(AUTH, "could not init hmacmd5\n");
+ goto out;
+ }
+
+ /* convert user_name to unicode */
+ len = strlen(user_name(sess->user));
+ uniname = kzalloc(2 + UNICODE_LEN(len), GFP_KERNEL);
+ if (!uniname) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ conv_len = smb_strtoUTF16(uniname, user_name(sess->user), len,
+ sess->conn->local_nls);
+ if (conv_len < 0 || conv_len > len) {
+ ret = -EINVAL;
+ goto out;
+ }
+ UniStrupr(uniname);
+
+ ret = crypto_shash_update(CRYPTO_HMACMD5(ctx),
+ (char *)uniname,
+ UNICODE_LEN(conv_len));
+ if (ret) {
+ ksmbd_debug(AUTH, "Could not update with user\n");
+ goto out;
+ }
+
+ /* Convert domain name or conn name to unicode and uppercase */
+ len = strlen(dname);
+ domain = kzalloc(2 + UNICODE_LEN(len), GFP_KERNEL);
+ if (!domain) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ conv_len = smb_strtoUTF16((__le16 *)domain, dname, len,
+ sess->conn->local_nls);
+ if (conv_len < 0 || conv_len > len) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = crypto_shash_update(CRYPTO_HMACMD5(ctx),
+ (char *)domain,
+ UNICODE_LEN(conv_len));
+ if (ret) {
+ ksmbd_debug(AUTH, "Could not update with domain\n");
+ goto out;
+ }
+
+ ret = crypto_shash_final(CRYPTO_HMACMD5(ctx), ntlmv2_hash);
+ if (ret)
+ ksmbd_debug(AUTH, "Could not generate md5 hash\n");
+out:
+ kfree(uniname);
+ kfree(domain);
+ ksmbd_release_crypto_ctx(ctx);
+ return ret;
+}
+
+/**
+ * ksmbd_auth_ntlm() - NTLM authentication handler
+ * @sess: session of connection
+ * @pw_buf: NTLM challenge response
+ * @passkey: user password
+ *
+ * Return: 0 on success, error number on error
+ */
+int ksmbd_auth_ntlm(struct ksmbd_session *sess, char *pw_buf)
+{
+ int rc;
+ unsigned char p21[21];
+ char key[CIFS_AUTH_RESP_SIZE];
+
+ memset(p21, '\0', 21);
+ memcpy(p21, user_passkey(sess->user), CIFS_NTHASH_SIZE);
+ rc = ksmbd_enc_p24(p21, sess->ntlmssp.cryptkey, key);
+ if (rc) {
+ pr_err("password processing failed\n");
+ return rc;
+ }
+
+ ksmbd_enc_md4(sess->sess_key, user_passkey(sess->user),
+ CIFS_SMB1_SESSKEY_SIZE);
+ memcpy(sess->sess_key + CIFS_SMB1_SESSKEY_SIZE, key,
+ CIFS_AUTH_RESP_SIZE);
+ sess->sequence_number = 1;
+
+ if (strncmp(pw_buf, key, CIFS_AUTH_RESP_SIZE) != 0) {
+ ksmbd_debug(AUTH, "ntlmv1 authentication failed\n");
+ return -EINVAL;
+ }
+
+ ksmbd_debug(AUTH, "ntlmv1 authentication pass\n");
+ return 0;
+}
+
+/**
+ * ksmbd_auth_ntlmv2() - NTLMv2 authentication handler
+ * @sess: session of connection
+ * @ntlmv2: NTLMv2 challenge response
+ * @blen: NTLMv2 blob length
+ * @domain_name: domain name
+ *
+ * Return: 0 on success, error number on error
+ */
+int ksmbd_auth_ntlmv2(struct ksmbd_session *sess, struct ntlmv2_resp *ntlmv2,
+ int blen, char *domain_name)
+{
+ char ntlmv2_hash[CIFS_ENCPWD_SIZE];
+ char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
+ struct ksmbd_crypto_ctx *ctx;
+ char *construct = NULL;
+ int rc, len;
+
+ ctx = ksmbd_crypto_ctx_find_hmacmd5();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n");
+ return -ENOMEM;
+ }
+
+ rc = calc_ntlmv2_hash(sess, ntlmv2_hash, domain_name);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not get v2 hash rc %d\n", rc);
+ goto out;
+ }
+
+ rc = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx),
+ ntlmv2_hash,
+ CIFS_HMAC_MD5_HASH_SIZE);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not set NTLMV2 Hash as a key\n");
+ goto out;
+ }
+
+ rc = crypto_shash_init(CRYPTO_HMACMD5(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not init hmacmd5\n");
+ goto out;
+ }
+
+ len = CIFS_CRYPTO_KEY_SIZE + blen;
+ construct = kzalloc(len, GFP_KERNEL);
+ if (!construct) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(construct, sess->ntlmssp.cryptkey, CIFS_CRYPTO_KEY_SIZE);
+ memcpy(construct + CIFS_CRYPTO_KEY_SIZE, &ntlmv2->blob_signature, blen);
+
+ rc = crypto_shash_update(CRYPTO_HMACMD5(ctx), construct, len);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not update with response\n");
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_HMACMD5(ctx), ntlmv2_rsp);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate md5 hash\n");
+ goto out;
+ }
+
+ rc = ksmbd_gen_sess_key(sess, ntlmv2_hash, ntlmv2_rsp);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate sess key\n");
+ goto out;
+ }
+
+ if (memcmp(ntlmv2->ntlmv2_hash, ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE) != 0)
+ rc = -EINVAL;
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ kfree(construct);
+ return rc;
+}
+
+/**
+ * __ksmbd_auth_ntlmv2() - NTLM2(extended security) authentication handler
+ * @sess: session of connection
+ * @client_nonce: client nonce from LM response.
+ * @ntlm_resp: ntlm response data from client.
+ *
+ * Return: 0 on success, error number on error
+ */
+static int __ksmbd_auth_ntlmv2(struct ksmbd_session *sess, char *client_nonce,
+ char *ntlm_resp)
+{
+ char sess_key[CIFS_SMB1_SESSKEY_SIZE] = {0};
+ int rc;
+ unsigned char p21[21];
+ char key[CIFS_AUTH_RESP_SIZE];
+
+ rc = ksmbd_enc_update_sess_key(sess_key,
+ client_nonce,
+ (char *)sess->ntlmssp.cryptkey, 8);
+ if (rc) {
+ pr_err("password processing failed\n");
+ goto out;
+ }
+
+ memset(p21, '\0', 21);
+ memcpy(p21, user_passkey(sess->user), CIFS_NTHASH_SIZE);
+ rc = ksmbd_enc_p24(p21, sess_key, key);
+ if (rc) {
+ pr_err("password processing failed\n");
+ goto out;
+ }
+
+ if (memcmp(ntlm_resp, key, CIFS_AUTH_RESP_SIZE) != 0)
+ rc = -EINVAL;
+out:
+ return rc;
+}
+
+/**
+ * ksmbd_decode_ntlmssp_auth_blob() - helper function to construct
+ * authenticate blob
+ * @authblob: authenticate blob source pointer
+ * @usr: user details
+ * @sess: session of connection
+ *
+ * Return: 0 on success, error number on error
+ */
+int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
+ int blob_len, struct ksmbd_session *sess)
+{
+ char *domain_name;
+ unsigned int lm_off, nt_off;
+ unsigned short nt_len;
+ int ret;
+
+ if (blob_len < sizeof(struct authenticate_message)) {
+ ksmbd_debug(AUTH, "negotiate blob len %d too small\n",
+ blob_len);
+ return -EINVAL;
+ }
+
+ if (memcmp(authblob->Signature, "NTLMSSP", 8)) {
+ ksmbd_debug(AUTH, "blob signature incorrect %s\n",
+ authblob->Signature);
+ return -EINVAL;
+ }
+
+ lm_off = le32_to_cpu(authblob->LmChallengeResponse.BufferOffset);
+ nt_off = le32_to_cpu(authblob->NtChallengeResponse.BufferOffset);
+ nt_len = le16_to_cpu(authblob->NtChallengeResponse.Length);
+
+ /* process NTLM authentication */
+ if (nt_len == CIFS_AUTH_RESP_SIZE) {
+ if (le32_to_cpu(authblob->NegotiateFlags) &
+ NTLMSSP_NEGOTIATE_EXTENDED_SEC)
+ return __ksmbd_auth_ntlmv2(sess, (char *)authblob +
+ lm_off, (char *)authblob + nt_off);
+ else
+ return ksmbd_auth_ntlm(sess, (char *)authblob +
+ nt_off);
+ }
+
+ /* TODO : use domain name that imported from configuration file */
+ domain_name = smb_strndup_from_utf16((const char *)authblob +
+ le32_to_cpu(authblob->DomainName.BufferOffset),
+ le16_to_cpu(authblob->DomainName.Length), true,
+ sess->conn->local_nls);
+ if (IS_ERR(domain_name))
+ return PTR_ERR(domain_name);
+
+ /* process NTLMv2 authentication */
+ ksmbd_debug(AUTH, "decode_ntlmssp_authenticate_blob dname%s\n",
+ domain_name);
+ ret = ksmbd_auth_ntlmv2(sess, (struct ntlmv2_resp *)((char *)authblob + nt_off),
+ nt_len - CIFS_ENCPWD_SIZE,
+ domain_name);
+ kfree(domain_name);
+ return ret;
+}
+
+/**
+ * ksmbd_decode_ntlmssp_neg_blob() - helper function to construct
+ * negotiate blob
+ * @negblob: negotiate blob source pointer
+ * @rsp: response header pointer to be updated
+ * @sess: session of connection
+ *
+ */
+int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob,
+ int blob_len, struct ksmbd_session *sess)
+{
+ if (blob_len < sizeof(struct negotiate_message)) {
+ ksmbd_debug(AUTH, "negotiate blob len %d too small\n",
+ blob_len);
+ return -EINVAL;
+ }
+
+ if (memcmp(negblob->Signature, "NTLMSSP", 8)) {
+ ksmbd_debug(AUTH, "blob signature incorrect %s\n",
+ negblob->Signature);
+ return -EINVAL;
+ }
+
+ sess->ntlmssp.client_flags = le32_to_cpu(negblob->NegotiateFlags);
+ return 0;
+}
+
+/**
+ * ksmbd_build_ntlmssp_challenge_blob() - helper function to construct
+ * challenge blob
+ * @chgblob: challenge blob source pointer to initialize
+ * @rsp: response header pointer to be updated
+ * @sess: session of connection
+ *
+ */
+unsigned int
+ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
+ struct ksmbd_session *sess)
+{
+ struct target_info *tinfo;
+ wchar_t *name;
+ __u8 *target_name;
+ unsigned int flags, blob_off, blob_len, type, target_info_len = 0;
+ int len, uni_len, conv_len;
+ int cflags = sess->ntlmssp.client_flags;
+
+ memcpy(chgblob->Signature, NTLMSSP_SIGNATURE, 8);
+ chgblob->MessageType = NtLmChallenge;
+
+ flags = NTLMSSP_NEGOTIATE_UNICODE |
+ NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_TARGET_TYPE_SERVER |
+ NTLMSSP_NEGOTIATE_TARGET_INFO;
+
+ if (cflags & NTLMSSP_NEGOTIATE_SIGN) {
+ flags |= NTLMSSP_NEGOTIATE_SIGN;
+ flags |= cflags & (NTLMSSP_NEGOTIATE_128 |
+ NTLMSSP_NEGOTIATE_56);
+ }
+
+ if (cflags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
+ flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+
+ if (cflags & NTLMSSP_REQUEST_TARGET)
+ flags |= NTLMSSP_REQUEST_TARGET;
+
+ if (sess->conn->use_spnego &&
+ (cflags & NTLMSSP_NEGOTIATE_EXTENDED_SEC))
+ flags |= NTLMSSP_NEGOTIATE_EXTENDED_SEC;
+
+ chgblob->NegotiateFlags = cpu_to_le32(flags);
+ len = strlen(ksmbd_netbios_name());
+ name = kmalloc(2 + UNICODE_LEN(len), GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+
+ conv_len = smb_strtoUTF16((__le16 *)name, ksmbd_netbios_name(), len,
+ sess->conn->local_nls);
+ if (conv_len < 0 || conv_len > len) {
+ kfree(name);
+ return -EINVAL;
+ }
+
+ uni_len = UNICODE_LEN(conv_len);
+
+ blob_off = sizeof(struct challenge_message);
+ blob_len = blob_off + uni_len;
+
+ chgblob->TargetName.Length = cpu_to_le16(uni_len);
+ chgblob->TargetName.MaximumLength = cpu_to_le16(uni_len);
+ chgblob->TargetName.BufferOffset = cpu_to_le32(blob_off);
+
+ /* Initialize random conn challenge */
+ get_random_bytes(sess->ntlmssp.cryptkey, sizeof(__u64));
+ memcpy(chgblob->Challenge, sess->ntlmssp.cryptkey,
+ CIFS_CRYPTO_KEY_SIZE);
+
+ /* Add Target Information to security buffer */
+ chgblob->TargetInfoArray.BufferOffset = cpu_to_le32(blob_len);
+
+ target_name = (__u8 *)chgblob + blob_off;
+ memcpy(target_name, name, uni_len);
+ tinfo = (struct target_info *)(target_name + uni_len);
+
+ chgblob->TargetInfoArray.Length = 0;
+ /* Add target info list for NetBIOS/DNS settings */
+ for (type = NTLMSSP_AV_NB_COMPUTER_NAME;
+ type <= NTLMSSP_AV_DNS_DOMAIN_NAME; type++) {
+ tinfo->Type = cpu_to_le16(type);
+ tinfo->Length = cpu_to_le16(uni_len);
+ memcpy(tinfo->Content, name, uni_len);
+ tinfo = (struct target_info *)((char *)tinfo + 4 + uni_len);
+ target_info_len += 4 + uni_len;
+ }
+
+ /* Add terminator subblock */
+ tinfo->Type = 0;
+ tinfo->Length = 0;
+ target_info_len += 4;
+
+ chgblob->TargetInfoArray.Length = cpu_to_le16(target_info_len);
+ chgblob->TargetInfoArray.MaximumLength = cpu_to_le16(target_info_len);
+ blob_len += target_info_len;
+ kfree(name);
+ ksmbd_debug(AUTH, "NTLMSSP SecurityBufferLength %d\n", blob_len);
+ return blob_len;
+}
+
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
+ int in_len, char *out_blob, int *out_len)
+{
+ struct ksmbd_spnego_authen_response *resp;
+ struct ksmbd_user *user = NULL;
+ int retval;
+
+ resp = ksmbd_ipc_spnego_authen_request(in_blob, in_len);
+ if (!resp) {
+ ksmbd_debug(AUTH, "SPNEGO_AUTHEN_REQUEST failure\n");
+ return -EINVAL;
+ }
+
+ if (!(resp->login_response.status & KSMBD_USER_FLAG_OK)) {
+ ksmbd_debug(AUTH, "krb5 authentication failure\n");
+ retval = -EPERM;
+ goto out;
+ }
+
+ if (*out_len <= resp->spnego_blob_len) {
+ ksmbd_debug(AUTH, "buf len %d, but blob len %d\n",
+ *out_len, resp->spnego_blob_len);
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (resp->session_key_len > sizeof(sess->sess_key)) {
+ ksmbd_debug(AUTH, "session key is too long\n");
+ retval = -EINVAL;
+ goto out;
+ }
+
+ user = ksmbd_alloc_user(&resp->login_response);
+ if (!user) {
+ ksmbd_debug(AUTH, "login failure\n");
+ retval = -ENOMEM;
+ goto out;
+ }
+ sess->user = user;
+
+ memcpy(sess->sess_key, resp->payload, resp->session_key_len);
+ memcpy(out_blob, resp->payload + resp->session_key_len,
+ resp->spnego_blob_len);
+ *out_len = resp->spnego_blob_len;
+ retval = 0;
+out:
+ kvfree(resp);
+ return retval;
+}
+#else
+int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
+ int in_len, char *out_blob, int *out_len)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+/**
+ * ksmbd_sign_smb2_pdu() - function to generate packet signing
+ * @conn: connection
+ * @key: signing key
+ * @iov: buffer iov array
+ * @n_vec: number of iovecs
+ * @sig: signature value generated for client request packet
+ *
+ */
+int ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
+ int n_vec, char *sig)
+{
+ struct ksmbd_crypto_ctx *ctx;
+ int rc, i;
+
+ ctx = ksmbd_crypto_ctx_find_hmacsha256();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_setkey(CRYPTO_HMACSHA256_TFM(ctx),
+ key,
+ SMB2_NTLMV2_SESSKEY_SIZE);
+ if (rc)
+ goto out;
+
+ rc = crypto_shash_init(CRYPTO_HMACSHA256(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "hmacsha256 init error %d\n", rc);
+ goto out;
+ }
+
+ for (i = 0; i < n_vec; i++) {
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx),
+ iov[i].iov_base,
+ iov[i].iov_len);
+ if (rc) {
+ ksmbd_debug(AUTH, "hmacsha256 update error %d\n", rc);
+ goto out;
+ }
+ }
+
+ rc = crypto_shash_final(CRYPTO_HMACSHA256(ctx), sig);
+ if (rc)
+ ksmbd_debug(AUTH, "hmacsha256 generation error %d\n", rc);
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+/**
+ * ksmbd_sign_smb3_pdu() - function to generate packet signing
+ * @conn: connection
+ * @key: signing key
+ * @iov: buffer iov array
+ * @n_vec: number of iovecs
+ * @sig: signature value generated for client request packet
+ *
+ */
+int ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
+ int n_vec, char *sig)
+{
+ struct ksmbd_crypto_ctx *ctx;
+ int rc, i;
+
+ ctx = ksmbd_crypto_ctx_find_cmacaes();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not crypto alloc cmac\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_setkey(CRYPTO_CMACAES_TFM(ctx),
+ key,
+ SMB2_CMACAES_SIZE);
+ if (rc)
+ goto out;
+
+ rc = crypto_shash_init(CRYPTO_CMACAES(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "cmaces init error %d\n", rc);
+ goto out;
+ }
+
+ for (i = 0; i < n_vec; i++) {
+ rc = crypto_shash_update(CRYPTO_CMACAES(ctx),
+ iov[i].iov_base,
+ iov[i].iov_len);
+ if (rc) {
+ ksmbd_debug(AUTH, "cmaces update error %d\n", rc);
+ goto out;
+ }
+ }
+
+ rc = crypto_shash_final(CRYPTO_CMACAES(ctx), sig);
+ if (rc)
+ ksmbd_debug(AUTH, "cmaces generation error %d\n", rc);
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+struct derivation {
+ struct kvec label;
+ struct kvec context;
+ bool binding;
+};
+
+static int generate_key(struct ksmbd_session *sess, struct kvec label,
+ struct kvec context, __u8 *key, unsigned int key_size)
+{
+ unsigned char zero = 0x0;
+ __u8 i[4] = {0, 0, 0, 1};
+ __u8 L128[4] = {0, 0, 0, 128};
+ __u8 L256[4] = {0, 0, 1, 0};
+ int rc;
+ unsigned char prfhash[SMB2_HMACSHA256_SIZE];
+ unsigned char *hashptr = prfhash;
+ struct ksmbd_crypto_ctx *ctx;
+
+ memset(prfhash, 0x0, SMB2_HMACSHA256_SIZE);
+ memset(key, 0x0, key_size);
+
+ ctx = ksmbd_crypto_ctx_find_hmacsha256();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_setkey(CRYPTO_HMACSHA256_TFM(ctx),
+ sess->sess_key,
+ SMB2_NTLMV2_SESSKEY_SIZE);
+ if (rc)
+ goto smb3signkey_ret;
+
+ rc = crypto_shash_init(CRYPTO_HMACSHA256(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "hmacsha256 init error %d\n", rc);
+ goto smb3signkey_ret;
+ }
+
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), i, 4);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with n\n");
+ goto smb3signkey_ret;
+ }
+
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx),
+ label.iov_base,
+ label.iov_len);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with label\n");
+ goto smb3signkey_ret;
+ }
+
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), &zero, 1);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with zero\n");
+ goto smb3signkey_ret;
+ }
+
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx),
+ context.iov_base,
+ context.iov_len);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with context\n");
+ goto smb3signkey_ret;
+ }
+
+ if (sess->conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ sess->conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), L256, 4);
+ else
+ rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), L128, 4);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with L\n");
+ goto smb3signkey_ret;
+ }
+
+ rc = crypto_shash_final(CRYPTO_HMACSHA256(ctx), hashptr);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate hmacmd5 hash error %d\n",
+ rc);
+ goto smb3signkey_ret;
+ }
+
+ memcpy(key, hashptr, key_size);
+
+smb3signkey_ret:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+static int generate_smb3signingkey(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn,
+ const struct derivation *signing)
+{
+ int rc;
+ struct channel *chann;
+ char *key;
+
+ chann = lookup_chann_list(sess, conn);
+ if (!chann)
+ return 0;
+
+ if (sess->conn->dialect >= SMB30_PROT_ID && signing->binding)
+ key = chann->smb3signingkey;
+ else
+ key = sess->smb3signingkey;
+
+ rc = generate_key(sess, signing->label, signing->context, key,
+ SMB3_SIGN_KEY_SIZE);
+ if (rc)
+ return rc;
+
+ if (!(sess->conn->dialect >= SMB30_PROT_ID && signing->binding))
+ memcpy(chann->smb3signingkey, key, SMB3_SIGN_KEY_SIZE);
+
+ ksmbd_debug(AUTH, "dumping generated AES signing keys\n");
+ ksmbd_debug(AUTH, "Session Id %llu\n", sess->id);
+ ksmbd_debug(AUTH, "Session Key %*ph\n",
+ SMB2_NTLMV2_SESSKEY_SIZE, sess->sess_key);
+ ksmbd_debug(AUTH, "Signing Key %*ph\n",
+ SMB3_SIGN_KEY_SIZE, key);
+ return 0;
+}
+
+int ksmbd_gen_smb30_signingkey(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn)
+{
+ struct derivation d;
+
+ d.label.iov_base = "SMB2AESCMAC";
+ d.label.iov_len = 12;
+ d.context.iov_base = "SmbSign";
+ d.context.iov_len = 8;
+ d.binding = conn->binding;
+
+ return generate_smb3signingkey(sess, conn, &d);
+}
+
+int ksmbd_gen_smb311_signingkey(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn)
+{
+ struct derivation d;
+
+ d.label.iov_base = "SMBSigningKey";
+ d.label.iov_len = 14;
+ if (conn->binding) {
+ struct preauth_session *preauth_sess;
+
+ preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
+ if (!preauth_sess)
+ return -ENOENT;
+ d.context.iov_base = preauth_sess->Preauth_HashValue;
+ } else {
+ d.context.iov_base = sess->Preauth_HashValue;
+ }
+ d.context.iov_len = 64;
+ d.binding = conn->binding;
+
+ return generate_smb3signingkey(sess, conn, &d);
+}
+
+struct derivation_twin {
+ struct derivation encryption;
+ struct derivation decryption;
+};
+
+static int generate_smb3encryptionkey(struct ksmbd_session *sess,
+ const struct derivation_twin *ptwin)
+{
+ int rc;
+
+ rc = generate_key(sess, ptwin->encryption.label,
+ ptwin->encryption.context, sess->smb3encryptionkey,
+ SMB3_ENC_DEC_KEY_SIZE);
+ if (rc)
+ return rc;
+
+ rc = generate_key(sess, ptwin->decryption.label,
+ ptwin->decryption.context,
+ sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE);
+ if (rc)
+ return rc;
+
+ ksmbd_debug(AUTH, "dumping generated AES encryption keys\n");
+ ksmbd_debug(AUTH, "Cipher type %d\n", sess->conn->cipher_type);
+ ksmbd_debug(AUTH, "Session Id %llu\n", sess->id);
+ ksmbd_debug(AUTH, "Session Key %*ph\n",
+ SMB2_NTLMV2_SESSKEY_SIZE, sess->sess_key);
+ if (sess->conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ sess->conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ ksmbd_debug(AUTH, "ServerIn Key %*ph\n",
+ SMB3_GCM256_CRYPTKEY_SIZE, sess->smb3encryptionkey);
+ ksmbd_debug(AUTH, "ServerOut Key %*ph\n",
+ SMB3_GCM256_CRYPTKEY_SIZE, sess->smb3decryptionkey);
+ } else {
+ ksmbd_debug(AUTH, "ServerIn Key %*ph\n",
+ SMB3_GCM128_CRYPTKEY_SIZE, sess->smb3encryptionkey);
+ ksmbd_debug(AUTH, "ServerOut Key %*ph\n",
+ SMB3_GCM128_CRYPTKEY_SIZE, sess->smb3decryptionkey);
+ }
+ return 0;
+}
+
+int ksmbd_gen_smb30_encryptionkey(struct ksmbd_session *sess)
+{
+ struct derivation_twin twin;
+ struct derivation *d;
+
+ d = &twin.encryption;
+ d->label.iov_base = "SMB2AESCCM";
+ d->label.iov_len = 11;
+ d->context.iov_base = "ServerOut";
+ d->context.iov_len = 10;
+
+ d = &twin.decryption;
+ d->label.iov_base = "SMB2AESCCM";
+ d->label.iov_len = 11;
+ d->context.iov_base = "ServerIn ";
+ d->context.iov_len = 10;
+
+ return generate_smb3encryptionkey(sess, &twin);
+}
+
+int ksmbd_gen_smb311_encryptionkey(struct ksmbd_session *sess)
+{
+ struct derivation_twin twin;
+ struct derivation *d;
+
+ d = &twin.encryption;
+ d->label.iov_base = "SMBS2CCipherKey";
+ d->label.iov_len = 16;
+ d->context.iov_base = sess->Preauth_HashValue;
+ d->context.iov_len = 64;
+
+ d = &twin.decryption;
+ d->label.iov_base = "SMBC2SCipherKey";
+ d->label.iov_len = 16;
+ d->context.iov_base = sess->Preauth_HashValue;
+ d->context.iov_len = 64;
+
+ return generate_smb3encryptionkey(sess, &twin);
+}
+
+int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf,
+ __u8 *pi_hash)
+{
+ int rc;
+ struct smb2_hdr *rcv_hdr = (struct smb2_hdr *)buf;
+ char *all_bytes_msg = (char *)&rcv_hdr->ProtocolId;
+ int msg_size = be32_to_cpu(rcv_hdr->smb2_buf_length);
+ struct ksmbd_crypto_ctx *ctx = NULL;
+
+ if (conn->preauth_info->Preauth_HashId !=
+ SMB2_PREAUTH_INTEGRITY_SHA512)
+ return -EINVAL;
+
+ ctx = ksmbd_crypto_ctx_find_sha512();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not alloc sha512\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_init(CRYPTO_SHA512(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "could not init shashn");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_SHA512(ctx), pi_hash, 64);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with n\n");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_SHA512(ctx), all_bytes_msg, msg_size);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with n\n");
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_SHA512(ctx), pi_hash);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate hash err : %d\n", rc);
+ goto out;
+ }
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+int ksmbd_gen_sd_hash(struct ksmbd_conn *conn, char *sd_buf, int len,
+ __u8 *pi_hash)
+{
+ int rc;
+ struct ksmbd_crypto_ctx *ctx = NULL;
+
+ ctx = ksmbd_crypto_ctx_find_sha256();
+ if (!ctx) {
+ ksmbd_debug(AUTH, "could not alloc sha256\n");
+ return -ENOMEM;
+ }
+
+ rc = crypto_shash_init(CRYPTO_SHA256(ctx));
+ if (rc) {
+ ksmbd_debug(AUTH, "could not init shashn");
+ goto out;
+ }
+
+ rc = crypto_shash_update(CRYPTO_SHA256(ctx), sd_buf, len);
+ if (rc) {
+ ksmbd_debug(AUTH, "could not update with n\n");
+ goto out;
+ }
+
+ rc = crypto_shash_final(CRYPTO_SHA256(ctx), pi_hash);
+ if (rc) {
+ ksmbd_debug(AUTH, "Could not generate hash err : %d\n", rc);
+ goto out;
+ }
+out:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
+static int ksmbd_get_encryption_key(struct ksmbd_conn *conn, __u64 ses_id,
+ int enc, u8 *key)
+{
+ struct ksmbd_session *sess;
+ u8 *ses_enc_key;
+
+ sess = ksmbd_session_lookup_all(conn, ses_id);
+ if (!sess)
+ return -EINVAL;
+
+ ses_enc_key = enc ? sess->smb3encryptionkey :
+ sess->smb3decryptionkey;
+ memcpy(key, ses_enc_key, SMB3_ENC_DEC_KEY_SIZE);
+
+ return 0;
+}
+
+static inline void smb2_sg_set_buf(struct scatterlist *sg, const void *buf,
+ unsigned int buflen)
+{
+ void *addr;
+
+ if (is_vmalloc_addr(buf))
+ addr = vmalloc_to_page(buf);
+ else
+ addr = virt_to_page(buf);
+ sg_set_page(sg, addr, buflen, offset_in_page(buf));
+}
+
+static struct scatterlist *ksmbd_init_sg(struct kvec *iov, unsigned int nvec,
+ u8 *sign)
+{
+ struct scatterlist *sg;
+ unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 24;
+ int i, nr_entries[3] = {0}, total_entries = 0, sg_idx = 0;
+
+ if (!nvec)
+ return NULL;
+
+ for (i = 0; i < nvec - 1; i++) {
+ unsigned long kaddr = (unsigned long)iov[i + 1].iov_base;
+
+ if (is_vmalloc_addr(iov[i + 1].iov_base)) {
+ nr_entries[i] = ((kaddr + iov[i + 1].iov_len +
+ PAGE_SIZE - 1) >> PAGE_SHIFT) -
+ (kaddr >> PAGE_SHIFT);
+ } else {
+ nr_entries[i]++;
+ }
+ total_entries += nr_entries[i];
+ }
+
+ /* Add two entries for transform header and signature */
+ total_entries += 2;
+
+ sg = kmalloc_array(total_entries, sizeof(struct scatterlist), GFP_KERNEL);
+ if (!sg)
+ return NULL;
+
+ sg_init_table(sg, total_entries);
+ smb2_sg_set_buf(&sg[sg_idx++], iov[0].iov_base + 24, assoc_data_len);
+ for (i = 0; i < nvec - 1; i++) {
+ void *data = iov[i + 1].iov_base;
+ int len = iov[i + 1].iov_len;
+
+ if (is_vmalloc_addr(data)) {
+ int j, offset = offset_in_page(data);
+
+ for (j = 0; j < nr_entries[i]; j++) {
+ unsigned int bytes = PAGE_SIZE - offset;
+
+ if (!len)
+ break;
+
+ if (bytes > len)
+ bytes = len;
+
+ sg_set_page(&sg[sg_idx++],
+ vmalloc_to_page(data), bytes,
+ offset_in_page(data));
+
+ data += bytes;
+ len -= bytes;
+ offset = 0;
+ }
+ } else {
+ sg_set_page(&sg[sg_idx++], virt_to_page(data), len,
+ offset_in_page(data));
+ }
+ }
+ smb2_sg_set_buf(&sg[sg_idx], sign, SMB2_SIGNATURE_SIZE);
+ return sg;
+}
+
+int ksmbd_crypt_message(struct ksmbd_conn *conn, struct kvec *iov,
+ unsigned int nvec, int enc)
+{
+ struct smb2_transform_hdr *tr_hdr =
+ (struct smb2_transform_hdr *)iov[0].iov_base;
+ unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 24;
+ int rc;
+ struct scatterlist *sg;
+ u8 sign[SMB2_SIGNATURE_SIZE] = {};
+ u8 key[SMB3_ENC_DEC_KEY_SIZE];
+ struct aead_request *req;
+ char *iv;
+ unsigned int iv_len;
+ struct crypto_aead *tfm;
+ unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
+ struct ksmbd_crypto_ctx *ctx;
+
+ rc = ksmbd_get_encryption_key(conn,
+ le64_to_cpu(tr_hdr->SessionId),
+ enc,
+ key);
+ if (rc) {
+ pr_err("Could not get %scryption key\n", enc ? "en" : "de");
+ return rc;
+ }
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ ctx = ksmbd_crypto_ctx_find_gcm();
+ else
+ ctx = ksmbd_crypto_ctx_find_ccm();
+ if (!ctx) {
+ pr_err("crypto alloc failed\n");
+ return -ENOMEM;
+ }
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ tfm = CRYPTO_GCM(ctx);
+ else
+ tfm = CRYPTO_CCM(ctx);
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
+ else
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE);
+ if (rc) {
+ pr_err("Failed to set aead key %d\n", rc);
+ goto free_ctx;
+ }
+
+ rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE);
+ if (rc) {
+ pr_err("Failed to set authsize %d\n", rc);
+ goto free_ctx;
+ }
+
+ req = aead_request_alloc(tfm, GFP_KERNEL);
+ if (!req) {
+ rc = -ENOMEM;
+ goto free_ctx;
+ }
+
+ if (!enc) {
+ memcpy(sign, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
+ crypt_len += SMB2_SIGNATURE_SIZE;
+ }
+
+ sg = ksmbd_init_sg(iov, nvec, sign);
+ if (!sg) {
+ pr_err("Failed to init sg\n");
+ rc = -ENOMEM;
+ goto free_req;
+ }
+
+ iv_len = crypto_aead_ivsize(tfm);
+ iv = kzalloc(iv_len, GFP_KERNEL);
+ if (!iv) {
+ rc = -ENOMEM;
+ goto free_sg;
+ }
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ memcpy(iv, (char *)tr_hdr->Nonce, SMB3_AES_GCM_NONCE);
+ } else {
+ iv[0] = 3;
+ memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES_CCM_NONCE);
+ }
+
+ aead_request_set_crypt(req, sg, sg, crypt_len, iv);
+ aead_request_set_ad(req, assoc_data_len);
+ aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL);
+
+ if (enc)
+ rc = crypto_aead_encrypt(req);
+ else
+ rc = crypto_aead_decrypt(req);
+ if (rc)
+ goto free_iv;
+
+ if (enc)
+ memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);
+
+free_iv:
+ kfree(iv);
+free_sg:
+ kfree(sg);
+free_req:
+ kfree(req);
+free_ctx:
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
diff --git a/fs/ksmbd/auth.h b/fs/ksmbd/auth.h
new file mode 100644
index 000000000000..9c2d4badd05d
--- /dev/null
+++ b/fs/ksmbd/auth.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __AUTH_H__
+#define __AUTH_H__
+
+#include "ntlmssp.h"
+
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+#define AUTH_GSS_LENGTH 96
+#define AUTH_GSS_PADDING 0
+#else
+#define AUTH_GSS_LENGTH 74
+#define AUTH_GSS_PADDING 6
+#endif
+
+#define CIFS_HMAC_MD5_HASH_SIZE (16)
+#define CIFS_NTHASH_SIZE (16)
+
+/*
+ * Size of the ntlm client response
+ */
+#define CIFS_AUTH_RESP_SIZE 24
+#define CIFS_SMB1_SIGNATURE_SIZE 8
+#define CIFS_SMB1_SESSKEY_SIZE 16
+
+#define KSMBD_AUTH_NTLMSSP 0x0001
+#define KSMBD_AUTH_KRB5 0x0002
+#define KSMBD_AUTH_MSKRB5 0x0004
+#define KSMBD_AUTH_KRB5U2U 0x0008
+
+struct ksmbd_session;
+struct ksmbd_conn;
+struct kvec;
+
+int ksmbd_crypt_message(struct ksmbd_conn *conn, struct kvec *iov,
+ unsigned int nvec, int enc);
+void ksmbd_copy_gss_neg_header(void *buf);
+int ksmbd_auth_ntlm(struct ksmbd_session *sess, char *pw_buf);
+int ksmbd_auth_ntlmv2(struct ksmbd_session *sess, struct ntlmv2_resp *ntlmv2,
+ int blen, char *domain_name);
+int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
+ int blob_len, struct ksmbd_session *sess);
+int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob,
+ int blob_len, struct ksmbd_session *sess);
+unsigned int
+ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
+ struct ksmbd_session *sess);
+int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
+ int in_len, char *out_blob, int *out_len);
+int ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
+ int n_vec, char *sig);
+int ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
+ int n_vec, char *sig);
+int ksmbd_gen_smb30_signingkey(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn);
+int ksmbd_gen_smb311_signingkey(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn);
+int ksmbd_gen_smb30_encryptionkey(struct ksmbd_session *sess);
+int ksmbd_gen_smb311_encryptionkey(struct ksmbd_session *sess);
+int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf,
+ __u8 *pi_hash);
+int ksmbd_gen_sd_hash(struct ksmbd_conn *conn, char *sd_buf, int len,
+ __u8 *pi_hash);
+#endif
diff --git a/fs/ksmbd/connection.c b/fs/ksmbd/connection.c
new file mode 100644
index 000000000000..af086d35398a
--- /dev/null
+++ b/fs/ksmbd/connection.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <namjae.jeon@protocolfreedom.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/mutex.h>
+#include <linux/freezer.h>
+#include <linux/module.h>
+
+#include "server.h"
+#include "smb_common.h"
+#include "mgmt/ksmbd_ida.h"
+#include "connection.h"
+#include "transport_tcp.h"
+#include "transport_rdma.h"
+
+static DEFINE_MUTEX(init_lock);
+
+static struct ksmbd_conn_ops default_conn_ops;
+
+LIST_HEAD(conn_list);
+DEFINE_RWLOCK(conn_list_lock);
+
+/**
+ * ksmbd_conn_free() - free resources of the connection instance
+ *
+ * @conn: connection instance to be cleand up
+ *
+ * During the thread termination, the corresponding conn instance
+ * resources(sock/memory) are released and finally the conn object is freed.
+ */
+void ksmbd_conn_free(struct ksmbd_conn *conn)
+{
+ write_lock(&conn_list_lock);
+ list_del(&conn->conns_list);
+ write_unlock(&conn_list_lock);
+
+ kvfree(conn->request_buf);
+ kfree(conn->preauth_info);
+ kfree(conn);
+}
+
+/**
+ * ksmbd_conn_alloc() - initialize a new connection instance
+ *
+ * Return: ksmbd_conn struct on success, otherwise NULL
+ */
+struct ksmbd_conn *ksmbd_conn_alloc(void)
+{
+ struct ksmbd_conn *conn;
+
+ conn = kzalloc(sizeof(struct ksmbd_conn), GFP_KERNEL);
+ if (!conn)
+ return NULL;
+
+ conn->need_neg = true;
+ conn->status = KSMBD_SESS_NEW;
+ conn->local_nls = load_nls("utf8");
+ if (!conn->local_nls)
+ conn->local_nls = load_nls_default();
+ atomic_set(&conn->req_running, 0);
+ atomic_set(&conn->r_count, 0);
+ init_waitqueue_head(&conn->req_running_q);
+ INIT_LIST_HEAD(&conn->conns_list);
+ INIT_LIST_HEAD(&conn->sessions);
+ INIT_LIST_HEAD(&conn->requests);
+ INIT_LIST_HEAD(&conn->async_requests);
+ spin_lock_init(&conn->request_lock);
+ spin_lock_init(&conn->credits_lock);
+ ida_init(&conn->async_ida);
+
+ spin_lock_init(&conn->llist_lock);
+ INIT_LIST_HEAD(&conn->lock_list);
+
+ write_lock(&conn_list_lock);
+ list_add(&conn->conns_list, &conn_list);
+ write_unlock(&conn_list_lock);
+ return conn;
+}
+
+bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c)
+{
+ struct ksmbd_conn *t;
+ bool ret = false;
+
+ read_lock(&conn_list_lock);
+ list_for_each_entry(t, &conn_list, conns_list) {
+ if (memcmp(t->ClientGUID, c->ClientGUID, SMB2_CLIENT_GUID_SIZE))
+ continue;
+
+ ret = true;
+ break;
+ }
+ read_unlock(&conn_list_lock);
+ return ret;
+}
+
+void ksmbd_conn_enqueue_request(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct list_head *requests_queue = NULL;
+
+ if (conn->ops->get_cmd_val(work) != SMB2_CANCEL_HE) {
+ requests_queue = &conn->requests;
+ work->syncronous = true;
+ }
+
+ if (requests_queue) {
+ atomic_inc(&conn->req_running);
+ spin_lock(&conn->request_lock);
+ list_add_tail(&work->request_entry, requests_queue);
+ spin_unlock(&conn->request_lock);
+ }
+}
+
+int ksmbd_conn_try_dequeue_request(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ int ret = 1;
+
+ if (list_empty(&work->request_entry) &&
+ list_empty(&work->async_request_entry))
+ return 0;
+
+ if (!work->multiRsp)
+ atomic_dec(&conn->req_running);
+ spin_lock(&conn->request_lock);
+ if (!work->multiRsp) {
+ list_del_init(&work->request_entry);
+ if (work->syncronous == false)
+ list_del_init(&work->async_request_entry);
+ ret = 0;
+ }
+ spin_unlock(&conn->request_lock);
+
+ wake_up_all(&conn->req_running_q);
+ return ret;
+}
+
+static void ksmbd_conn_lock(struct ksmbd_conn *conn)
+{
+ mutex_lock(&conn->srv_mutex);
+}
+
+static void ksmbd_conn_unlock(struct ksmbd_conn *conn)
+{
+ mutex_unlock(&conn->srv_mutex);
+}
+
+void ksmbd_conn_wait_idle(struct ksmbd_conn *conn)
+{
+ wait_event(conn->req_running_q, atomic_read(&conn->req_running) < 2);
+}
+
+int ksmbd_conn_write(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb_hdr *rsp_hdr = work->response_buf;
+ size_t len = 0;
+ int sent;
+ struct kvec iov[3];
+ int iov_idx = 0;
+
+ ksmbd_conn_try_dequeue_request(work);
+ if (!rsp_hdr) {
+ pr_err("NULL response header\n");
+ return -EINVAL;
+ }
+
+ if (work->tr_buf) {
+ iov[iov_idx] = (struct kvec) { work->tr_buf,
+ sizeof(struct smb2_transform_hdr) };
+ len += iov[iov_idx++].iov_len;
+ }
+
+ if (work->aux_payload_sz) {
+ iov[iov_idx] = (struct kvec) { rsp_hdr, work->resp_hdr_sz };
+ len += iov[iov_idx++].iov_len;
+ iov[iov_idx] = (struct kvec) { work->aux_payload_buf, work->aux_payload_sz };
+ len += iov[iov_idx++].iov_len;
+ } else {
+ if (work->tr_buf)
+ iov[iov_idx].iov_len = work->resp_hdr_sz;
+ else
+ iov[iov_idx].iov_len = get_rfc1002_len(rsp_hdr) + 4;
+ iov[iov_idx].iov_base = rsp_hdr;
+ len += iov[iov_idx++].iov_len;
+ }
+
+ ksmbd_conn_lock(conn);
+ sent = conn->transport->ops->writev(conn->transport, &iov[0],
+ iov_idx, len,
+ work->need_invalidate_rkey,
+ work->remote_key);
+ ksmbd_conn_unlock(conn);
+
+ if (sent < 0) {
+ pr_err("Failed to send message: %d\n", sent);
+ return sent;
+ }
+
+ return 0;
+}
+
+int ksmbd_conn_rdma_read(struct ksmbd_conn *conn, void *buf,
+ unsigned int buflen, u32 remote_key, u64 remote_offset,
+ u32 remote_len)
+{
+ int ret = -EINVAL;
+
+ if (conn->transport->ops->rdma_read)
+ ret = conn->transport->ops->rdma_read(conn->transport,
+ buf, buflen,
+ remote_key, remote_offset,
+ remote_len);
+ return ret;
+}
+
+int ksmbd_conn_rdma_write(struct ksmbd_conn *conn, void *buf,
+ unsigned int buflen, u32 remote_key,
+ u64 remote_offset, u32 remote_len)
+{
+ int ret = -EINVAL;
+
+ if (conn->transport->ops->rdma_write)
+ ret = conn->transport->ops->rdma_write(conn->transport,
+ buf, buflen,
+ remote_key, remote_offset,
+ remote_len);
+ return ret;
+}
+
+bool ksmbd_conn_alive(struct ksmbd_conn *conn)
+{
+ if (!ksmbd_server_running())
+ return false;
+
+ if (conn->status == KSMBD_SESS_EXITING)
+ return false;
+
+ if (kthread_should_stop())
+ return false;
+
+ if (atomic_read(&conn->stats.open_files_count) > 0)
+ return true;
+
+ /*
+ * Stop current session if the time that get last request from client
+ * is bigger than deadtime user configured and opening file count is
+ * zero.
+ */
+ if (server_conf.deadtime > 0 &&
+ time_after(jiffies, conn->last_active + server_conf.deadtime)) {
+ ksmbd_debug(CONN, "No response from client in %lu minutes\n",
+ server_conf.deadtime / SMB_ECHO_INTERVAL);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ksmbd_conn_handler_loop() - session thread to listen on new smb requests
+ * @p: connection instance
+ *
+ * One thread each per connection
+ *
+ * Return: 0 on success
+ */
+int ksmbd_conn_handler_loop(void *p)
+{
+ struct ksmbd_conn *conn = (struct ksmbd_conn *)p;
+ struct ksmbd_transport *t = conn->transport;
+ unsigned int pdu_size;
+ char hdr_buf[4] = {0,};
+ int size;
+
+ mutex_init(&conn->srv_mutex);
+ __module_get(THIS_MODULE);
+
+ if (t->ops->prepare && t->ops->prepare(t))
+ goto out;
+
+ conn->last_active = jiffies;
+ while (ksmbd_conn_alive(conn)) {
+ if (try_to_freeze())
+ continue;
+
+ kvfree(conn->request_buf);
+ conn->request_buf = NULL;
+
+ size = t->ops->read(t, hdr_buf, sizeof(hdr_buf));
+ if (size != sizeof(hdr_buf))
+ break;
+
+ pdu_size = get_rfc1002_len(hdr_buf);
+ ksmbd_debug(CONN, "RFC1002 header %u bytes\n", pdu_size);
+
+ /* make sure we have enough to get to SMB header end */
+ if (!ksmbd_pdu_size_has_room(pdu_size)) {
+ ksmbd_debug(CONN, "SMB request too short (%u bytes)\n",
+ pdu_size);
+ continue;
+ }
+
+ /* 4 for rfc1002 length field */
+ size = pdu_size + 4;
+ conn->request_buf = kvmalloc(size, GFP_KERNEL);
+ if (!conn->request_buf)
+ continue;
+
+ memcpy(conn->request_buf, hdr_buf, sizeof(hdr_buf));
+ if (!ksmbd_smb_request(conn))
+ break;
+
+ /*
+ * We already read 4 bytes to find out PDU size, now
+ * read in PDU
+ */
+ size = t->ops->read(t, conn->request_buf + 4, pdu_size);
+ if (size < 0) {
+ pr_err("sock_read failed: %d\n", size);
+ break;
+ }
+
+ if (size != pdu_size) {
+ pr_err("PDU error. Read: %d, Expected: %d\n",
+ size, pdu_size);
+ continue;
+ }
+
+ if (!default_conn_ops.process_fn) {
+ pr_err("No connection request callback\n");
+ break;
+ }
+
+ if (default_conn_ops.process_fn(conn)) {
+ pr_err("Cannot handle request\n");
+ break;
+ }
+ }
+
+out:
+ /* Wait till all reference dropped to the Server object*/
+ while (atomic_read(&conn->r_count) > 0)
+ schedule_timeout(HZ);
+
+ unload_nls(conn->local_nls);
+ if (default_conn_ops.terminate_fn)
+ default_conn_ops.terminate_fn(conn);
+ t->ops->disconnect(t);
+ module_put(THIS_MODULE);
+ return 0;
+}
+
+void ksmbd_conn_init_server_callbacks(struct ksmbd_conn_ops *ops)
+{
+ default_conn_ops.process_fn = ops->process_fn;
+ default_conn_ops.terminate_fn = ops->terminate_fn;
+}
+
+int ksmbd_conn_transport_init(void)
+{
+ int ret;
+
+ mutex_lock(&init_lock);
+ ret = ksmbd_tcp_init();
+ if (ret) {
+ pr_err("Failed to init TCP subsystem: %d\n", ret);
+ goto out;
+ }
+
+ ret = ksmbd_rdma_init();
+ if (ret) {
+ pr_err("Failed to init RDMA subsystem: %d\n", ret);
+ goto out;
+ }
+out:
+ mutex_unlock(&init_lock);
+ return ret;
+}
+
+static void stop_sessions(void)
+{
+ struct ksmbd_conn *conn;
+
+again:
+ read_lock(&conn_list_lock);
+ list_for_each_entry(conn, &conn_list, conns_list) {
+ struct task_struct *task;
+
+ task = conn->transport->handler;
+ if (task)
+ ksmbd_debug(CONN, "Stop session handler %s/%d\n",
+ task->comm, task_pid_nr(task));
+ conn->status = KSMBD_SESS_EXITING;
+ }
+ read_unlock(&conn_list_lock);
+
+ if (!list_empty(&conn_list)) {
+ schedule_timeout_interruptible(HZ / 10); /* 100ms */
+ goto again;
+ }
+}
+
+void ksmbd_conn_transport_destroy(void)
+{
+ mutex_lock(&init_lock);
+ ksmbd_tcp_destroy();
+ ksmbd_rdma_destroy();
+ stop_sessions();
+ mutex_unlock(&init_lock);
+}
diff --git a/fs/ksmbd/connection.h b/fs/ksmbd/connection.h
new file mode 100644
index 000000000000..e5403c587a58
--- /dev/null
+++ b/fs/ksmbd/connection.h
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_CONNECTION_H__
+#define __KSMBD_CONNECTION_H__
+
+#include <linux/list.h>
+#include <linux/ip.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+#include <net/inet_connection_sock.h>
+#include <net/request_sock.h>
+#include <linux/kthread.h>
+#include <linux/nls.h>
+
+#include "smb_common.h"
+#include "ksmbd_work.h"
+
+#define KSMBD_SOCKET_BACKLOG 16
+
+/*
+ * WARNING
+ *
+ * This is nothing but a HACK. Session status should move to channel
+ * or to session. As of now we have 1 tcp_conn : 1 ksmbd_session, but
+ * we need to change it to 1 tcp_conn : N ksmbd_sessions.
+ */
+enum {
+ KSMBD_SESS_NEW = 0,
+ KSMBD_SESS_GOOD,
+ KSMBD_SESS_EXITING,
+ KSMBD_SESS_NEED_RECONNECT,
+ KSMBD_SESS_NEED_NEGOTIATE
+};
+
+struct ksmbd_stats {
+ atomic_t open_files_count;
+ atomic64_t request_served;
+};
+
+struct ksmbd_transport;
+
+struct ksmbd_conn {
+ struct smb_version_values *vals;
+ struct smb_version_ops *ops;
+ struct smb_version_cmds *cmds;
+ unsigned int max_cmds;
+ struct mutex srv_mutex;
+ int status;
+ unsigned int cli_cap;
+ char *request_buf;
+ struct ksmbd_transport *transport;
+ struct nls_table *local_nls;
+ struct list_head conns_list;
+ /* smb session 1 per user */
+ struct list_head sessions;
+ unsigned long last_active;
+ /* How many request are running currently */
+ atomic_t req_running;
+ /* References which are made for this Server object*/
+ atomic_t r_count;
+ unsigned short total_credits;
+ unsigned short max_credits;
+ spinlock_t credits_lock;
+ wait_queue_head_t req_running_q;
+ /* Lock to protect requests list*/
+ spinlock_t request_lock;
+ struct list_head requests;
+ struct list_head async_requests;
+ int connection_type;
+ struct ksmbd_stats stats;
+ char ClientGUID[SMB2_CLIENT_GUID_SIZE];
+ union {
+ /* pending trans request table */
+ struct trans_state *recent_trans;
+ /* Used by ntlmssp */
+ char *ntlmssp_cryptkey;
+ };
+
+ spinlock_t llist_lock;
+ struct list_head lock_list;
+
+ struct preauth_integrity_info *preauth_info;
+
+ bool need_neg;
+ unsigned int auth_mechs;
+ unsigned int preferred_auth_mech;
+ bool sign;
+ bool use_spnego:1;
+ __u16 cli_sec_mode;
+ __u16 srv_sec_mode;
+ /* dialect index that server chose */
+ __u16 dialect;
+
+ char *mechToken;
+
+ struct ksmbd_conn_ops *conn_ops;
+
+ /* Preauth Session Table */
+ struct list_head preauth_sess_table;
+
+ struct sockaddr_storage peer_addr;
+
+ /* Identifier for async message */
+ struct ida async_ida;
+
+ __le16 cipher_type;
+ __le16 compress_algorithm;
+ bool posix_ext_supported;
+ bool signing_negotiated;
+ __le16 signing_algorithm;
+ bool binding;
+};
+
+struct ksmbd_conn_ops {
+ int (*process_fn)(struct ksmbd_conn *conn);
+ int (*terminate_fn)(struct ksmbd_conn *conn);
+};
+
+struct ksmbd_transport_ops {
+ int (*prepare)(struct ksmbd_transport *t);
+ void (*disconnect)(struct ksmbd_transport *t);
+ int (*read)(struct ksmbd_transport *t, char *buf, unsigned int size);
+ int (*writev)(struct ksmbd_transport *t, struct kvec *iovs, int niov,
+ int size, bool need_invalidate_rkey,
+ unsigned int remote_key);
+ int (*rdma_read)(struct ksmbd_transport *t, void *buf, unsigned int len,
+ u32 remote_key, u64 remote_offset, u32 remote_len);
+ int (*rdma_write)(struct ksmbd_transport *t, void *buf,
+ unsigned int len, u32 remote_key, u64 remote_offset,
+ u32 remote_len);
+};
+
+struct ksmbd_transport {
+ struct ksmbd_conn *conn;
+ struct ksmbd_transport_ops *ops;
+ struct task_struct *handler;
+};
+
+#define KSMBD_TCP_RECV_TIMEOUT (7 * HZ)
+#define KSMBD_TCP_SEND_TIMEOUT (5 * HZ)
+#define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr))
+
+extern struct list_head conn_list;
+extern rwlock_t conn_list_lock;
+
+bool ksmbd_conn_alive(struct ksmbd_conn *conn);
+void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);
+struct ksmbd_conn *ksmbd_conn_alloc(void);
+void ksmbd_conn_free(struct ksmbd_conn *conn);
+bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
+int ksmbd_conn_write(struct ksmbd_work *work);
+int ksmbd_conn_rdma_read(struct ksmbd_conn *conn, void *buf,
+ unsigned int buflen, u32 remote_key, u64 remote_offset,
+ u32 remote_len);
+int ksmbd_conn_rdma_write(struct ksmbd_conn *conn, void *buf,
+ unsigned int buflen, u32 remote_key, u64 remote_offset,
+ u32 remote_len);
+void ksmbd_conn_enqueue_request(struct ksmbd_work *work);
+int ksmbd_conn_try_dequeue_request(struct ksmbd_work *work);
+void ksmbd_conn_init_server_callbacks(struct ksmbd_conn_ops *ops);
+int ksmbd_conn_handler_loop(void *p);
+int ksmbd_conn_transport_init(void);
+void ksmbd_conn_transport_destroy(void);
+
+/*
+ * WARNING
+ *
+ * This is a hack. We will move status to a proper place once we land
+ * a multi-sessions support.
+ */
+static inline bool ksmbd_conn_good(struct ksmbd_work *work)
+{
+ return work->conn->status == KSMBD_SESS_GOOD;
+}
+
+static inline bool ksmbd_conn_need_negotiate(struct ksmbd_work *work)
+{
+ return work->conn->status == KSMBD_SESS_NEED_NEGOTIATE;
+}
+
+static inline bool ksmbd_conn_need_reconnect(struct ksmbd_work *work)
+{
+ return work->conn->status == KSMBD_SESS_NEED_RECONNECT;
+}
+
+static inline bool ksmbd_conn_exiting(struct ksmbd_work *work)
+{
+ return work->conn->status == KSMBD_SESS_EXITING;
+}
+
+static inline void ksmbd_conn_set_good(struct ksmbd_work *work)
+{
+ work->conn->status = KSMBD_SESS_GOOD;
+}
+
+static inline void ksmbd_conn_set_need_negotiate(struct ksmbd_work *work)
+{
+ work->conn->status = KSMBD_SESS_NEED_NEGOTIATE;
+}
+
+static inline void ksmbd_conn_set_need_reconnect(struct ksmbd_work *work)
+{
+ work->conn->status = KSMBD_SESS_NEED_RECONNECT;
+}
+
+static inline void ksmbd_conn_set_exiting(struct ksmbd_work *work)
+{
+ work->conn->status = KSMBD_SESS_EXITING;
+}
+#endif /* __CONNECTION_H__ */
diff --git a/fs/ksmbd/crypto_ctx.c b/fs/ksmbd/crypto_ctx.c
new file mode 100644
index 000000000000..5f4b1008d17e
--- /dev/null
+++ b/fs/ksmbd/crypto_ctx.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+
+#include "glob.h"
+#include "crypto_ctx.h"
+
+struct crypto_ctx_list {
+ spinlock_t ctx_lock;
+ int avail_ctx;
+ struct list_head idle_ctx;
+ wait_queue_head_t ctx_wait;
+};
+
+static struct crypto_ctx_list ctx_list;
+
+static inline void free_aead(struct crypto_aead *aead)
+{
+ if (aead)
+ crypto_free_aead(aead);
+}
+
+static void free_shash(struct shash_desc *shash)
+{
+ if (shash) {
+ crypto_free_shash(shash->tfm);
+ kfree(shash);
+ }
+}
+
+static struct crypto_aead *alloc_aead(int id)
+{
+ struct crypto_aead *tfm = NULL;
+
+ switch (id) {
+ case CRYPTO_AEAD_AES_GCM:
+ tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+ break;
+ case CRYPTO_AEAD_AES_CCM:
+ tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
+ break;
+ default:
+ pr_err("Does not support encrypt ahead(id : %d)\n", id);
+ return NULL;
+ }
+
+ if (IS_ERR(tfm)) {
+ pr_err("Failed to alloc encrypt aead : %ld\n", PTR_ERR(tfm));
+ return NULL;
+ }
+
+ return tfm;
+}
+
+static struct shash_desc *alloc_shash_desc(int id)
+{
+ struct crypto_shash *tfm = NULL;
+ struct shash_desc *shash;
+
+ switch (id) {
+ case CRYPTO_SHASH_HMACMD5:
+ tfm = crypto_alloc_shash("hmac(md5)", 0, 0);
+ break;
+ case CRYPTO_SHASH_HMACSHA256:
+ tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
+ break;
+ case CRYPTO_SHASH_CMACAES:
+ tfm = crypto_alloc_shash("cmac(aes)", 0, 0);
+ break;
+ case CRYPTO_SHASH_SHA256:
+ tfm = crypto_alloc_shash("sha256", 0, 0);
+ break;
+ case CRYPTO_SHASH_SHA512:
+ tfm = crypto_alloc_shash("sha512", 0, 0);
+ break;
+ case CRYPTO_SHASH_MD4:
+ tfm = crypto_alloc_shash("md4", 0, 0);
+ break;
+ case CRYPTO_SHASH_MD5:
+ tfm = crypto_alloc_shash("md5", 0, 0);
+ break;
+ default:
+ return NULL;
+ }
+
+ if (IS_ERR(tfm))
+ return NULL;
+
+ shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
+ GFP_KERNEL);
+ if (!shash)
+ crypto_free_shash(tfm);
+ else
+ shash->tfm = tfm;
+ return shash;
+}
+
+static void ctx_free(struct ksmbd_crypto_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i < CRYPTO_SHASH_MAX; i++)
+ free_shash(ctx->desc[i]);
+ for (i = 0; i < CRYPTO_AEAD_MAX; i++)
+ free_aead(ctx->ccmaes[i]);
+ kfree(ctx);
+}
+
+static struct ksmbd_crypto_ctx *ksmbd_find_crypto_ctx(void)
+{
+ struct ksmbd_crypto_ctx *ctx;
+
+ while (1) {
+ spin_lock(&ctx_list.ctx_lock);
+ if (!list_empty(&ctx_list.idle_ctx)) {
+ ctx = list_entry(ctx_list.idle_ctx.next,
+ struct ksmbd_crypto_ctx,
+ list);
+ list_del(&ctx->list);
+ spin_unlock(&ctx_list.ctx_lock);
+ return ctx;
+ }
+
+ if (ctx_list.avail_ctx > num_online_cpus()) {
+ spin_unlock(&ctx_list.ctx_lock);
+ wait_event(ctx_list.ctx_wait,
+ !list_empty(&ctx_list.idle_ctx));
+ continue;
+ }
+
+ ctx_list.avail_ctx++;
+ spin_unlock(&ctx_list.ctx_lock);
+
+ ctx = kzalloc(sizeof(struct ksmbd_crypto_ctx), GFP_KERNEL);
+ if (!ctx) {
+ spin_lock(&ctx_list.ctx_lock);
+ ctx_list.avail_ctx--;
+ spin_unlock(&ctx_list.ctx_lock);
+ wait_event(ctx_list.ctx_wait,
+ !list_empty(&ctx_list.idle_ctx));
+ continue;
+ }
+ break;
+ }
+ return ctx;
+}
+
+void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ spin_lock(&ctx_list.ctx_lock);
+ if (ctx_list.avail_ctx <= num_online_cpus()) {
+ list_add(&ctx->list, &ctx_list.idle_ctx);
+ spin_unlock(&ctx_list.ctx_lock);
+ wake_up(&ctx_list.ctx_wait);
+ return;
+ }
+
+ ctx_list.avail_ctx--;
+ spin_unlock(&ctx_list.ctx_lock);
+ ctx_free(ctx);
+}
+
+static struct ksmbd_crypto_ctx *____crypto_shash_ctx_find(int id)
+{
+ struct ksmbd_crypto_ctx *ctx;
+
+ if (id >= CRYPTO_SHASH_MAX)
+ return NULL;
+
+ ctx = ksmbd_find_crypto_ctx();
+ if (ctx->desc[id])
+ return ctx;
+
+ ctx->desc[id] = alloc_shash_desc(id);
+ if (ctx->desc[id])
+ return ctx;
+ ksmbd_release_crypto_ctx(ctx);
+ return NULL;
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_HMACMD5);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacsha256(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_HMACSHA256);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_CMACAES);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha256(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_SHA256);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha512(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_SHA512);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_md4(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_MD4);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_md5(void)
+{
+ return ____crypto_shash_ctx_find(CRYPTO_SHASH_MD5);
+}
+
+static struct ksmbd_crypto_ctx *____crypto_aead_ctx_find(int id)
+{
+ struct ksmbd_crypto_ctx *ctx;
+
+ if (id >= CRYPTO_AEAD_MAX)
+ return NULL;
+
+ ctx = ksmbd_find_crypto_ctx();
+ if (ctx->ccmaes[id])
+ return ctx;
+
+ ctx->ccmaes[id] = alloc_aead(id);
+ if (ctx->ccmaes[id])
+ return ctx;
+ ksmbd_release_crypto_ctx(ctx);
+ return NULL;
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void)
+{
+ return ____crypto_aead_ctx_find(CRYPTO_AEAD_AES_GCM);
+}
+
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void)
+{
+ return ____crypto_aead_ctx_find(CRYPTO_AEAD_AES_CCM);
+}
+
+void ksmbd_crypto_destroy(void)
+{
+ struct ksmbd_crypto_ctx *ctx;
+
+ while (!list_empty(&ctx_list.idle_ctx)) {
+ ctx = list_entry(ctx_list.idle_ctx.next,
+ struct ksmbd_crypto_ctx,
+ list);
+ list_del(&ctx->list);
+ ctx_free(ctx);
+ }
+}
+
+int ksmbd_crypto_create(void)
+{
+ struct ksmbd_crypto_ctx *ctx;
+
+ spin_lock_init(&ctx_list.ctx_lock);
+ INIT_LIST_HEAD(&ctx_list.idle_ctx);
+ init_waitqueue_head(&ctx_list.ctx_wait);
+ ctx_list.avail_ctx = 1;
+
+ ctx = kzalloc(sizeof(struct ksmbd_crypto_ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ list_add(&ctx->list, &ctx_list.idle_ctx);
+ return 0;
+}
diff --git a/fs/ksmbd/crypto_ctx.h b/fs/ksmbd/crypto_ctx.h
new file mode 100644
index 000000000000..ef11154b43df
--- /dev/null
+++ b/fs/ksmbd/crypto_ctx.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __CRYPTO_CTX_H__
+#define __CRYPTO_CTX_H__
+
+#include <crypto/hash.h>
+#include <crypto/aead.h>
+
+enum {
+ CRYPTO_SHASH_HMACMD5 = 0,
+ CRYPTO_SHASH_HMACSHA256,
+ CRYPTO_SHASH_CMACAES,
+ CRYPTO_SHASH_SHA256,
+ CRYPTO_SHASH_SHA512,
+ CRYPTO_SHASH_MD4,
+ CRYPTO_SHASH_MD5,
+ CRYPTO_SHASH_MAX,
+};
+
+enum {
+ CRYPTO_AEAD_AES_GCM = 16,
+ CRYPTO_AEAD_AES_CCM,
+ CRYPTO_AEAD_MAX,
+};
+
+enum {
+ CRYPTO_BLK_ECBDES = 32,
+ CRYPTO_BLK_MAX,
+};
+
+struct ksmbd_crypto_ctx {
+ struct list_head list;
+
+ struct shash_desc *desc[CRYPTO_SHASH_MAX];
+ struct crypto_aead *ccmaes[CRYPTO_AEAD_MAX];
+};
+
+#define CRYPTO_HMACMD5(c) ((c)->desc[CRYPTO_SHASH_HMACMD5])
+#define CRYPTO_HMACSHA256(c) ((c)->desc[CRYPTO_SHASH_HMACSHA256])
+#define CRYPTO_CMACAES(c) ((c)->desc[CRYPTO_SHASH_CMACAES])
+#define CRYPTO_SHA256(c) ((c)->desc[CRYPTO_SHASH_SHA256])
+#define CRYPTO_SHA512(c) ((c)->desc[CRYPTO_SHASH_SHA512])
+#define CRYPTO_MD4(c) ((c)->desc[CRYPTO_SHASH_MD4])
+#define CRYPTO_MD5(c) ((c)->desc[CRYPTO_SHASH_MD5])
+
+#define CRYPTO_HMACMD5_TFM(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]->tfm)
+#define CRYPTO_HMACSHA256_TFM(c)\
+ ((c)->desc[CRYPTO_SHASH_HMACSHA256]->tfm)
+#define CRYPTO_CMACAES_TFM(c) ((c)->desc[CRYPTO_SHASH_CMACAES]->tfm)
+#define CRYPTO_SHA256_TFM(c) ((c)->desc[CRYPTO_SHASH_SHA256]->tfm)
+#define CRYPTO_SHA512_TFM(c) ((c)->desc[CRYPTO_SHASH_SHA512]->tfm)
+#define CRYPTO_MD4_TFM(c) ((c)->desc[CRYPTO_SHASH_MD4]->tfm)
+#define CRYPTO_MD5_TFM(c) ((c)->desc[CRYPTO_SHASH_MD5]->tfm)
+
+#define CRYPTO_GCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_GCM])
+#define CRYPTO_CCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_CCM])
+
+void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacsha256(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha512(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha256(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_md4(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_md5(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void);
+struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void);
+void ksmbd_crypto_destroy(void);
+int ksmbd_crypto_create(void);
+
+#endif /* __CRYPTO_CTX_H__ */
diff --git a/fs/ksmbd/glob.h b/fs/ksmbd/glob.h
new file mode 100644
index 000000000000..49a5a3afa118
--- /dev/null
+++ b/fs/ksmbd/glob.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_GLOB_H
+#define __KSMBD_GLOB_H
+
+#include <linux/ctype.h>
+
+#include "unicode.h"
+#include "vfs_cache.h"
+
+#define KSMBD_VERSION "3.1.9"
+
+extern int ksmbd_debug_types;
+
+#define KSMBD_DEBUG_SMB BIT(0)
+#define KSMBD_DEBUG_AUTH BIT(1)
+#define KSMBD_DEBUG_VFS BIT(2)
+#define KSMBD_DEBUG_OPLOCK BIT(3)
+#define KSMBD_DEBUG_IPC BIT(4)
+#define KSMBD_DEBUG_CONN BIT(5)
+#define KSMBD_DEBUG_RDMA BIT(6)
+#define KSMBD_DEBUG_ALL (KSMBD_DEBUG_SMB | KSMBD_DEBUG_AUTH | \
+ KSMBD_DEBUG_VFS | KSMBD_DEBUG_OPLOCK | \
+ KSMBD_DEBUG_IPC | KSMBD_DEBUG_CONN | \
+ KSMBD_DEBUG_RDMA)
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+
+#ifdef SUBMOD_NAME
+#define pr_fmt(fmt) "ksmbd: " SUBMOD_NAME ": " fmt
+#else
+#define pr_fmt(fmt) "ksmbd: " fmt
+#endif
+
+#define ksmbd_debug(type, fmt, ...) \
+ do { \
+ if (ksmbd_debug_types & KSMBD_DEBUG_##type) \
+ pr_info(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define UNICODE_LEN(x) ((x) * 2)
+
+#endif /* __KSMBD_GLOB_H */
diff --git a/fs/ksmbd/ksmbd_netlink.h b/fs/ksmbd/ksmbd_netlink.h
new file mode 100644
index 000000000000..2fbe2bc1e093
--- /dev/null
+++ b/fs/ksmbd/ksmbd_netlink.h
@@ -0,0 +1,395 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ *
+ * linux-ksmbd-devel@lists.sourceforge.net
+ */
+
+#ifndef _LINUX_KSMBD_SERVER_H
+#define _LINUX_KSMBD_SERVER_H
+
+#include <linux/types.h>
+
+/*
+ * This is a userspace ABI to communicate data between ksmbd and user IPC
+ * daemon using netlink. This is added to track and cache user account DB
+ * and share configuration info from userspace.
+ *
+ * - KSMBD_EVENT_HEARTBEAT_REQUEST(ksmbd_heartbeat)
+ * This event is to check whether user IPC daemon is alive. If user IPC
+ * daemon is dead, ksmbd keep existing connection till disconnecting and
+ * new connection will be denied.
+ *
+ * - KSMBD_EVENT_STARTING_UP(ksmbd_startup_request)
+ * This event is to receive the information that initializes the ksmbd
+ * server from the user IPC daemon and to start the server. The global
+ * section parameters are given from smb.conf as initialization
+ * information.
+ *
+ * - KSMBD_EVENT_SHUTTING_DOWN(ksmbd_shutdown_request)
+ * This event is to shutdown ksmbd server.
+ *
+ * - KSMBD_EVENT_LOGIN_REQUEST/RESPONSE(ksmbd_login_request/response)
+ * This event is to get user account info to user IPC daemon.
+ *
+ * - KSMBD_EVENT_SHARE_CONFIG_REQUEST/RESPONSE(ksmbd_share_config_request/response)
+ * This event is to get net share configuration info.
+ *
+ * - KSMBD_EVENT_TREE_CONNECT_REQUEST/RESPONSE(ksmbd_tree_connect_request/response)
+ * This event is to get session and tree connect info.
+ *
+ * - KSMBD_EVENT_TREE_DISCONNECT_REQUEST(ksmbd_tree_disconnect_request)
+ * This event is to send tree disconnect info to user IPC daemon.
+ *
+ * - KSMBD_EVENT_LOGOUT_REQUEST(ksmbd_logout_request)
+ * This event is to send logout request to user IPC daemon.
+ *
+ * - KSMBD_EVENT_RPC_REQUEST/RESPONSE(ksmbd_rpc_command)
+ * This event is to make DCE/RPC request like srvsvc, wkssvc, lsarpc,
+ * samr to be processed in userspace.
+ *
+ * - KSMBD_EVENT_SPNEGO_AUTHEN_REQUEST/RESPONSE(ksmbd_spnego_authen_request/response)
+ * This event is to make kerberos authentication to be processed in
+ * userspace.
+ */
+
+#define KSMBD_GENL_NAME "SMBD_GENL"
+#define KSMBD_GENL_VERSION 0x01
+
+#define KSMBD_REQ_MAX_ACCOUNT_NAME_SZ 48
+#define KSMBD_REQ_MAX_HASH_SZ 18
+#define KSMBD_REQ_MAX_SHARE_NAME 64
+
+/*
+ * IPC heartbeat frame to check whether user IPC daemon is alive.
+ */
+struct ksmbd_heartbeat {
+ __u32 handle;
+};
+
+/*
+ * Global config flags.
+ */
+#define KSMBD_GLOBAL_FLAG_INVALID (0)
+#define KSMBD_GLOBAL_FLAG_SMB2_LEASES BIT(0)
+#define KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION BIT(1)
+#define KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL BIT(2)
+
+/*
+ * IPC request for ksmbd server startup
+ */
+struct ksmbd_startup_request {
+ __u32 flags; /* Flags for global config */
+ __s32 signing; /* Signing enabled */
+ __s8 min_prot[16]; /* The minimum SMB protocol version */
+ __s8 max_prot[16]; /* The maximum SMB protocol version */
+ __s8 netbios_name[16];
+ __s8 work_group[64]; /* Workgroup */
+ __s8 server_string[64]; /* Server string */
+ __u16 tcp_port; /* tcp port */
+ __u16 ipc_timeout; /*
+ * specifies the number of seconds
+ * server will wait for the userspace to
+ * reply to heartbeat frames.
+ */
+ __u32 deadtime; /* Number of minutes of inactivity */
+ __u32 file_max; /* Limits the maximum number of open files */
+ __u32 smb2_max_write; /* MAX write size */
+ __u32 smb2_max_read; /* MAX read size */
+ __u32 smb2_max_trans; /* MAX trans size */
+ __u32 share_fake_fscaps; /*
+ * Support some special application that
+ * makes QFSINFO calls to check whether
+ * we set the SPARSE_FILES bit (0x40).
+ */
+ __u32 sub_auth[3]; /* Subauth value for Security ID */
+ __u32 ifc_list_sz; /* interfaces list size */
+ __s8 ____payload[];
+};
+
+#define KSMBD_STARTUP_CONFIG_INTERFACES(s) ((s)->____payload)
+
+/*
+ * IPC request to shutdown ksmbd server.
+ */
+struct ksmbd_shutdown_request {
+ __s32 reserved;
+};
+
+/*
+ * IPC user login request.
+ */
+struct ksmbd_login_request {
+ __u32 handle;
+ __s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
+};
+
+/*
+ * IPC user login response.
+ */
+struct ksmbd_login_response {
+ __u32 handle;
+ __u32 gid; /* group id */
+ __u32 uid; /* user id */
+ __s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
+ __u16 status;
+ __u16 hash_sz; /* hash size */
+ __s8 hash[KSMBD_REQ_MAX_HASH_SZ]; /* password hash */
+};
+
+/*
+ * IPC request to fetch net share config.
+ */
+struct ksmbd_share_config_request {
+ __u32 handle;
+ __s8 share_name[KSMBD_REQ_MAX_SHARE_NAME]; /* share name */
+};
+
+/*
+ * IPC response to the net share config request.
+ */
+struct ksmbd_share_config_response {
+ __u32 handle;
+ __u32 flags;
+ __u16 create_mask;
+ __u16 directory_mask;
+ __u16 force_create_mode;
+ __u16 force_directory_mode;
+ __u16 force_uid;
+ __u16 force_gid;
+ __u32 veto_list_sz;
+ __s8 ____payload[];
+};
+
+#define KSMBD_SHARE_CONFIG_VETO_LIST(s) ((s)->____payload)
+
+static inline char *
+ksmbd_share_config_path(struct ksmbd_share_config_response *sc)
+{
+ char *p = sc->____payload;
+
+ if (sc->veto_list_sz)
+ p += sc->veto_list_sz + 1;
+
+ return p;
+}
+
+/*
+ * IPC request for tree connection. This request include session and tree
+ * connect info from client.
+ */
+struct ksmbd_tree_connect_request {
+ __u32 handle;
+ __u16 account_flags;
+ __u16 flags;
+ __u64 session_id;
+ __u64 connect_id;
+ __s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ];
+ __s8 share[KSMBD_REQ_MAX_SHARE_NAME];
+ __s8 peer_addr[64];
+};
+
+/*
+ * IPC Response structure for tree connection.
+ */
+struct ksmbd_tree_connect_response {
+ __u32 handle;
+ __u16 status;
+ __u16 connection_flags;
+};
+
+/*
+ * IPC Request struture to disconnect tree connection.
+ */
+struct ksmbd_tree_disconnect_request {
+ __u64 session_id; /* session id */
+ __u64 connect_id; /* tree connection id */
+};
+
+/*
+ * IPC Response structure to logout user account.
+ */
+struct ksmbd_logout_request {
+ __s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
+};
+
+/*
+ * RPC command structure to send rpc request like srvsvc or wkssvc to
+ * IPC user daemon.
+ */
+struct ksmbd_rpc_command {
+ __u32 handle;
+ __u32 flags;
+ __u32 payload_sz;
+ __u8 payload[];
+};
+
+/*
+ * IPC Request Kerberos authentication
+ */
+struct ksmbd_spnego_authen_request {
+ __u32 handle;
+ __u16 spnego_blob_len; /* the length of spnego_blob */
+ __u8 spnego_blob[0]; /*
+ * the GSS token from SecurityBuffer of
+ * SMB2 SESSION SETUP request
+ */
+};
+
+/*
+ * Response data which includes the GSS token and the session key generated by
+ * user daemon.
+ */
+struct ksmbd_spnego_authen_response {
+ __u32 handle;
+ struct ksmbd_login_response login_response; /*
+ * the login response with
+ * a user identified by the
+ * GSS token from a client
+ */
+ __u16 session_key_len; /* the length of the session key */
+ __u16 spnego_blob_len; /*
+ * the length of the GSS token which will be
+ * stored in SecurityBuffer of SMB2 SESSION
+ * SETUP response
+ */
+ __u8 payload[]; /* session key + AP_REP */
+};
+
+/*
+ * This also used as NETLINK attribute type value.
+ *
+ * NOTE:
+ * Response message type value should be equal to
+ * request message type value + 1.
+ */
+enum ksmbd_event {
+ KSMBD_EVENT_UNSPEC = 0,
+ KSMBD_EVENT_HEARTBEAT_REQUEST,
+
+ KSMBD_EVENT_STARTING_UP,
+ KSMBD_EVENT_SHUTTING_DOWN,
+
+ KSMBD_EVENT_LOGIN_REQUEST,
+ KSMBD_EVENT_LOGIN_RESPONSE = 5,
+
+ KSMBD_EVENT_SHARE_CONFIG_REQUEST,
+ KSMBD_EVENT_SHARE_CONFIG_RESPONSE,
+
+ KSMBD_EVENT_TREE_CONNECT_REQUEST,
+ KSMBD_EVENT_TREE_CONNECT_RESPONSE,
+
+ KSMBD_EVENT_TREE_DISCONNECT_REQUEST = 10,
+
+ KSMBD_EVENT_LOGOUT_REQUEST,
+
+ KSMBD_EVENT_RPC_REQUEST,
+ KSMBD_EVENT_RPC_RESPONSE,
+
+ KSMBD_EVENT_SPNEGO_AUTHEN_REQUEST,
+ KSMBD_EVENT_SPNEGO_AUTHEN_RESPONSE = 15,
+
+ KSMBD_EVENT_MAX
+};
+
+/*
+ * Enumeration for IPC tree connect status.
+ */
+enum KSMBD_TREE_CONN_STATUS {
+ KSMBD_TREE_CONN_STATUS_OK = 0,
+ KSMBD_TREE_CONN_STATUS_NOMEM,
+ KSMBD_TREE_CONN_STATUS_NO_SHARE,
+ KSMBD_TREE_CONN_STATUS_NO_USER,
+ KSMBD_TREE_CONN_STATUS_INVALID_USER,
+ KSMBD_TREE_CONN_STATUS_HOST_DENIED = 5,
+ KSMBD_TREE_CONN_STATUS_CONN_EXIST,
+ KSMBD_TREE_CONN_STATUS_TOO_MANY_CONNS,
+ KSMBD_TREE_CONN_STATUS_TOO_MANY_SESSIONS,
+ KSMBD_TREE_CONN_STATUS_ERROR,
+};
+
+/*
+ * User config flags.
+ */
+#define KSMBD_USER_FLAG_INVALID (0)
+#define KSMBD_USER_FLAG_OK BIT(0)
+#define KSMBD_USER_FLAG_BAD_PASSWORD BIT(1)
+#define KSMBD_USER_FLAG_BAD_UID BIT(2)
+#define KSMBD_USER_FLAG_BAD_USER BIT(3)
+#define KSMBD_USER_FLAG_GUEST_ACCOUNT BIT(4)
+
+/*
+ * Share config flags.
+ */
+#define KSMBD_SHARE_FLAG_INVALID (0)
+#define KSMBD_SHARE_FLAG_AVAILABLE BIT(0)
+#define KSMBD_SHARE_FLAG_BROWSEABLE BIT(1)
+#define KSMBD_SHARE_FLAG_WRITEABLE BIT(2)
+#define KSMBD_SHARE_FLAG_READONLY BIT(3)
+#define KSMBD_SHARE_FLAG_GUEST_OK BIT(4)
+#define KSMBD_SHARE_FLAG_GUEST_ONLY BIT(5)
+#define KSMBD_SHARE_FLAG_STORE_DOS_ATTRS BIT(6)
+#define KSMBD_SHARE_FLAG_OPLOCKS BIT(7)
+#define KSMBD_SHARE_FLAG_PIPE BIT(8)
+#define KSMBD_SHARE_FLAG_HIDE_DOT_FILES BIT(9)
+#define KSMBD_SHARE_FLAG_INHERIT_OWNER BIT(10)
+#define KSMBD_SHARE_FLAG_STREAMS BIT(11)
+#define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12)
+#define KSMBD_SHARE_FLAG_ACL_XATTR BIT(13)
+
+/*
+ * Tree connect request flags.
+ */
+#define KSMBD_TREE_CONN_FLAG_REQUEST_SMB1 (0)
+#define KSMBD_TREE_CONN_FLAG_REQUEST_IPV6 BIT(0)
+#define KSMBD_TREE_CONN_FLAG_REQUEST_SMB2 BIT(1)
+
+/*
+ * Tree connect flags.
+ */
+#define KSMBD_TREE_CONN_FLAG_GUEST_ACCOUNT BIT(0)
+#define KSMBD_TREE_CONN_FLAG_READ_ONLY BIT(1)
+#define KSMBD_TREE_CONN_FLAG_WRITABLE BIT(2)
+#define KSMBD_TREE_CONN_FLAG_ADMIN_ACCOUNT BIT(3)
+
+/*
+ * RPC over IPC.
+ */
+#define KSMBD_RPC_METHOD_RETURN BIT(0)
+#define KSMBD_RPC_SRVSVC_METHOD_INVOKE BIT(1)
+#define KSMBD_RPC_SRVSVC_METHOD_RETURN (KSMBD_RPC_SRVSVC_METHOD_INVOKE | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_WKSSVC_METHOD_INVOKE BIT(2)
+#define KSMBD_RPC_WKSSVC_METHOD_RETURN (KSMBD_RPC_WKSSVC_METHOD_INVOKE | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_IOCTL_METHOD (BIT(3) | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_OPEN_METHOD BIT(4)
+#define KSMBD_RPC_WRITE_METHOD BIT(5)
+#define KSMBD_RPC_READ_METHOD (BIT(6) | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_CLOSE_METHOD BIT(7)
+#define KSMBD_RPC_RAP_METHOD (BIT(8) | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_RESTRICTED_CONTEXT BIT(9)
+#define KSMBD_RPC_SAMR_METHOD_INVOKE BIT(10)
+#define KSMBD_RPC_SAMR_METHOD_RETURN (KSMBD_RPC_SAMR_METHOD_INVOKE | KSMBD_RPC_METHOD_RETURN)
+#define KSMBD_RPC_LSARPC_METHOD_INVOKE BIT(11)
+#define KSMBD_RPC_LSARPC_METHOD_RETURN (KSMBD_RPC_LSARPC_METHOD_INVOKE | KSMBD_RPC_METHOD_RETURN)
+
+/*
+ * RPC status definitions.
+ */
+#define KSMBD_RPC_OK 0
+#define KSMBD_RPC_EBAD_FUNC 0x00000001
+#define KSMBD_RPC_EACCESS_DENIED 0x00000005
+#define KSMBD_RPC_EBAD_FID 0x00000006
+#define KSMBD_RPC_ENOMEM 0x00000008
+#define KSMBD_RPC_EBAD_DATA 0x0000000D
+#define KSMBD_RPC_ENOTIMPLEMENTED 0x00000040
+#define KSMBD_RPC_EINVALID_PARAMETER 0x00000057
+#define KSMBD_RPC_EMORE_DATA 0x000000EA
+#define KSMBD_RPC_EINVALID_LEVEL 0x0000007C
+#define KSMBD_RPC_SOME_NOT_MAPPED 0x00000107
+
+#define KSMBD_CONFIG_OPT_DISABLED 0
+#define KSMBD_CONFIG_OPT_ENABLED 1
+#define KSMBD_CONFIG_OPT_AUTO 2
+#define KSMBD_CONFIG_OPT_MANDATORY 3
+
+#endif /* _LINUX_KSMBD_SERVER_H */
diff --git a/fs/ksmbd/ksmbd_spnego_negtokeninit.asn1 b/fs/ksmbd/ksmbd_spnego_negtokeninit.asn1
new file mode 100644
index 000000000000..0065f191b54b
--- /dev/null
+++ b/fs/ksmbd/ksmbd_spnego_negtokeninit.asn1
@@ -0,0 +1,31 @@
+GSSAPI ::=
+ [APPLICATION 0] IMPLICIT SEQUENCE {
+ thisMech
+ OBJECT IDENTIFIER ({ksmbd_gssapi_this_mech}),
+ negotiationToken
+ NegotiationToken
+ }
+
+MechType ::= OBJECT IDENTIFIER ({ksmbd_neg_token_init_mech_type})
+
+MechTypeList ::= SEQUENCE OF MechType
+
+NegTokenInit ::=
+ SEQUENCE {
+ mechTypes
+ [0] MechTypeList,
+ reqFlags
+ [1] BIT STRING OPTIONAL,
+ mechToken
+ [2] OCTET STRING OPTIONAL ({ksmbd_neg_token_init_mech_token}),
+ mechListMIC
+ [3] OCTET STRING OPTIONAL
+ }
+
+NegotiationToken ::=
+ CHOICE {
+ negTokenInit
+ [0] NegTokenInit,
+ negTokenTarg
+ [1] ANY
+ }
diff --git a/fs/ksmbd/ksmbd_spnego_negtokentarg.asn1 b/fs/ksmbd/ksmbd_spnego_negtokentarg.asn1
new file mode 100644
index 000000000000..1151933e7b9c
--- /dev/null
+++ b/fs/ksmbd/ksmbd_spnego_negtokentarg.asn1
@@ -0,0 +1,19 @@
+GSSAPI ::=
+ CHOICE {
+ negTokenInit
+ [0] ANY,
+ negTokenTarg
+ [1] NegTokenTarg
+ }
+
+NegTokenTarg ::=
+ SEQUENCE {
+ negResult
+ [0] ENUMERATED OPTIONAL,
+ supportedMech
+ [1] OBJECT IDENTIFIER OPTIONAL,
+ responseToken
+ [2] OCTET STRING OPTIONAL ({ksmbd_neg_token_targ_resp_token}),
+ mechListMIC
+ [3] OCTET STRING OPTIONAL
+ }
diff --git a/fs/ksmbd/ksmbd_work.c b/fs/ksmbd/ksmbd_work.c
new file mode 100644
index 000000000000..fd58eb4809f6
--- /dev/null
+++ b/fs/ksmbd/ksmbd_work.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "server.h"
+#include "connection.h"
+#include "ksmbd_work.h"
+#include "mgmt/ksmbd_ida.h"
+
+static struct kmem_cache *work_cache;
+static struct workqueue_struct *ksmbd_wq;
+
+struct ksmbd_work *ksmbd_alloc_work_struct(void)
+{
+ struct ksmbd_work *work = kmem_cache_zalloc(work_cache, GFP_KERNEL);
+
+ if (work) {
+ work->compound_fid = KSMBD_NO_FID;
+ work->compound_pfid = KSMBD_NO_FID;
+ INIT_LIST_HEAD(&work->request_entry);
+ INIT_LIST_HEAD(&work->async_request_entry);
+ INIT_LIST_HEAD(&work->fp_entry);
+ INIT_LIST_HEAD(&work->interim_entry);
+ }
+ return work;
+}
+
+void ksmbd_free_work_struct(struct ksmbd_work *work)
+{
+ WARN_ON(work->saved_cred != NULL);
+
+ kvfree(work->response_buf);
+ kvfree(work->aux_payload_buf);
+ kfree(work->tr_buf);
+ kvfree(work->request_buf);
+ if (work->async_id)
+ ksmbd_release_id(&work->conn->async_ida, work->async_id);
+ kmem_cache_free(work_cache, work);
+}
+
+void ksmbd_work_pool_destroy(void)
+{
+ kmem_cache_destroy(work_cache);
+}
+
+int ksmbd_work_pool_init(void)
+{
+ work_cache = kmem_cache_create("ksmbd_work_cache",
+ sizeof(struct ksmbd_work), 0,
+ SLAB_HWCACHE_ALIGN, NULL);
+ if (!work_cache)
+ return -ENOMEM;
+ return 0;
+}
+
+int ksmbd_workqueue_init(void)
+{
+ ksmbd_wq = alloc_workqueue("ksmbd-io", 0, 0);
+ if (!ksmbd_wq)
+ return -ENOMEM;
+ return 0;
+}
+
+void ksmbd_workqueue_destroy(void)
+{
+ flush_workqueue(ksmbd_wq);
+ destroy_workqueue(ksmbd_wq);
+ ksmbd_wq = NULL;
+}
+
+bool ksmbd_queue_work(struct ksmbd_work *work)
+{
+ return queue_work(ksmbd_wq, &work->work);
+}
diff --git a/fs/ksmbd/ksmbd_work.h b/fs/ksmbd/ksmbd_work.h
new file mode 100644
index 000000000000..f7156bc50049
--- /dev/null
+++ b/fs/ksmbd/ksmbd_work.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_WORK_H__
+#define __KSMBD_WORK_H__
+
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+
+struct ksmbd_conn;
+struct ksmbd_session;
+struct ksmbd_tree_connect;
+
+enum {
+ KSMBD_WORK_ACTIVE = 0,
+ KSMBD_WORK_CANCELLED,
+ KSMBD_WORK_CLOSED,
+};
+
+/* one of these for every pending CIFS request at the connection */
+struct ksmbd_work {
+ /* Server corresponding to this mid */
+ struct ksmbd_conn *conn;
+ struct ksmbd_session *sess;
+ struct ksmbd_tree_connect *tcon;
+
+ /* Pointer to received SMB header */
+ void *request_buf;
+ /* Response buffer */
+ void *response_buf;
+
+ /* Read data buffer */
+ void *aux_payload_buf;
+
+ /* Next cmd hdr in compound req buf*/
+ int next_smb2_rcv_hdr_off;
+ /* Next cmd hdr in compound rsp buf*/
+ int next_smb2_rsp_hdr_off;
+
+ /*
+ * Current Local FID assigned compound response if SMB2 CREATE
+ * command is present in compound request
+ */
+ u64 compound_fid;
+ u64 compound_pfid;
+ u64 compound_sid;
+
+ const struct cred *saved_cred;
+
+ /* Number of granted credits */
+ unsigned int credits_granted;
+
+ /* response smb header size */
+ unsigned int resp_hdr_sz;
+ unsigned int response_sz;
+ /* Read data count */
+ unsigned int aux_payload_sz;
+
+ void *tr_buf;
+
+ unsigned char state;
+ /* Multiple responses for one request e.g. SMB ECHO */
+ bool multiRsp:1;
+ /* No response for cancelled request */
+ bool send_no_response:1;
+ /* Request is encrypted */
+ bool encrypted:1;
+ /* Is this SYNC or ASYNC ksmbd_work */
+ bool syncronous:1;
+ bool need_invalidate_rkey:1;
+
+ unsigned int remote_key;
+ /* cancel works */
+ int async_id;
+ void **cancel_argv;
+ void (*cancel_fn)(void **argv);
+
+ struct work_struct work;
+ /* List head at conn->requests */
+ struct list_head request_entry;
+ /* List head at conn->async_requests */
+ struct list_head async_request_entry;
+ struct list_head fp_entry;
+ struct list_head interim_entry;
+};
+
+/**
+ * ksmbd_resp_buf_next - Get next buffer on compound response.
+ * @work: smb work containing response buffer
+ */
+static inline void *ksmbd_resp_buf_next(struct ksmbd_work *work)
+{
+ return work->response_buf + work->next_smb2_rsp_hdr_off;
+}
+
+/**
+ * ksmbd_req_buf_next - Get next buffer on compound request.
+ * @work: smb work containing response buffer
+ */
+static inline void *ksmbd_req_buf_next(struct ksmbd_work *work)
+{
+ return work->request_buf + work->next_smb2_rcv_hdr_off;
+}
+
+struct ksmbd_work *ksmbd_alloc_work_struct(void);
+void ksmbd_free_work_struct(struct ksmbd_work *work);
+
+void ksmbd_work_pool_destroy(void);
+int ksmbd_work_pool_init(void);
+
+int ksmbd_workqueue_init(void);
+void ksmbd_workqueue_destroy(void);
+bool ksmbd_queue_work(struct ksmbd_work *work);
+
+#endif /* __KSMBD_WORK_H__ */
diff --git a/fs/ksmbd/mgmt/ksmbd_ida.c b/fs/ksmbd/mgmt/ksmbd_ida.c
new file mode 100644
index 000000000000..54194d959a5e
--- /dev/null
+++ b/fs/ksmbd/mgmt/ksmbd_ida.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include "ksmbd_ida.h"
+
+static inline int __acquire_id(struct ida *ida, int from, int to)
+{
+ return ida_simple_get(ida, from, to, GFP_KERNEL);
+}
+
+int ksmbd_acquire_smb2_tid(struct ida *ida)
+{
+ int id;
+
+ id = __acquire_id(ida, 1, 0xFFFFFFFF);
+
+ return id;
+}
+
+int ksmbd_acquire_smb2_uid(struct ida *ida)
+{
+ int id;
+
+ id = __acquire_id(ida, 1, 0);
+ if (id == 0xFFFE)
+ id = __acquire_id(ida, 1, 0);
+
+ return id;
+}
+
+int ksmbd_acquire_async_msg_id(struct ida *ida)
+{
+ return __acquire_id(ida, 1, 0);
+}
+
+int ksmbd_acquire_id(struct ida *ida)
+{
+ return __acquire_id(ida, 0, 0);
+}
+
+void ksmbd_release_id(struct ida *ida, int id)
+{
+ ida_simple_remove(ida, id);
+}
diff --git a/fs/ksmbd/mgmt/ksmbd_ida.h b/fs/ksmbd/mgmt/ksmbd_ida.h
new file mode 100644
index 000000000000..2bc07b16cfde
--- /dev/null
+++ b/fs/ksmbd/mgmt/ksmbd_ida.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_IDA_MANAGEMENT_H__
+#define __KSMBD_IDA_MANAGEMENT_H__
+
+#include <linux/slab.h>
+#include <linux/idr.h>
+
+/*
+ * 2.2.1.6.7 TID Generation
+ * The value 0xFFFF MUST NOT be used as a valid TID. All other
+ * possible values for TID, including zero (0x0000), are valid.
+ * The value 0xFFFF is used to specify all TIDs or no TID,
+ * depending upon the context in which it is used.
+ */
+int ksmbd_acquire_smb2_tid(struct ida *ida);
+
+/*
+ * 2.2.1.6.8 UID Generation
+ * The value 0xFFFE was declared reserved in the LAN Manager 1.0
+ * documentation, so a value of 0xFFFE SHOULD NOT be used as a
+ * valid UID.<21> All other possible values for a UID, excluding
+ * zero (0x0000), are valid.
+ */
+int ksmbd_acquire_smb2_uid(struct ida *ida);
+int ksmbd_acquire_async_msg_id(struct ida *ida);
+
+int ksmbd_acquire_id(struct ida *ida);
+
+void ksmbd_release_id(struct ida *ida, int id);
+#endif /* __KSMBD_IDA_MANAGEMENT_H__ */
diff --git a/fs/ksmbd/mgmt/share_config.c b/fs/ksmbd/mgmt/share_config.c
new file mode 100644
index 000000000000..cb72d30f5b71
--- /dev/null
+++ b/fs/ksmbd/mgmt/share_config.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/list.h>
+#include <linux/jhash.h>
+#include <linux/slab.h>
+#include <linux/rwsem.h>
+#include <linux/parser.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+
+#include "share_config.h"
+#include "user_config.h"
+#include "user_session.h"
+#include "../transport_ipc.h"
+
+#define SHARE_HASH_BITS 3
+static DEFINE_HASHTABLE(shares_table, SHARE_HASH_BITS);
+static DECLARE_RWSEM(shares_table_lock);
+
+struct ksmbd_veto_pattern {
+ char *pattern;
+ struct list_head list;
+};
+
+static unsigned int share_name_hash(char *name)
+{
+ return jhash(name, strlen(name), 0);
+}
+
+static void kill_share(struct ksmbd_share_config *share)
+{
+ while (!list_empty(&share->veto_list)) {
+ struct ksmbd_veto_pattern *p;
+
+ p = list_entry(share->veto_list.next,
+ struct ksmbd_veto_pattern,
+ list);
+ list_del(&p->list);
+ kfree(p->pattern);
+ kfree(p);
+ }
+
+ if (share->path)
+ path_put(&share->vfs_path);
+ kfree(share->name);
+ kfree(share->path);
+ kfree(share);
+}
+
+void __ksmbd_share_config_put(struct ksmbd_share_config *share)
+{
+ down_write(&shares_table_lock);
+ hash_del(&share->hlist);
+ up_write(&shares_table_lock);
+
+ kill_share(share);
+}
+
+static struct ksmbd_share_config *
+__get_share_config(struct ksmbd_share_config *share)
+{
+ if (!atomic_inc_not_zero(&share->refcount))
+ return NULL;
+ return share;
+}
+
+static struct ksmbd_share_config *__share_lookup(char *name)
+{
+ struct ksmbd_share_config *share;
+ unsigned int key = share_name_hash(name);
+
+ hash_for_each_possible(shares_table, share, hlist, key) {
+ if (!strcmp(name, share->name))
+ return share;
+ }
+ return NULL;
+}
+
+static int parse_veto_list(struct ksmbd_share_config *share,
+ char *veto_list,
+ int veto_list_sz)
+{
+ int sz = 0;
+
+ if (!veto_list_sz)
+ return 0;
+
+ while (veto_list_sz > 0) {
+ struct ksmbd_veto_pattern *p;
+
+ sz = strlen(veto_list);
+ if (!sz)
+ break;
+
+ p = kzalloc(sizeof(struct ksmbd_veto_pattern), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ p->pattern = kstrdup(veto_list, GFP_KERNEL);
+ if (!p->pattern) {
+ kfree(p);
+ return -ENOMEM;
+ }
+
+ list_add(&p->list, &share->veto_list);
+
+ veto_list += sz + 1;
+ veto_list_sz -= (sz + 1);
+ }
+
+ return 0;
+}
+
+static struct ksmbd_share_config *share_config_request(char *name)
+{
+ struct ksmbd_share_config_response *resp;
+ struct ksmbd_share_config *share = NULL;
+ struct ksmbd_share_config *lookup;
+ int ret;
+
+ resp = ksmbd_ipc_share_config_request(name);
+ if (!resp)
+ return NULL;
+
+ if (resp->flags == KSMBD_SHARE_FLAG_INVALID)
+ goto out;
+
+ share = kzalloc(sizeof(struct ksmbd_share_config), GFP_KERNEL);
+ if (!share)
+ goto out;
+
+ share->flags = resp->flags;
+ atomic_set(&share->refcount, 1);
+ INIT_LIST_HEAD(&share->veto_list);
+ share->name = kstrdup(name, GFP_KERNEL);
+
+ if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
+ share->path = kstrdup(ksmbd_share_config_path(resp),
+ GFP_KERNEL);
+ if (share->path)
+ share->path_sz = strlen(share->path);
+ share->create_mask = resp->create_mask;
+ share->directory_mask = resp->directory_mask;
+ share->force_create_mode = resp->force_create_mode;
+ share->force_directory_mode = resp->force_directory_mode;
+ share->force_uid = resp->force_uid;
+ share->force_gid = resp->force_gid;
+ ret = parse_veto_list(share,
+ KSMBD_SHARE_CONFIG_VETO_LIST(resp),
+ resp->veto_list_sz);
+ if (!ret && share->path) {
+ ret = kern_path(share->path, 0, &share->vfs_path);
+ if (ret) {
+ ksmbd_debug(SMB, "failed to access '%s'\n",
+ share->path);
+ /* Avoid put_path() */
+ kfree(share->path);
+ share->path = NULL;
+ }
+ }
+ if (ret || !share->name) {
+ kill_share(share);
+ share = NULL;
+ goto out;
+ }
+ }
+
+ down_write(&shares_table_lock);
+ lookup = __share_lookup(name);
+ if (lookup)
+ lookup = __get_share_config(lookup);
+ if (!lookup) {
+ hash_add(shares_table, &share->hlist, share_name_hash(name));
+ } else {
+ kill_share(share);
+ share = lookup;
+ }
+ up_write(&shares_table_lock);
+
+out:
+ kvfree(resp);
+ return share;
+}
+
+static void strtolower(char *share_name)
+{
+ while (*share_name) {
+ *share_name = tolower(*share_name);
+ share_name++;
+ }
+}
+
+struct ksmbd_share_config *ksmbd_share_config_get(char *name)
+{
+ struct ksmbd_share_config *share;
+
+ strtolower(name);
+
+ down_read(&shares_table_lock);
+ share = __share_lookup(name);
+ if (share)
+ share = __get_share_config(share);
+ up_read(&shares_table_lock);
+
+ if (share)
+ return share;
+ return share_config_request(name);
+}
+
+bool ksmbd_share_veto_filename(struct ksmbd_share_config *share,
+ const char *filename)
+{
+ struct ksmbd_veto_pattern *p;
+
+ list_for_each_entry(p, &share->veto_list, list) {
+ if (match_wildcard(p->pattern, filename))
+ return true;
+ }
+ return false;
+}
+
+void ksmbd_share_configs_cleanup(void)
+{
+ struct ksmbd_share_config *share;
+ struct hlist_node *tmp;
+ int i;
+
+ down_write(&shares_table_lock);
+ hash_for_each_safe(shares_table, i, tmp, share, hlist) {
+ hash_del(&share->hlist);
+ kill_share(share);
+ }
+ up_write(&shares_table_lock);
+}
diff --git a/fs/ksmbd/mgmt/share_config.h b/fs/ksmbd/mgmt/share_config.h
new file mode 100644
index 000000000000..953befc94e84
--- /dev/null
+++ b/fs/ksmbd/mgmt/share_config.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __SHARE_CONFIG_MANAGEMENT_H__
+#define __SHARE_CONFIG_MANAGEMENT_H__
+
+#include <linux/workqueue.h>
+#include <linux/hashtable.h>
+#include <linux/path.h>
+
+struct ksmbd_share_config {
+ char *name;
+ char *path;
+
+ unsigned int path_sz;
+ unsigned int flags;
+ struct list_head veto_list;
+
+ struct path vfs_path;
+
+ atomic_t refcount;
+ struct hlist_node hlist;
+ unsigned short create_mask;
+ unsigned short directory_mask;
+ unsigned short force_create_mode;
+ unsigned short force_directory_mode;
+ unsigned short force_uid;
+ unsigned short force_gid;
+};
+
+#define KSMBD_SHARE_INVALID_UID ((__u16)-1)
+#define KSMBD_SHARE_INVALID_GID ((__u16)-1)
+
+static inline int share_config_create_mode(struct ksmbd_share_config *share,
+ umode_t posix_mode)
+{
+ if (!share->force_create_mode) {
+ if (!posix_mode)
+ return share->create_mask;
+ else
+ return posix_mode & share->create_mask;
+ }
+ return share->force_create_mode & share->create_mask;
+}
+
+static inline int share_config_directory_mode(struct ksmbd_share_config *share,
+ umode_t posix_mode)
+{
+ if (!share->force_directory_mode) {
+ if (!posix_mode)
+ return share->directory_mask;
+ else
+ return posix_mode & share->directory_mask;
+ }
+
+ return share->force_directory_mode & share->directory_mask;
+}
+
+static inline int test_share_config_flag(struct ksmbd_share_config *share,
+ int flag)
+{
+ return share->flags & flag;
+}
+
+void __ksmbd_share_config_put(struct ksmbd_share_config *share);
+
+static inline void ksmbd_share_config_put(struct ksmbd_share_config *share)
+{
+ if (!atomic_dec_and_test(&share->refcount))
+ return;
+ __ksmbd_share_config_put(share);
+}
+
+struct ksmbd_share_config *ksmbd_share_config_get(char *name);
+bool ksmbd_share_veto_filename(struct ksmbd_share_config *share,
+ const char *filename);
+void ksmbd_share_configs_cleanup(void);
+
+#endif /* __SHARE_CONFIG_MANAGEMENT_H__ */
diff --git a/fs/ksmbd/mgmt/tree_connect.c b/fs/ksmbd/mgmt/tree_connect.c
new file mode 100644
index 000000000000..0d28e723a28c
--- /dev/null
+++ b/fs/ksmbd/mgmt/tree_connect.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/xarray.h>
+
+#include "../transport_ipc.h"
+#include "../connection.h"
+
+#include "tree_connect.h"
+#include "user_config.h"
+#include "share_config.h"
+#include "user_session.h"
+
+struct ksmbd_tree_conn_status
+ksmbd_tree_conn_connect(struct ksmbd_session *sess, char *share_name)
+{
+ struct ksmbd_tree_conn_status status = {-EINVAL, NULL};
+ struct ksmbd_tree_connect_response *resp = NULL;
+ struct ksmbd_share_config *sc;
+ struct ksmbd_tree_connect *tree_conn = NULL;
+ struct sockaddr *peer_addr;
+ int ret;
+
+ sc = ksmbd_share_config_get(share_name);
+ if (!sc)
+ return status;
+
+ tree_conn = kzalloc(sizeof(struct ksmbd_tree_connect), GFP_KERNEL);
+ if (!tree_conn) {
+ status.ret = -ENOMEM;
+ goto out_error;
+ }
+
+ tree_conn->id = ksmbd_acquire_tree_conn_id(sess);
+ if (tree_conn->id < 0) {
+ status.ret = -EINVAL;
+ goto out_error;
+ }
+
+ peer_addr = KSMBD_TCP_PEER_SOCKADDR(sess->conn);
+ resp = ksmbd_ipc_tree_connect_request(sess,
+ sc,
+ tree_conn,
+ peer_addr);
+ if (!resp) {
+ status.ret = -EINVAL;
+ goto out_error;
+ }
+
+ status.ret = resp->status;
+ if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
+ goto out_error;
+
+ tree_conn->flags = resp->connection_flags;
+ tree_conn->user = sess->user;
+ tree_conn->share_conf = sc;
+ status.tree_conn = tree_conn;
+
+ ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn,
+ GFP_KERNEL));
+ if (ret) {
+ status.ret = -ENOMEM;
+ goto out_error;
+ }
+ kvfree(resp);
+ return status;
+
+out_error:
+ if (tree_conn)
+ ksmbd_release_tree_conn_id(sess, tree_conn->id);
+ ksmbd_share_config_put(sc);
+ kfree(tree_conn);
+ kvfree(resp);
+ return status;
+}
+
+int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
+ struct ksmbd_tree_connect *tree_conn)
+{
+ int ret;
+
+ ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id);
+ ksmbd_release_tree_conn_id(sess, tree_conn->id);
+ xa_erase(&sess->tree_conns, tree_conn->id);
+ ksmbd_share_config_put(tree_conn->share_conf);
+ kfree(tree_conn);
+ return ret;
+}
+
+struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess,
+ unsigned int id)
+{
+ return xa_load(&sess->tree_conns, id);
+}
+
+struct ksmbd_share_config *ksmbd_tree_conn_share(struct ksmbd_session *sess,
+ unsigned int id)
+{
+ struct ksmbd_tree_connect *tc;
+
+ tc = ksmbd_tree_conn_lookup(sess, id);
+ if (tc)
+ return tc->share_conf;
+ return NULL;
+}
+
+int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess)
+{
+ int ret = 0;
+ struct ksmbd_tree_connect *tc;
+ unsigned long id;
+
+ xa_for_each(&sess->tree_conns, id, tc)
+ ret |= ksmbd_tree_conn_disconnect(sess, tc);
+ xa_destroy(&sess->tree_conns);
+ return ret;
+}
diff --git a/fs/ksmbd/mgmt/tree_connect.h b/fs/ksmbd/mgmt/tree_connect.h
new file mode 100644
index 000000000000..18e2a996e0aa
--- /dev/null
+++ b/fs/ksmbd/mgmt/tree_connect.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __TREE_CONNECT_MANAGEMENT_H__
+#define __TREE_CONNECT_MANAGEMENT_H__
+
+#include <linux/hashtable.h>
+
+#include "../ksmbd_netlink.h"
+
+struct ksmbd_share_config;
+struct ksmbd_user;
+
+struct ksmbd_tree_connect {
+ int id;
+
+ unsigned int flags;
+ struct ksmbd_share_config *share_conf;
+ struct ksmbd_user *user;
+
+ struct list_head list;
+
+ int maximal_access;
+ bool posix_extensions;
+};
+
+struct ksmbd_tree_conn_status {
+ unsigned int ret;
+ struct ksmbd_tree_connect *tree_conn;
+};
+
+static inline int test_tree_conn_flag(struct ksmbd_tree_connect *tree_conn,
+ int flag)
+{
+ return tree_conn->flags & flag;
+}
+
+struct ksmbd_session;
+
+struct ksmbd_tree_conn_status
+ksmbd_tree_conn_connect(struct ksmbd_session *sess, char *share_name);
+
+int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
+ struct ksmbd_tree_connect *tree_conn);
+
+struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess,
+ unsigned int id);
+
+struct ksmbd_share_config *ksmbd_tree_conn_share(struct ksmbd_session *sess,
+ unsigned int id);
+
+int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess);
+
+#endif /* __TREE_CONNECT_MANAGEMENT_H__ */
diff --git a/fs/ksmbd/mgmt/user_config.c b/fs/ksmbd/mgmt/user_config.c
new file mode 100644
index 000000000000..d21629ae5c89
--- /dev/null
+++ b/fs/ksmbd/mgmt/user_config.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+#include "user_config.h"
+#include "../transport_ipc.h"
+
+struct ksmbd_user *ksmbd_login_user(const char *account)
+{
+ struct ksmbd_login_response *resp;
+ struct ksmbd_user *user = NULL;
+
+ resp = ksmbd_ipc_login_request(account);
+ if (!resp)
+ return NULL;
+
+ if (!(resp->status & KSMBD_USER_FLAG_OK))
+ goto out;
+
+ user = ksmbd_alloc_user(resp);
+out:
+ kvfree(resp);
+ return user;
+}
+
+struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp)
+{
+ struct ksmbd_user *user = NULL;
+
+ user = kmalloc(sizeof(struct ksmbd_user), GFP_KERNEL);
+ if (!user)
+ return NULL;
+
+ user->name = kstrdup(resp->account, GFP_KERNEL);
+ user->flags = resp->status;
+ user->gid = resp->gid;
+ user->uid = resp->uid;
+ user->passkey_sz = resp->hash_sz;
+ user->passkey = kmalloc(resp->hash_sz, GFP_KERNEL);
+ if (user->passkey)
+ memcpy(user->passkey, resp->hash, resp->hash_sz);
+
+ if (!user->name || !user->passkey) {
+ kfree(user->name);
+ kfree(user->passkey);
+ kfree(user);
+ user = NULL;
+ }
+ return user;
+}
+
+void ksmbd_free_user(struct ksmbd_user *user)
+{
+ ksmbd_ipc_logout_request(user->name);
+ kfree(user->name);
+ kfree(user->passkey);
+ kfree(user);
+}
+
+int ksmbd_anonymous_user(struct ksmbd_user *user)
+{
+ if (user->name[0] == '\0')
+ return 1;
+ return 0;
+}
diff --git a/fs/ksmbd/mgmt/user_config.h b/fs/ksmbd/mgmt/user_config.h
new file mode 100644
index 000000000000..b2bb074a0150
--- /dev/null
+++ b/fs/ksmbd/mgmt/user_config.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __USER_CONFIG_MANAGEMENT_H__
+#define __USER_CONFIG_MANAGEMENT_H__
+
+#include "../glob.h"
+
+struct ksmbd_user {
+ unsigned short flags;
+
+ unsigned int uid;
+ unsigned int gid;
+
+ char *name;
+
+ size_t passkey_sz;
+ char *passkey;
+};
+
+static inline bool user_guest(struct ksmbd_user *user)
+{
+ return user->flags & KSMBD_USER_FLAG_GUEST_ACCOUNT;
+}
+
+static inline void set_user_flag(struct ksmbd_user *user, int flag)
+{
+ user->flags |= flag;
+}
+
+static inline int test_user_flag(struct ksmbd_user *user, int flag)
+{
+ return user->flags & flag;
+}
+
+static inline void set_user_guest(struct ksmbd_user *user)
+{
+}
+
+static inline char *user_passkey(struct ksmbd_user *user)
+{
+ return user->passkey;
+}
+
+static inline char *user_name(struct ksmbd_user *user)
+{
+ return user->name;
+}
+
+static inline unsigned int user_uid(struct ksmbd_user *user)
+{
+ return user->uid;
+}
+
+static inline unsigned int user_gid(struct ksmbd_user *user)
+{
+ return user->gid;
+}
+
+struct ksmbd_user *ksmbd_login_user(const char *account);
+struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp);
+void ksmbd_free_user(struct ksmbd_user *user);
+int ksmbd_anonymous_user(struct ksmbd_user *user);
+#endif /* __USER_CONFIG_MANAGEMENT_H__ */
diff --git a/fs/ksmbd/mgmt/user_session.c b/fs/ksmbd/mgmt/user_session.c
new file mode 100644
index 000000000000..8d8ffd8c6f19
--- /dev/null
+++ b/fs/ksmbd/mgmt/user_session.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/rwsem.h>
+#include <linux/xarray.h>
+
+#include "ksmbd_ida.h"
+#include "user_session.h"
+#include "user_config.h"
+#include "tree_connect.h"
+#include "../transport_ipc.h"
+#include "../connection.h"
+#include "../vfs_cache.h"
+
+static DEFINE_IDA(session_ida);
+
+#define SESSION_HASH_BITS 3
+static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS);
+static DECLARE_RWSEM(sessions_table_lock);
+
+struct ksmbd_session_rpc {
+ int id;
+ unsigned int method;
+ struct list_head list;
+};
+
+static void free_channel_list(struct ksmbd_session *sess)
+{
+ struct channel *chann, *tmp;
+
+ list_for_each_entry_safe(chann, tmp, &sess->ksmbd_chann_list,
+ chann_list) {
+ list_del(&chann->chann_list);
+ kfree(chann);
+ }
+}
+
+static void __session_rpc_close(struct ksmbd_session *sess,
+ struct ksmbd_session_rpc *entry)
+{
+ struct ksmbd_rpc_command *resp;
+
+ resp = ksmbd_rpc_close(sess, entry->id);
+ if (!resp)
+ pr_err("Unable to close RPC pipe %d\n", entry->id);
+
+ kvfree(resp);
+ ksmbd_rpc_id_free(entry->id);
+ kfree(entry);
+}
+
+static void ksmbd_session_rpc_clear_list(struct ksmbd_session *sess)
+{
+ struct ksmbd_session_rpc *entry;
+
+ while (!list_empty(&sess->rpc_handle_list)) {
+ entry = list_entry(sess->rpc_handle_list.next,
+ struct ksmbd_session_rpc,
+ list);
+
+ list_del(&entry->list);
+ __session_rpc_close(sess, entry);
+ }
+}
+
+static int __rpc_method(char *rpc_name)
+{
+ if (!strcmp(rpc_name, "\\srvsvc") || !strcmp(rpc_name, "srvsvc"))
+ return KSMBD_RPC_SRVSVC_METHOD_INVOKE;
+
+ if (!strcmp(rpc_name, "\\wkssvc") || !strcmp(rpc_name, "wkssvc"))
+ return KSMBD_RPC_WKSSVC_METHOD_INVOKE;
+
+ if (!strcmp(rpc_name, "LANMAN") || !strcmp(rpc_name, "lanman"))
+ return KSMBD_RPC_RAP_METHOD;
+
+ if (!strcmp(rpc_name, "\\samr") || !strcmp(rpc_name, "samr"))
+ return KSMBD_RPC_SAMR_METHOD_INVOKE;
+
+ if (!strcmp(rpc_name, "\\lsarpc") || !strcmp(rpc_name, "lsarpc"))
+ return KSMBD_RPC_LSARPC_METHOD_INVOKE;
+
+ pr_err("Unsupported RPC: %s\n", rpc_name);
+ return 0;
+}
+
+int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name)
+{
+ struct ksmbd_session_rpc *entry;
+ struct ksmbd_rpc_command *resp;
+ int method;
+
+ method = __rpc_method(rpc_name);
+ if (!method)
+ return -EINVAL;
+
+ entry = kzalloc(sizeof(struct ksmbd_session_rpc), GFP_KERNEL);
+ if (!entry)
+ return -EINVAL;
+
+ list_add(&entry->list, &sess->rpc_handle_list);
+ entry->method = method;
+ entry->id = ksmbd_ipc_id_alloc();
+ if (entry->id < 0)
+ goto error;
+
+ resp = ksmbd_rpc_open(sess, entry->id);
+ if (!resp)
+ goto error;
+
+ kvfree(resp);
+ return entry->id;
+error:
+ list_del(&entry->list);
+ kfree(entry);
+ return -EINVAL;
+}
+
+void ksmbd_session_rpc_close(struct ksmbd_session *sess, int id)
+{
+ struct ksmbd_session_rpc *entry;
+
+ list_for_each_entry(entry, &sess->rpc_handle_list, list) {
+ if (entry->id == id) {
+ list_del(&entry->list);
+ __session_rpc_close(sess, entry);
+ break;
+ }
+ }
+}
+
+int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id)
+{
+ struct ksmbd_session_rpc *entry;
+
+ list_for_each_entry(entry, &sess->rpc_handle_list, list) {
+ if (entry->id == id)
+ return entry->method;
+ }
+ return 0;
+}
+
+void ksmbd_session_destroy(struct ksmbd_session *sess)
+{
+ if (!sess)
+ return;
+
+ if (!atomic_dec_and_test(&sess->refcnt))
+ return;
+
+ list_del(&sess->sessions_entry);
+
+ down_write(&sessions_table_lock);
+ hash_del(&sess->hlist);
+ up_write(&sessions_table_lock);
+
+ if (sess->user)
+ ksmbd_free_user(sess->user);
+
+ ksmbd_tree_conn_session_logoff(sess);
+ ksmbd_destroy_file_table(&sess->file_table);
+ ksmbd_session_rpc_clear_list(sess);
+ free_channel_list(sess);
+ kfree(sess->Preauth_HashValue);
+ ksmbd_release_id(&session_ida, sess->id);
+ kfree(sess);
+}
+
+static struct ksmbd_session *__session_lookup(unsigned long long id)
+{
+ struct ksmbd_session *sess;
+
+ hash_for_each_possible(sessions_table, sess, hlist, id) {
+ if (id == sess->id)
+ return sess;
+ }
+ return NULL;
+}
+
+void ksmbd_session_register(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess)
+{
+ sess->conn = conn;
+ list_add(&sess->sessions_entry, &conn->sessions);
+}
+
+void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
+{
+ struct ksmbd_session *sess;
+
+ while (!list_empty(&conn->sessions)) {
+ sess = list_entry(conn->sessions.next,
+ struct ksmbd_session,
+ sessions_entry);
+
+ ksmbd_session_destroy(sess);
+ }
+}
+
+static bool ksmbd_session_id_match(struct ksmbd_session *sess,
+ unsigned long long id)
+{
+ return sess->id == id;
+}
+
+struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
+ unsigned long long id)
+{
+ struct ksmbd_session *sess = NULL;
+
+ list_for_each_entry(sess, &conn->sessions, sessions_entry) {
+ if (ksmbd_session_id_match(sess, id))
+ return sess;
+ }
+ return NULL;
+}
+
+int get_session(struct ksmbd_session *sess)
+{
+ return atomic_inc_not_zero(&sess->refcnt);
+}
+
+void put_session(struct ksmbd_session *sess)
+{
+ if (atomic_dec_and_test(&sess->refcnt))
+ pr_err("get/%s seems to be mismatched.", __func__);
+}
+
+struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)
+{
+ struct ksmbd_session *sess;
+
+ down_read(&sessions_table_lock);
+ sess = __session_lookup(id);
+ if (sess) {
+ if (!get_session(sess))
+ sess = NULL;
+ }
+ up_read(&sessions_table_lock);
+
+ return sess;
+}
+
+struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
+ unsigned long long id)
+{
+ struct ksmbd_session *sess;
+
+ sess = ksmbd_session_lookup(conn, id);
+ if (!sess && conn->binding)
+ sess = ksmbd_session_lookup_slowpath(id);
+ return sess;
+}
+
+struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
+ u64 sess_id)
+{
+ struct preauth_session *sess;
+
+ sess = kmalloc(sizeof(struct preauth_session), GFP_KERNEL);
+ if (!sess)
+ return NULL;
+
+ sess->id = sess_id;
+ memcpy(sess->Preauth_HashValue, conn->preauth_info->Preauth_HashValue,
+ PREAUTH_HASHVALUE_SIZE);
+ list_add(&sess->preauth_entry, &conn->preauth_sess_table);
+
+ return sess;
+}
+
+static bool ksmbd_preauth_session_id_match(struct preauth_session *sess,
+ unsigned long long id)
+{
+ return sess->id == id;
+}
+
+struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn,
+ unsigned long long id)
+{
+ struct preauth_session *sess = NULL;
+
+ list_for_each_entry(sess, &conn->preauth_sess_table, preauth_entry) {
+ if (ksmbd_preauth_session_id_match(sess, id))
+ return sess;
+ }
+ return NULL;
+}
+
+static int __init_smb2_session(struct ksmbd_session *sess)
+{
+ int id = ksmbd_acquire_smb2_uid(&session_ida);
+
+ if (id < 0)
+ return -EINVAL;
+ sess->id = id;
+ return 0;
+}
+
+static struct ksmbd_session *__session_create(int protocol)
+{
+ struct ksmbd_session *sess;
+ int ret;
+
+ sess = kzalloc(sizeof(struct ksmbd_session), GFP_KERNEL);
+ if (!sess)
+ return NULL;
+
+ if (ksmbd_init_file_table(&sess->file_table))
+ goto error;
+
+ set_session_flag(sess, protocol);
+ INIT_LIST_HEAD(&sess->sessions_entry);
+ xa_init(&sess->tree_conns);
+ INIT_LIST_HEAD(&sess->ksmbd_chann_list);
+ INIT_LIST_HEAD(&sess->rpc_handle_list);
+ sess->sequence_number = 1;
+ atomic_set(&sess->refcnt, 1);
+
+ switch (protocol) {
+ case CIFDS_SESSION_FLAG_SMB2:
+ ret = __init_smb2_session(sess);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ goto error;
+
+ ida_init(&sess->tree_conn_ida);
+
+ if (protocol == CIFDS_SESSION_FLAG_SMB2) {
+ down_write(&sessions_table_lock);
+ hash_add(sessions_table, &sess->hlist, sess->id);
+ up_write(&sessions_table_lock);
+ }
+ return sess;
+
+error:
+ ksmbd_session_destroy(sess);
+ return NULL;
+}
+
+struct ksmbd_session *ksmbd_smb2_session_create(void)
+{
+ return __session_create(CIFDS_SESSION_FLAG_SMB2);
+}
+
+int ksmbd_acquire_tree_conn_id(struct ksmbd_session *sess)
+{
+ int id = -EINVAL;
+
+ if (test_session_flag(sess, CIFDS_SESSION_FLAG_SMB2))
+ id = ksmbd_acquire_smb2_tid(&sess->tree_conn_ida);
+
+ return id;
+}
+
+void ksmbd_release_tree_conn_id(struct ksmbd_session *sess, int id)
+{
+ if (id >= 0)
+ ksmbd_release_id(&sess->tree_conn_ida, id);
+}
diff --git a/fs/ksmbd/mgmt/user_session.h b/fs/ksmbd/mgmt/user_session.h
new file mode 100644
index 000000000000..82289c3cbd2b
--- /dev/null
+++ b/fs/ksmbd/mgmt/user_session.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __USER_SESSION_MANAGEMENT_H__
+#define __USER_SESSION_MANAGEMENT_H__
+
+#include <linux/hashtable.h>
+#include <linux/xarray.h>
+
+#include "../smb_common.h"
+#include "../ntlmssp.h"
+
+#define CIFDS_SESSION_FLAG_SMB2 BIT(1)
+
+#define PREAUTH_HASHVALUE_SIZE 64
+
+struct ksmbd_file_table;
+
+struct channel {
+ __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE];
+ struct ksmbd_conn *conn;
+ struct list_head chann_list;
+};
+
+struct preauth_session {
+ __u8 Preauth_HashValue[PREAUTH_HASHVALUE_SIZE];
+ u64 id;
+ struct list_head preauth_entry;
+};
+
+struct ksmbd_session {
+ u64 id;
+
+ struct ksmbd_user *user;
+ struct ksmbd_conn *conn;
+ unsigned int sequence_number;
+ unsigned int flags;
+
+ bool sign;
+ bool enc;
+ bool is_anonymous;
+
+ int state;
+ __u8 *Preauth_HashValue;
+
+ struct ntlmssp_auth ntlmssp;
+ char sess_key[CIFS_KEY_SIZE];
+
+ struct hlist_node hlist;
+ struct list_head ksmbd_chann_list;
+ struct xarray tree_conns;
+ struct ida tree_conn_ida;
+ struct list_head rpc_handle_list;
+
+ __u8 smb3encryptionkey[SMB3_ENC_DEC_KEY_SIZE];
+ __u8 smb3decryptionkey[SMB3_ENC_DEC_KEY_SIZE];
+ __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE];
+
+ struct list_head sessions_entry;
+ struct ksmbd_file_table file_table;
+ atomic_t refcnt;
+};
+
+static inline int test_session_flag(struct ksmbd_session *sess, int bit)
+{
+ return sess->flags & bit;
+}
+
+static inline void set_session_flag(struct ksmbd_session *sess, int bit)
+{
+ sess->flags |= bit;
+}
+
+static inline void clear_session_flag(struct ksmbd_session *sess, int bit)
+{
+ sess->flags &= ~bit;
+}
+
+struct ksmbd_session *ksmbd_smb2_session_create(void);
+
+void ksmbd_session_destroy(struct ksmbd_session *sess);
+
+struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id);
+struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
+ unsigned long long id);
+void ksmbd_session_register(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess);
+void ksmbd_sessions_deregister(struct ksmbd_conn *conn);
+struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
+ unsigned long long id);
+struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
+ u64 sess_id);
+struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn,
+ unsigned long long id);
+
+int ksmbd_acquire_tree_conn_id(struct ksmbd_session *sess);
+void ksmbd_release_tree_conn_id(struct ksmbd_session *sess, int id);
+
+int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name);
+void ksmbd_session_rpc_close(struct ksmbd_session *sess, int id);
+int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id);
+int get_session(struct ksmbd_session *sess);
+void put_session(struct ksmbd_session *sess);
+#endif /* __USER_SESSION_MANAGEMENT_H__ */
diff --git a/fs/ksmbd/misc.c b/fs/ksmbd/misc.c
new file mode 100644
index 000000000000..0b307ca28a19
--- /dev/null
+++ b/fs/ksmbd/misc.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/xattr.h>
+#include <linux/fs.h>
+
+#include "misc.h"
+#include "smb_common.h"
+#include "connection.h"
+#include "vfs.h"
+
+#include "mgmt/share_config.h"
+
+/**
+ * match_pattern() - compare a string with a pattern which might include
+ * wildcard '*' and '?'
+ * TODO : implement consideration about DOS_DOT, DOS_QM and DOS_STAR
+ *
+ * @string: string to compare with a pattern
+ * @len: string length
+ * @pattern: pattern string which might include wildcard '*' and '?'
+ *
+ * Return: 0 if pattern matched with the string, otherwise non zero value
+ */
+int match_pattern(const char *str, size_t len, const char *pattern)
+{
+ const char *s = str;
+ const char *p = pattern;
+ bool star = false;
+
+ while (*s && len) {
+ switch (*p) {
+ case '?':
+ s++;
+ len--;
+ p++;
+ break;
+ case '*':
+ star = true;
+ str = s;
+ if (!*++p)
+ return true;
+ pattern = p;
+ break;
+ default:
+ if (tolower(*s) == tolower(*p)) {
+ s++;
+ len--;
+ p++;
+ } else {
+ if (!star)
+ return false;
+ str++;
+ s = str;
+ p = pattern;
+ }
+ break;
+ }
+ }
+
+ if (*p == '*')
+ ++p;
+ return !*p;
+}
+
+/*
+ * is_char_allowed() - check for valid character
+ * @ch: input character to be checked
+ *
+ * Return: 1 if char is allowed, otherwise 0
+ */
+static inline int is_char_allowed(char ch)
+{
+ /* check for control chars, wildcards etc. */
+ if (!(ch & 0x80) &&
+ (ch <= 0x1f ||
+ ch == '?' || ch == '"' || ch == '<' ||
+ ch == '>' || ch == '|' || ch == '*'))
+ return 0;
+
+ return 1;
+}
+
+int ksmbd_validate_filename(char *filename)
+{
+ while (*filename) {
+ char c = *filename;
+
+ filename++;
+ if (!is_char_allowed(c)) {
+ ksmbd_debug(VFS, "File name validation failed: 0x%x\n", c);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+static int ksmbd_validate_stream_name(char *stream_name)
+{
+ while (*stream_name) {
+ char c = *stream_name;
+
+ stream_name++;
+ if (c == '/' || c == ':' || c == '\\') {
+ pr_err("Stream name validation failed: %c\n", c);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+int parse_stream_name(char *filename, char **stream_name, int *s_type)
+{
+ char *stream_type;
+ char *s_name;
+ int rc = 0;
+
+ s_name = filename;
+ filename = strsep(&s_name, ":");
+ ksmbd_debug(SMB, "filename : %s, streams : %s\n", filename, s_name);
+ if (strchr(s_name, ':')) {
+ stream_type = s_name;
+ s_name = strsep(&stream_type, ":");
+
+ rc = ksmbd_validate_stream_name(s_name);
+ if (rc < 0) {
+ rc = -ENOENT;
+ goto out;
+ }
+
+ ksmbd_debug(SMB, "stream name : %s, stream type : %s\n", s_name,
+ stream_type);
+ if (!strncasecmp("$data", stream_type, 5))
+ *s_type = DATA_STREAM;
+ else if (!strncasecmp("$index_allocation", stream_type, 17))
+ *s_type = DIR_STREAM;
+ else
+ rc = -ENOENT;
+ }
+
+ *stream_name = s_name;
+out:
+ return rc;
+}
+
+/**
+ * convert_to_nt_pathname() - extract and return windows path string
+ * whose share directory prefix was removed from file path
+ * @filename : unix filename
+ * @sharepath: share path string
+ *
+ * Return : windows path string or error
+ */
+
+char *convert_to_nt_pathname(char *filename, char *sharepath)
+{
+ char *ab_pathname;
+ int len, name_len;
+
+ name_len = strlen(filename);
+ ab_pathname = kmalloc(name_len, GFP_KERNEL);
+ if (!ab_pathname)
+ return NULL;
+
+ ab_pathname[0] = '\\';
+ ab_pathname[1] = '\0';
+
+ len = strlen(sharepath);
+ if (!strncmp(filename, sharepath, len) && name_len != len) {
+ strscpy(ab_pathname, &filename[len], name_len);
+ ksmbd_conv_path_to_windows(ab_pathname);
+ }
+
+ return ab_pathname;
+}
+
+int get_nlink(struct kstat *st)
+{
+ int nlink;
+
+ nlink = st->nlink;
+ if (S_ISDIR(st->mode))
+ nlink--;
+
+ return nlink;
+}
+
+void ksmbd_conv_path_to_unix(char *path)
+{
+ strreplace(path, '\\', '/');
+}
+
+void ksmbd_strip_last_slash(char *path)
+{
+ int len = strlen(path);
+
+ while (len && path[len - 1] == '/') {
+ path[len - 1] = '\0';
+ len--;
+ }
+}
+
+void ksmbd_conv_path_to_windows(char *path)
+{
+ strreplace(path, '/', '\\');
+}
+
+/**
+ * ksmbd_extract_sharename() - get share name from tree connect request
+ * @treename: buffer containing tree name and share name
+ *
+ * Return: share name on success, otherwise error
+ */
+char *ksmbd_extract_sharename(char *treename)
+{
+ char *name = treename;
+ char *dst;
+ char *pos = strrchr(name, '\\');
+
+ if (pos)
+ name = (pos + 1);
+
+ /* caller has to free the memory */
+ dst = kstrdup(name, GFP_KERNEL);
+ if (!dst)
+ return ERR_PTR(-ENOMEM);
+ return dst;
+}
+
+/**
+ * convert_to_unix_name() - convert windows name to unix format
+ * @path: name to be converted
+ * @tid: tree id of mathing share
+ *
+ * Return: converted name on success, otherwise NULL
+ */
+char *convert_to_unix_name(struct ksmbd_share_config *share, char *name)
+{
+ int no_slash = 0, name_len, path_len;
+ char *new_name;
+
+ if (name[0] == '/')
+ name++;
+
+ path_len = share->path_sz;
+ name_len = strlen(name);
+ new_name = kmalloc(path_len + name_len + 2, GFP_KERNEL);
+ if (!new_name)
+ return new_name;
+
+ memcpy(new_name, share->path, path_len);
+ if (new_name[path_len - 1] != '/') {
+ new_name[path_len] = '/';
+ no_slash = 1;
+ }
+
+ memcpy(new_name + path_len + no_slash, name, name_len);
+ path_len += name_len + no_slash;
+ new_name[path_len] = 0x00;
+ return new_name;
+}
+
+char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,
+ const struct nls_table *local_nls,
+ int *conv_len)
+{
+ char *conv;
+ int sz = min(4 * d_info->name_len, PATH_MAX);
+
+ if (!sz)
+ return NULL;
+
+ conv = kmalloc(sz, GFP_KERNEL);
+ if (!conv)
+ return NULL;
+
+ /* XXX */
+ *conv_len = smbConvertToUTF16((__le16 *)conv, d_info->name,
+ d_info->name_len, local_nls, 0);
+ *conv_len *= 2;
+
+ /* We allocate buffer twice bigger than needed. */
+ conv[*conv_len] = 0x00;
+ conv[*conv_len + 1] = 0x00;
+ return conv;
+}
+
+/*
+ * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units)
+ * into Unix UTC (based 1970-01-01, in seconds).
+ */
+struct timespec64 ksmbd_NTtimeToUnix(__le64 ntutc)
+{
+ struct timespec64 ts;
+
+ /* Subtract the NTFS time offset, then convert to 1s intervals. */
+ s64 t = le64_to_cpu(ntutc) - NTFS_TIME_OFFSET;
+ u64 abs_t;
+
+ /*
+ * Unfortunately can not use normal 64 bit division on 32 bit arch, but
+ * the alternative, do_div, does not work with negative numbers so have
+ * to special case them
+ */
+ if (t < 0) {
+ abs_t = -t;
+ ts.tv_nsec = do_div(abs_t, 10000000) * 100;
+ ts.tv_nsec = -ts.tv_nsec;
+ ts.tv_sec = -abs_t;
+ } else {
+ abs_t = t;
+ ts.tv_nsec = do_div(abs_t, 10000000) * 100;
+ ts.tv_sec = abs_t;
+ }
+
+ return ts;
+}
+
+/* Convert the Unix UTC into NT UTC. */
+inline u64 ksmbd_UnixTimeToNT(struct timespec64 t)
+{
+ /* Convert to 100ns intervals and then add the NTFS time offset. */
+ return (u64)t.tv_sec * 10000000 + t.tv_nsec / 100 + NTFS_TIME_OFFSET;
+}
+
+inline long long ksmbd_systime(void)
+{
+ struct timespec64 ts;
+
+ ktime_get_real_ts64(&ts);
+ return ksmbd_UnixTimeToNT(ts);
+}
diff --git a/fs/ksmbd/misc.h b/fs/ksmbd/misc.h
new file mode 100644
index 000000000000..af8717d4d85b
--- /dev/null
+++ b/fs/ksmbd/misc.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_MISC_H__
+#define __KSMBD_MISC_H__
+
+struct ksmbd_share_config;
+struct nls_table;
+struct kstat;
+struct ksmbd_file;
+
+int match_pattern(const char *str, size_t len, const char *pattern);
+int ksmbd_validate_filename(char *filename);
+int parse_stream_name(char *filename, char **stream_name, int *s_type);
+char *convert_to_nt_pathname(char *filename, char *sharepath);
+int get_nlink(struct kstat *st);
+void ksmbd_conv_path_to_unix(char *path);
+void ksmbd_strip_last_slash(char *path);
+void ksmbd_conv_path_to_windows(char *path);
+char *ksmbd_extract_sharename(char *treename);
+char *convert_to_unix_name(struct ksmbd_share_config *share, char *name);
+
+#define KSMBD_DIR_INFO_ALIGNMENT 8
+struct ksmbd_dir_info;
+char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,
+ const struct nls_table *local_nls,
+ int *conv_len);
+
+#define NTFS_TIME_OFFSET ((u64)(369 * 365 + 89) * 24 * 3600 * 10000000)
+struct timespec64 ksmbd_NTtimeToUnix(__le64 ntutc);
+u64 ksmbd_UnixTimeToNT(struct timespec64 t);
+long long ksmbd_systime(void);
+#endif /* __KSMBD_MISC_H__ */
diff --git a/fs/ksmbd/ndr.c b/fs/ksmbd/ndr.c
new file mode 100644
index 000000000000..2243a2c64b37
--- /dev/null
+++ b/fs/ksmbd/ndr.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Samsung Electronics Co., Ltd.
+ * Author(s): Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <linux/fs.h>
+
+#include "glob.h"
+#include "ndr.h"
+
+static inline char *ndr_get_field(struct ndr *n)
+{
+ return n->data + n->offset;
+}
+
+static int try_to_realloc_ndr_blob(struct ndr *n, size_t sz)
+{
+ char *data;
+
+ data = krealloc(n->data, n->offset + sz + 1024, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ n->data = data;
+ n->length += 1024;
+ memset(n->data + n->offset, 0, 1024);
+ return 0;
+}
+
+static void ndr_write_int16(struct ndr *n, __u16 value)
+{
+ if (n->length <= n->offset + sizeof(value))
+ try_to_realloc_ndr_blob(n, sizeof(value));
+
+ *(__le16 *)ndr_get_field(n) = cpu_to_le16(value);
+ n->offset += sizeof(value);
+}
+
+static void ndr_write_int32(struct ndr *n, __u32 value)
+{
+ if (n->length <= n->offset + sizeof(value))
+ try_to_realloc_ndr_blob(n, sizeof(value));
+
+ *(__le32 *)ndr_get_field(n) = cpu_to_le32(value);
+ n->offset += sizeof(value);
+}
+
+static void ndr_write_int64(struct ndr *n, __u64 value)
+{
+ if (n->length <= n->offset + sizeof(value))
+ try_to_realloc_ndr_blob(n, sizeof(value));
+
+ *(__le64 *)ndr_get_field(n) = cpu_to_le64(value);
+ n->offset += sizeof(value);
+}
+
+static int ndr_write_bytes(struct ndr *n, void *value, size_t sz)
+{
+ if (n->length <= n->offset + sz)
+ try_to_realloc_ndr_blob(n, sz);
+
+ memcpy(ndr_get_field(n), value, sz);
+ n->offset += sz;
+ return 0;
+}
+
+static int ndr_write_string(struct ndr *n, char *value)
+{
+ size_t sz;
+
+ sz = strlen(value) + 1;
+ if (n->length <= n->offset + sz)
+ try_to_realloc_ndr_blob(n, sz);
+
+ memcpy(ndr_get_field(n), value, sz);
+ n->offset += sz;
+ n->offset = ALIGN(n->offset, 2);
+ return 0;
+}
+
+static int ndr_read_string(struct ndr *n, void *value, size_t sz)
+{
+ int len = strnlen(ndr_get_field(n), sz);
+
+ memcpy(value, ndr_get_field(n), len);
+ len++;
+ n->offset += len;
+ n->offset = ALIGN(n->offset, 2);
+ return 0;
+}
+
+static int ndr_read_bytes(struct ndr *n, void *value, size_t sz)
+{
+ memcpy(value, ndr_get_field(n), sz);
+ n->offset += sz;
+ return 0;
+}
+
+static __u16 ndr_read_int16(struct ndr *n)
+{
+ __u16 ret;
+
+ ret = le16_to_cpu(*(__le16 *)ndr_get_field(n));
+ n->offset += sizeof(__u16);
+ return ret;
+}
+
+static __u32 ndr_read_int32(struct ndr *n)
+{
+ __u32 ret;
+
+ ret = le32_to_cpu(*(__le32 *)ndr_get_field(n));
+ n->offset += sizeof(__u32);
+ return ret;
+}
+
+static __u64 ndr_read_int64(struct ndr *n)
+{
+ __u64 ret;
+
+ ret = le64_to_cpu(*(__le64 *)ndr_get_field(n));
+ n->offset += sizeof(__u64);
+ return ret;
+}
+
+int ndr_encode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da)
+{
+ char hex_attr[12] = {0};
+
+ n->offset = 0;
+ n->length = 1024;
+ n->data = kzalloc(n->length, GFP_KERNEL);
+ if (!n->data)
+ return -ENOMEM;
+
+ if (da->version == 3) {
+ snprintf(hex_attr, 10, "0x%x", da->attr);
+ ndr_write_string(n, hex_attr);
+ } else {
+ ndr_write_string(n, "");
+ }
+ ndr_write_int16(n, da->version);
+ ndr_write_int32(n, da->version);
+
+ ndr_write_int32(n, da->flags);
+ ndr_write_int32(n, da->attr);
+ if (da->version == 3) {
+ ndr_write_int32(n, da->ea_size);
+ ndr_write_int64(n, da->size);
+ ndr_write_int64(n, da->alloc_size);
+ } else {
+ ndr_write_int64(n, da->itime);
+ }
+ ndr_write_int64(n, da->create_time);
+ if (da->version == 3)
+ ndr_write_int64(n, da->change_time);
+ return 0;
+}
+
+int ndr_decode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da)
+{
+ char *hex_attr;
+ int version2;
+
+ hex_attr = kzalloc(n->length, GFP_KERNEL);
+ if (!hex_attr)
+ return -ENOMEM;
+
+ n->offset = 0;
+ ndr_read_string(n, hex_attr, n->length);
+ kfree(hex_attr);
+ da->version = ndr_read_int16(n);
+
+ if (da->version != 3 && da->version != 4) {
+ pr_err("v%d version is not supported\n", da->version);
+ return -EINVAL;
+ }
+
+ version2 = ndr_read_int32(n);
+ if (da->version != version2) {
+ pr_err("ndr version mismatched(version: %d, version2: %d)\n",
+ da->version, version2);
+ return -EINVAL;
+ }
+
+ ndr_read_int32(n);
+ da->attr = ndr_read_int32(n);
+ if (da->version == 4) {
+ da->itime = ndr_read_int64(n);
+ da->create_time = ndr_read_int64(n);
+ } else {
+ ndr_read_int32(n);
+ ndr_read_int64(n);
+ ndr_read_int64(n);
+ da->create_time = ndr_read_int64(n);
+ ndr_read_int64(n);
+ }
+
+ return 0;
+}
+
+static int ndr_encode_posix_acl_entry(struct ndr *n, struct xattr_smb_acl *acl)
+{
+ int i;
+
+ ndr_write_int32(n, acl->count);
+ n->offset = ALIGN(n->offset, 8);
+ ndr_write_int32(n, acl->count);
+ ndr_write_int32(n, 0);
+
+ for (i = 0; i < acl->count; i++) {
+ n->offset = ALIGN(n->offset, 8);
+ ndr_write_int16(n, acl->entries[i].type);
+ ndr_write_int16(n, acl->entries[i].type);
+
+ if (acl->entries[i].type == SMB_ACL_USER) {
+ n->offset = ALIGN(n->offset, 8);
+ ndr_write_int64(n, acl->entries[i].uid);
+ } else if (acl->entries[i].type == SMB_ACL_GROUP) {
+ n->offset = ALIGN(n->offset, 8);
+ ndr_write_int64(n, acl->entries[i].gid);
+ }
+
+ /* push permission */
+ ndr_write_int32(n, acl->entries[i].perm);
+ }
+
+ return 0;
+}
+
+int ndr_encode_posix_acl(struct ndr *n,
+ struct user_namespace *user_ns,
+ struct inode *inode,
+ struct xattr_smb_acl *acl,
+ struct xattr_smb_acl *def_acl)
+{
+ int ref_id = 0x00020000;
+
+ n->offset = 0;
+ n->length = 1024;
+ n->data = kzalloc(n->length, GFP_KERNEL);
+ if (!n->data)
+ return -ENOMEM;
+
+ if (acl) {
+ /* ACL ACCESS */
+ ndr_write_int32(n, ref_id);
+ ref_id += 4;
+ } else {
+ ndr_write_int32(n, 0);
+ }
+
+ if (def_acl) {
+ /* DEFAULT ACL ACCESS */
+ ndr_write_int32(n, ref_id);
+ ref_id += 4;
+ } else {
+ ndr_write_int32(n, 0);
+ }
+
+ ndr_write_int64(n, from_kuid(user_ns, inode->i_uid));
+ ndr_write_int64(n, from_kgid(user_ns, inode->i_gid));
+ ndr_write_int32(n, inode->i_mode);
+
+ if (acl) {
+ ndr_encode_posix_acl_entry(n, acl);
+ if (def_acl)
+ ndr_encode_posix_acl_entry(n, def_acl);
+ }
+ return 0;
+}
+
+int ndr_encode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl)
+{
+ int ref_id = 0x00020004;
+
+ n->offset = 0;
+ n->length = 2048;
+ n->data = kzalloc(n->length, GFP_KERNEL);
+ if (!n->data)
+ return -ENOMEM;
+
+ ndr_write_int16(n, acl->version);
+ ndr_write_int32(n, acl->version);
+ ndr_write_int16(n, 2);
+ ndr_write_int32(n, ref_id);
+
+ /* push hash type and hash 64bytes */
+ ndr_write_int16(n, acl->hash_type);
+ ndr_write_bytes(n, acl->hash, XATTR_SD_HASH_SIZE);
+ ndr_write_bytes(n, acl->desc, acl->desc_len);
+ ndr_write_int64(n, acl->current_time);
+ ndr_write_bytes(n, acl->posix_acl_hash, XATTR_SD_HASH_SIZE);
+
+ /* push ndr for security descriptor */
+ ndr_write_bytes(n, acl->sd_buf, acl->sd_size);
+
+ return 0;
+}
+
+int ndr_decode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl)
+{
+ int version2;
+
+ n->offset = 0;
+ acl->version = ndr_read_int16(n);
+ if (acl->version != 4) {
+ pr_err("v%d version is not supported\n", acl->version);
+ return -EINVAL;
+ }
+
+ version2 = ndr_read_int32(n);
+ if (acl->version != version2) {
+ pr_err("ndr version mismatched(version: %d, version2: %d)\n",
+ acl->version, version2);
+ return -EINVAL;
+ }
+
+ /* Read Level */
+ ndr_read_int16(n);
+ /* Read Ref Id */
+ ndr_read_int32(n);
+ acl->hash_type = ndr_read_int16(n);
+ ndr_read_bytes(n, acl->hash, XATTR_SD_HASH_SIZE);
+
+ ndr_read_bytes(n, acl->desc, 10);
+ if (strncmp(acl->desc, "posix_acl", 9)) {
+ pr_err("Invalid acl description : %s\n", acl->desc);
+ return -EINVAL;
+ }
+
+ /* Read Time */
+ ndr_read_int64(n);
+ /* Read Posix ACL hash */
+ ndr_read_bytes(n, acl->posix_acl_hash, XATTR_SD_HASH_SIZE);
+ acl->sd_size = n->length - n->offset;
+ acl->sd_buf = kzalloc(acl->sd_size, GFP_KERNEL);
+ if (!acl->sd_buf)
+ return -ENOMEM;
+
+ ndr_read_bytes(n, acl->sd_buf, acl->sd_size);
+
+ return 0;
+}
diff --git a/fs/ksmbd/ndr.h b/fs/ksmbd/ndr.h
new file mode 100644
index 000000000000..60ca265d1bb0
--- /dev/null
+++ b/fs/ksmbd/ndr.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020 Samsung Electronics Co., Ltd.
+ * Author(s): Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+struct ndr {
+ char *data;
+ int offset;
+ int length;
+};
+
+#define NDR_NTSD_OFFSETOF 0xA0
+
+int ndr_encode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da);
+int ndr_decode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da);
+int ndr_encode_posix_acl(struct ndr *n, struct user_namespace *user_ns,
+ struct inode *inode, struct xattr_smb_acl *acl,
+ struct xattr_smb_acl *def_acl);
+int ndr_encode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl);
+int ndr_encode_v3_ntacl(struct ndr *n, struct xattr_ntacl *acl);
+int ndr_decode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl);
diff --git a/fs/ksmbd/nterr.h b/fs/ksmbd/nterr.h
new file mode 100644
index 000000000000..2f358f88a018
--- /dev/null
+++ b/fs/ksmbd/nterr.h
@@ -0,0 +1,543 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Unix SMB/Netbios implementation.
+ * Version 1.9.
+ * NT error code constants
+ * Copyright (C) Andrew Tridgell 1992-2000
+ * Copyright (C) John H Terpstra 1996-2000
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ * Copyright (C) Paul Ashton 1998-2000
+ */
+
+#ifndef _NTERR_H
+#define _NTERR_H
+
+/* Win32 Status codes. */
+#define NT_STATUS_MORE_ENTRIES 0x0105
+#define NT_ERROR_INVALID_PARAMETER 0x0057
+#define NT_ERROR_INSUFFICIENT_BUFFER 0x007a
+#define NT_STATUS_1804 0x070c
+#define NT_STATUS_NOTIFY_ENUM_DIR 0x010c
+#define NT_STATUS_INVALID_LOCK_RANGE (0xC0000000 | 0x01a1)
+/*
+ * Win32 Error codes extracted using a loop in smbclient then printing a netmon
+ * sniff to a file.
+ */
+
+#define NT_STATUS_OK 0x0000
+#define NT_STATUS_SOME_UNMAPPED 0x0107
+#define NT_STATUS_BUFFER_OVERFLOW 0x80000005
+#define NT_STATUS_NO_MORE_ENTRIES 0x8000001a
+#define NT_STATUS_MEDIA_CHANGED 0x8000001c
+#define NT_STATUS_END_OF_MEDIA 0x8000001e
+#define NT_STATUS_MEDIA_CHECK 0x80000020
+#define NT_STATUS_NO_DATA_DETECTED 0x8000001c
+#define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002d
+#define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288
+#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000288
+#define NT_STATUS_UNSUCCESSFUL (0xC0000000 | 0x0001)
+#define NT_STATUS_NOT_IMPLEMENTED (0xC0000000 | 0x0002)
+#define NT_STATUS_INVALID_INFO_CLASS (0xC0000000 | 0x0003)
+#define NT_STATUS_INFO_LENGTH_MISMATCH (0xC0000000 | 0x0004)
+#define NT_STATUS_ACCESS_VIOLATION (0xC0000000 | 0x0005)
+#define NT_STATUS_IN_PAGE_ERROR (0xC0000000 | 0x0006)
+#define NT_STATUS_PAGEFILE_QUOTA (0xC0000000 | 0x0007)
+#define NT_STATUS_INVALID_HANDLE (0xC0000000 | 0x0008)
+#define NT_STATUS_BAD_INITIAL_STACK (0xC0000000 | 0x0009)
+#define NT_STATUS_BAD_INITIAL_PC (0xC0000000 | 0x000a)
+#define NT_STATUS_INVALID_CID (0xC0000000 | 0x000b)
+#define NT_STATUS_TIMER_NOT_CANCELED (0xC0000000 | 0x000c)
+#define NT_STATUS_INVALID_PARAMETER (0xC0000000 | 0x000d)
+#define NT_STATUS_NO_SUCH_DEVICE (0xC0000000 | 0x000e)
+#define NT_STATUS_NO_SUCH_FILE (0xC0000000 | 0x000f)
+#define NT_STATUS_INVALID_DEVICE_REQUEST (0xC0000000 | 0x0010)
+#define NT_STATUS_END_OF_FILE (0xC0000000 | 0x0011)
+#define NT_STATUS_WRONG_VOLUME (0xC0000000 | 0x0012)
+#define NT_STATUS_NO_MEDIA_IN_DEVICE (0xC0000000 | 0x0013)
+#define NT_STATUS_UNRECOGNIZED_MEDIA (0xC0000000 | 0x0014)
+#define NT_STATUS_NONEXISTENT_SECTOR (0xC0000000 | 0x0015)
+#define NT_STATUS_MORE_PROCESSING_REQUIRED (0xC0000000 | 0x0016)
+#define NT_STATUS_NO_MEMORY (0xC0000000 | 0x0017)
+#define NT_STATUS_CONFLICTING_ADDRESSES (0xC0000000 | 0x0018)
+#define NT_STATUS_NOT_MAPPED_VIEW (0xC0000000 | 0x0019)
+#define NT_STATUS_UNABLE_TO_FREE_VM (0x80000000 | 0x001a)
+#define NT_STATUS_UNABLE_TO_DELETE_SECTION (0xC0000000 | 0x001b)
+#define NT_STATUS_INVALID_SYSTEM_SERVICE (0xC0000000 | 0x001c)
+#define NT_STATUS_ILLEGAL_INSTRUCTION (0xC0000000 | 0x001d)
+#define NT_STATUS_INVALID_LOCK_SEQUENCE (0xC0000000 | 0x001e)
+#define NT_STATUS_INVALID_VIEW_SIZE (0xC0000000 | 0x001f)
+#define NT_STATUS_INVALID_FILE_FOR_SECTION (0xC0000000 | 0x0020)
+#define NT_STATUS_ALREADY_COMMITTED (0xC0000000 | 0x0021)
+#define NT_STATUS_ACCESS_DENIED (0xC0000000 | 0x0022)
+#define NT_STATUS_BUFFER_TOO_SMALL (0xC0000000 | 0x0023)
+#define NT_STATUS_OBJECT_TYPE_MISMATCH (0xC0000000 | 0x0024)
+#define NT_STATUS_NONCONTINUABLE_EXCEPTION (0xC0000000 | 0x0025)
+#define NT_STATUS_INVALID_DISPOSITION (0xC0000000 | 0x0026)
+#define NT_STATUS_UNWIND (0xC0000000 | 0x0027)
+#define NT_STATUS_BAD_STACK (0xC0000000 | 0x0028)
+#define NT_STATUS_INVALID_UNWIND_TARGET (0xC0000000 | 0x0029)
+#define NT_STATUS_NOT_LOCKED (0xC0000000 | 0x002a)
+#define NT_STATUS_PARITY_ERROR (0xC0000000 | 0x002b)
+#define NT_STATUS_UNABLE_TO_DECOMMIT_VM (0xC0000000 | 0x002c)
+#define NT_STATUS_NOT_COMMITTED (0xC0000000 | 0x002d)
+#define NT_STATUS_INVALID_PORT_ATTRIBUTES (0xC0000000 | 0x002e)
+#define NT_STATUS_PORT_MESSAGE_TOO_LONG (0xC0000000 | 0x002f)
+#define NT_STATUS_INVALID_PARAMETER_MIX (0xC0000000 | 0x0030)
+#define NT_STATUS_INVALID_QUOTA_LOWER (0xC0000000 | 0x0031)
+#define NT_STATUS_DISK_CORRUPT_ERROR (0xC0000000 | 0x0032)
+#define NT_STATUS_OBJECT_NAME_INVALID (0xC0000000 | 0x0033)
+#define NT_STATUS_OBJECT_NAME_NOT_FOUND (0xC0000000 | 0x0034)
+#define NT_STATUS_OBJECT_NAME_COLLISION (0xC0000000 | 0x0035)
+#define NT_STATUS_HANDLE_NOT_WAITABLE (0xC0000000 | 0x0036)
+#define NT_STATUS_PORT_DISCONNECTED (0xC0000000 | 0x0037)
+#define NT_STATUS_DEVICE_ALREADY_ATTACHED (0xC0000000 | 0x0038)
+#define NT_STATUS_OBJECT_PATH_INVALID (0xC0000000 | 0x0039)
+#define NT_STATUS_OBJECT_PATH_NOT_FOUND (0xC0000000 | 0x003a)
+#define NT_STATUS_OBJECT_PATH_SYNTAX_BAD (0xC0000000 | 0x003b)
+#define NT_STATUS_DATA_OVERRUN (0xC0000000 | 0x003c)
+#define NT_STATUS_DATA_LATE_ERROR (0xC0000000 | 0x003d)
+#define NT_STATUS_DATA_ERROR (0xC0000000 | 0x003e)
+#define NT_STATUS_CRC_ERROR (0xC0000000 | 0x003f)
+#define NT_STATUS_SECTION_TOO_BIG (0xC0000000 | 0x0040)
+#define NT_STATUS_PORT_CONNECTION_REFUSED (0xC0000000 | 0x0041)
+#define NT_STATUS_INVALID_PORT_HANDLE (0xC0000000 | 0x0042)
+#define NT_STATUS_SHARING_VIOLATION (0xC0000000 | 0x0043)
+#define NT_STATUS_QUOTA_EXCEEDED (0xC0000000 | 0x0044)
+#define NT_STATUS_INVALID_PAGE_PROTECTION (0xC0000000 | 0x0045)
+#define NT_STATUS_MUTANT_NOT_OWNED (0xC0000000 | 0x0046)
+#define NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED (0xC0000000 | 0x0047)
+#define NT_STATUS_PORT_ALREADY_SET (0xC0000000 | 0x0048)
+#define NT_STATUS_SECTION_NOT_IMAGE (0xC0000000 | 0x0049)
+#define NT_STATUS_SUSPEND_COUNT_EXCEEDED (0xC0000000 | 0x004a)
+#define NT_STATUS_THREAD_IS_TERMINATING (0xC0000000 | 0x004b)
+#define NT_STATUS_BAD_WORKING_SET_LIMIT (0xC0000000 | 0x004c)
+#define NT_STATUS_INCOMPATIBLE_FILE_MAP (0xC0000000 | 0x004d)
+#define NT_STATUS_SECTION_PROTECTION (0xC0000000 | 0x004e)
+#define NT_STATUS_EAS_NOT_SUPPORTED (0xC0000000 | 0x004f)
+#define NT_STATUS_EA_TOO_LARGE (0xC0000000 | 0x0050)
+#define NT_STATUS_NONEXISTENT_EA_ENTRY (0xC0000000 | 0x0051)
+#define NT_STATUS_NO_EAS_ON_FILE (0xC0000000 | 0x0052)
+#define NT_STATUS_EA_CORRUPT_ERROR (0xC0000000 | 0x0053)
+#define NT_STATUS_FILE_LOCK_CONFLICT (0xC0000000 | 0x0054)
+#define NT_STATUS_LOCK_NOT_GRANTED (0xC0000000 | 0x0055)
+#define NT_STATUS_DELETE_PENDING (0xC0000000 | 0x0056)
+#define NT_STATUS_CTL_FILE_NOT_SUPPORTED (0xC0000000 | 0x0057)
+#define NT_STATUS_UNKNOWN_REVISION (0xC0000000 | 0x0058)
+#define NT_STATUS_REVISION_MISMATCH (0xC0000000 | 0x0059)
+#define NT_STATUS_INVALID_OWNER (0xC0000000 | 0x005a)
+#define NT_STATUS_INVALID_PRIMARY_GROUP (0xC0000000 | 0x005b)
+#define NT_STATUS_NO_IMPERSONATION_TOKEN (0xC0000000 | 0x005c)
+#define NT_STATUS_CANT_DISABLE_MANDATORY (0xC0000000 | 0x005d)
+#define NT_STATUS_NO_LOGON_SERVERS (0xC0000000 | 0x005e)
+#define NT_STATUS_NO_SUCH_LOGON_SESSION (0xC0000000 | 0x005f)
+#define NT_STATUS_NO_SUCH_PRIVILEGE (0xC0000000 | 0x0060)
+#define NT_STATUS_PRIVILEGE_NOT_HELD (0xC0000000 | 0x0061)
+#define NT_STATUS_INVALID_ACCOUNT_NAME (0xC0000000 | 0x0062)
+#define NT_STATUS_USER_EXISTS (0xC0000000 | 0x0063)
+#define NT_STATUS_NO_SUCH_USER (0xC0000000 | 0x0064)
+#define NT_STATUS_GROUP_EXISTS (0xC0000000 | 0x0065)
+#define NT_STATUS_NO_SUCH_GROUP (0xC0000000 | 0x0066)
+#define NT_STATUS_MEMBER_IN_GROUP (0xC0000000 | 0x0067)
+#define NT_STATUS_MEMBER_NOT_IN_GROUP (0xC0000000 | 0x0068)
+#define NT_STATUS_LAST_ADMIN (0xC0000000 | 0x0069)
+#define NT_STATUS_WRONG_PASSWORD (0xC0000000 | 0x006a)
+#define NT_STATUS_ILL_FORMED_PASSWORD (0xC0000000 | 0x006b)
+#define NT_STATUS_PASSWORD_RESTRICTION (0xC0000000 | 0x006c)
+#define NT_STATUS_LOGON_FAILURE (0xC0000000 | 0x006d)
+#define NT_STATUS_ACCOUNT_RESTRICTION (0xC0000000 | 0x006e)
+#define NT_STATUS_INVALID_LOGON_HOURS (0xC0000000 | 0x006f)
+#define NT_STATUS_INVALID_WORKSTATION (0xC0000000 | 0x0070)
+#define NT_STATUS_PASSWORD_EXPIRED (0xC0000000 | 0x0071)
+#define NT_STATUS_ACCOUNT_DISABLED (0xC0000000 | 0x0072)
+#define NT_STATUS_NONE_MAPPED (0xC0000000 | 0x0073)
+#define NT_STATUS_TOO_MANY_LUIDS_REQUESTED (0xC0000000 | 0x0074)
+#define NT_STATUS_LUIDS_EXHAUSTED (0xC0000000 | 0x0075)
+#define NT_STATUS_INVALID_SUB_AUTHORITY (0xC0000000 | 0x0076)
+#define NT_STATUS_INVALID_ACL (0xC0000000 | 0x0077)
+#define NT_STATUS_INVALID_SID (0xC0000000 | 0x0078)
+#define NT_STATUS_INVALID_SECURITY_DESCR (0xC0000000 | 0x0079)
+#define NT_STATUS_PROCEDURE_NOT_FOUND (0xC0000000 | 0x007a)
+#define NT_STATUS_INVALID_IMAGE_FORMAT (0xC0000000 | 0x007b)
+#define NT_STATUS_NO_TOKEN (0xC0000000 | 0x007c)
+#define NT_STATUS_BAD_INHERITANCE_ACL (0xC0000000 | 0x007d)
+#define NT_STATUS_RANGE_NOT_LOCKED (0xC0000000 | 0x007e)
+#define NT_STATUS_DISK_FULL (0xC0000000 | 0x007f)
+#define NT_STATUS_SERVER_DISABLED (0xC0000000 | 0x0080)
+#define NT_STATUS_SERVER_NOT_DISABLED (0xC0000000 | 0x0081)
+#define NT_STATUS_TOO_MANY_GUIDS_REQUESTED (0xC0000000 | 0x0082)
+#define NT_STATUS_GUIDS_EXHAUSTED (0xC0000000 | 0x0083)
+#define NT_STATUS_INVALID_ID_AUTHORITY (0xC0000000 | 0x0084)
+#define NT_STATUS_AGENTS_EXHAUSTED (0xC0000000 | 0x0085)
+#define NT_STATUS_INVALID_VOLUME_LABEL (0xC0000000 | 0x0086)
+#define NT_STATUS_SECTION_NOT_EXTENDED (0xC0000000 | 0x0087)
+#define NT_STATUS_NOT_MAPPED_DATA (0xC0000000 | 0x0088)
+#define NT_STATUS_RESOURCE_DATA_NOT_FOUND (0xC0000000 | 0x0089)
+#define NT_STATUS_RESOURCE_TYPE_NOT_FOUND (0xC0000000 | 0x008a)
+#define NT_STATUS_RESOURCE_NAME_NOT_FOUND (0xC0000000 | 0x008b)
+#define NT_STATUS_ARRAY_BOUNDS_EXCEEDED (0xC0000000 | 0x008c)
+#define NT_STATUS_FLOAT_DENORMAL_OPERAND (0xC0000000 | 0x008d)
+#define NT_STATUS_FLOAT_DIVIDE_BY_ZERO (0xC0000000 | 0x008e)
+#define NT_STATUS_FLOAT_INEXACT_RESULT (0xC0000000 | 0x008f)
+#define NT_STATUS_FLOAT_INVALID_OPERATION (0xC0000000 | 0x0090)
+#define NT_STATUS_FLOAT_OVERFLOW (0xC0000000 | 0x0091)
+#define NT_STATUS_FLOAT_STACK_CHECK (0xC0000000 | 0x0092)
+#define NT_STATUS_FLOAT_UNDERFLOW (0xC0000000 | 0x0093)
+#define NT_STATUS_INTEGER_DIVIDE_BY_ZERO (0xC0000000 | 0x0094)
+#define NT_STATUS_INTEGER_OVERFLOW (0xC0000000 | 0x0095)
+#define NT_STATUS_PRIVILEGED_INSTRUCTION (0xC0000000 | 0x0096)
+#define NT_STATUS_TOO_MANY_PAGING_FILES (0xC0000000 | 0x0097)
+#define NT_STATUS_FILE_INVALID (0xC0000000 | 0x0098)
+#define NT_STATUS_ALLOTTED_SPACE_EXCEEDED (0xC0000000 | 0x0099)
+#define NT_STATUS_INSUFFICIENT_RESOURCES (0xC0000000 | 0x009a)
+#define NT_STATUS_DFS_EXIT_PATH_FOUND (0xC0000000 | 0x009b)
+#define NT_STATUS_DEVICE_DATA_ERROR (0xC0000000 | 0x009c)
+#define NT_STATUS_DEVICE_NOT_CONNECTED (0xC0000000 | 0x009d)
+#define NT_STATUS_DEVICE_POWER_FAILURE (0xC0000000 | 0x009e)
+#define NT_STATUS_FREE_VM_NOT_AT_BASE (0xC0000000 | 0x009f)
+#define NT_STATUS_MEMORY_NOT_ALLOCATED (0xC0000000 | 0x00a0)
+#define NT_STATUS_WORKING_SET_QUOTA (0xC0000000 | 0x00a1)
+#define NT_STATUS_MEDIA_WRITE_PROTECTED (0xC0000000 | 0x00a2)
+#define NT_STATUS_DEVICE_NOT_READY (0xC0000000 | 0x00a3)
+#define NT_STATUS_INVALID_GROUP_ATTRIBUTES (0xC0000000 | 0x00a4)
+#define NT_STATUS_BAD_IMPERSONATION_LEVEL (0xC0000000 | 0x00a5)
+#define NT_STATUS_CANT_OPEN_ANONYMOUS (0xC0000000 | 0x00a6)
+#define NT_STATUS_BAD_VALIDATION_CLASS (0xC0000000 | 0x00a7)
+#define NT_STATUS_BAD_TOKEN_TYPE (0xC0000000 | 0x00a8)
+#define NT_STATUS_BAD_MASTER_BOOT_RECORD (0xC0000000 | 0x00a9)
+#define NT_STATUS_INSTRUCTION_MISALIGNMENT (0xC0000000 | 0x00aa)
+#define NT_STATUS_INSTANCE_NOT_AVAILABLE (0xC0000000 | 0x00ab)
+#define NT_STATUS_PIPE_NOT_AVAILABLE (0xC0000000 | 0x00ac)
+#define NT_STATUS_INVALID_PIPE_STATE (0xC0000000 | 0x00ad)
+#define NT_STATUS_PIPE_BUSY (0xC0000000 | 0x00ae)
+#define NT_STATUS_ILLEGAL_FUNCTION (0xC0000000 | 0x00af)
+#define NT_STATUS_PIPE_DISCONNECTED (0xC0000000 | 0x00b0)
+#define NT_STATUS_PIPE_CLOSING (0xC0000000 | 0x00b1)
+#define NT_STATUS_PIPE_CONNECTED (0xC0000000 | 0x00b2)
+#define NT_STATUS_PIPE_LISTENING (0xC0000000 | 0x00b3)
+#define NT_STATUS_INVALID_READ_MODE (0xC0000000 | 0x00b4)
+#define NT_STATUS_IO_TIMEOUT (0xC0000000 | 0x00b5)
+#define NT_STATUS_FILE_FORCED_CLOSED (0xC0000000 | 0x00b6)
+#define NT_STATUS_PROFILING_NOT_STARTED (0xC0000000 | 0x00b7)
+#define NT_STATUS_PROFILING_NOT_STOPPED (0xC0000000 | 0x00b8)
+#define NT_STATUS_COULD_NOT_INTERPRET (0xC0000000 | 0x00b9)
+#define NT_STATUS_FILE_IS_A_DIRECTORY (0xC0000000 | 0x00ba)
+#define NT_STATUS_NOT_SUPPORTED (0xC0000000 | 0x00bb)
+#define NT_STATUS_REMOTE_NOT_LISTENING (0xC0000000 | 0x00bc)
+#define NT_STATUS_DUPLICATE_NAME (0xC0000000 | 0x00bd)
+#define NT_STATUS_BAD_NETWORK_PATH (0xC0000000 | 0x00be)
+#define NT_STATUS_NETWORK_BUSY (0xC0000000 | 0x00bf)
+#define NT_STATUS_DEVICE_DOES_NOT_EXIST (0xC0000000 | 0x00c0)
+#define NT_STATUS_TOO_MANY_COMMANDS (0xC0000000 | 0x00c1)
+#define NT_STATUS_ADAPTER_HARDWARE_ERROR (0xC0000000 | 0x00c2)
+#define NT_STATUS_INVALID_NETWORK_RESPONSE (0xC0000000 | 0x00c3)
+#define NT_STATUS_UNEXPECTED_NETWORK_ERROR (0xC0000000 | 0x00c4)
+#define NT_STATUS_BAD_REMOTE_ADAPTER (0xC0000000 | 0x00c5)
+#define NT_STATUS_PRINT_QUEUE_FULL (0xC0000000 | 0x00c6)
+#define NT_STATUS_NO_SPOOL_SPACE (0xC0000000 | 0x00c7)
+#define NT_STATUS_PRINT_CANCELLED (0xC0000000 | 0x00c8)
+#define NT_STATUS_NETWORK_NAME_DELETED (0xC0000000 | 0x00c9)
+#define NT_STATUS_NETWORK_ACCESS_DENIED (0xC0000000 | 0x00ca)
+#define NT_STATUS_BAD_DEVICE_TYPE (0xC0000000 | 0x00cb)
+#define NT_STATUS_BAD_NETWORK_NAME (0xC0000000 | 0x00cc)
+#define NT_STATUS_TOO_MANY_NAMES (0xC0000000 | 0x00cd)
+#define NT_STATUS_TOO_MANY_SESSIONS (0xC0000000 | 0x00ce)
+#define NT_STATUS_SHARING_PAUSED (0xC0000000 | 0x00cf)
+#define NT_STATUS_REQUEST_NOT_ACCEPTED (0xC0000000 | 0x00d0)
+#define NT_STATUS_REDIRECTOR_PAUSED (0xC0000000 | 0x00d1)
+#define NT_STATUS_NET_WRITE_FAULT (0xC0000000 | 0x00d2)
+#define NT_STATUS_PROFILING_AT_LIMIT (0xC0000000 | 0x00d3)
+#define NT_STATUS_NOT_SAME_DEVICE (0xC0000000 | 0x00d4)
+#define NT_STATUS_FILE_RENAMED (0xC0000000 | 0x00d5)
+#define NT_STATUS_VIRTUAL_CIRCUIT_CLOSED (0xC0000000 | 0x00d6)
+#define NT_STATUS_NO_SECURITY_ON_OBJECT (0xC0000000 | 0x00d7)
+#define NT_STATUS_CANT_WAIT (0xC0000000 | 0x00d8)
+#define NT_STATUS_PIPE_EMPTY (0xC0000000 | 0x00d9)
+#define NT_STATUS_CANT_ACCESS_DOMAIN_INFO (0xC0000000 | 0x00da)
+#define NT_STATUS_CANT_TERMINATE_SELF (0xC0000000 | 0x00db)
+#define NT_STATUS_INVALID_SERVER_STATE (0xC0000000 | 0x00dc)
+#define NT_STATUS_INVALID_DOMAIN_STATE (0xC0000000 | 0x00dd)
+#define NT_STATUS_INVALID_DOMAIN_ROLE (0xC0000000 | 0x00de)
+#define NT_STATUS_NO_SUCH_DOMAIN (0xC0000000 | 0x00df)
+#define NT_STATUS_DOMAIN_EXISTS (0xC0000000 | 0x00e0)
+#define NT_STATUS_DOMAIN_LIMIT_EXCEEDED (0xC0000000 | 0x00e1)
+#define NT_STATUS_OPLOCK_NOT_GRANTED (0xC0000000 | 0x00e2)
+#define NT_STATUS_INVALID_OPLOCK_PROTOCOL (0xC0000000 | 0x00e3)
+#define NT_STATUS_INTERNAL_DB_CORRUPTION (0xC0000000 | 0x00e4)
+#define NT_STATUS_INTERNAL_ERROR (0xC0000000 | 0x00e5)
+#define NT_STATUS_GENERIC_NOT_MAPPED (0xC0000000 | 0x00e6)
+#define NT_STATUS_BAD_DESCRIPTOR_FORMAT (0xC0000000 | 0x00e7)
+#define NT_STATUS_INVALID_USER_BUFFER (0xC0000000 | 0x00e8)
+#define NT_STATUS_UNEXPECTED_IO_ERROR (0xC0000000 | 0x00e9)
+#define NT_STATUS_UNEXPECTED_MM_CREATE_ERR (0xC0000000 | 0x00ea)
+#define NT_STATUS_UNEXPECTED_MM_MAP_ERROR (0xC0000000 | 0x00eb)
+#define NT_STATUS_UNEXPECTED_MM_EXTEND_ERR (0xC0000000 | 0x00ec)
+#define NT_STATUS_NOT_LOGON_PROCESS (0xC0000000 | 0x00ed)
+#define NT_STATUS_LOGON_SESSION_EXISTS (0xC0000000 | 0x00ee)
+#define NT_STATUS_INVALID_PARAMETER_1 (0xC0000000 | 0x00ef)
+#define NT_STATUS_INVALID_PARAMETER_2 (0xC0000000 | 0x00f0)
+#define NT_STATUS_INVALID_PARAMETER_3 (0xC0000000 | 0x00f1)
+#define NT_STATUS_INVALID_PARAMETER_4 (0xC0000000 | 0x00f2)
+#define NT_STATUS_INVALID_PARAMETER_5 (0xC0000000 | 0x00f3)
+#define NT_STATUS_INVALID_PARAMETER_6 (0xC0000000 | 0x00f4)
+#define NT_STATUS_INVALID_PARAMETER_7 (0xC0000000 | 0x00f5)
+#define NT_STATUS_INVALID_PARAMETER_8 (0xC0000000 | 0x00f6)
+#define NT_STATUS_INVALID_PARAMETER_9 (0xC0000000 | 0x00f7)
+#define NT_STATUS_INVALID_PARAMETER_10 (0xC0000000 | 0x00f8)
+#define NT_STATUS_INVALID_PARAMETER_11 (0xC0000000 | 0x00f9)
+#define NT_STATUS_INVALID_PARAMETER_12 (0xC0000000 | 0x00fa)
+#define NT_STATUS_REDIRECTOR_NOT_STARTED (0xC0000000 | 0x00fb)
+#define NT_STATUS_REDIRECTOR_STARTED (0xC0000000 | 0x00fc)
+#define NT_STATUS_STACK_OVERFLOW (0xC0000000 | 0x00fd)
+#define NT_STATUS_NO_SUCH_PACKAGE (0xC0000000 | 0x00fe)
+#define NT_STATUS_BAD_FUNCTION_TABLE (0xC0000000 | 0x00ff)
+#define NT_STATUS_DIRECTORY_NOT_EMPTY (0xC0000000 | 0x0101)
+#define NT_STATUS_FILE_CORRUPT_ERROR (0xC0000000 | 0x0102)
+#define NT_STATUS_NOT_A_DIRECTORY (0xC0000000 | 0x0103)
+#define NT_STATUS_BAD_LOGON_SESSION_STATE (0xC0000000 | 0x0104)
+#define NT_STATUS_LOGON_SESSION_COLLISION (0xC0000000 | 0x0105)
+#define NT_STATUS_NAME_TOO_LONG (0xC0000000 | 0x0106)
+#define NT_STATUS_FILES_OPEN (0xC0000000 | 0x0107)
+#define NT_STATUS_CONNECTION_IN_USE (0xC0000000 | 0x0108)
+#define NT_STATUS_MESSAGE_NOT_FOUND (0xC0000000 | 0x0109)
+#define NT_STATUS_PROCESS_IS_TERMINATING (0xC0000000 | 0x010a)
+#define NT_STATUS_INVALID_LOGON_TYPE (0xC0000000 | 0x010b)
+#define NT_STATUS_NO_GUID_TRANSLATION (0xC0000000 | 0x010c)
+#define NT_STATUS_CANNOT_IMPERSONATE (0xC0000000 | 0x010d)
+#define NT_STATUS_IMAGE_ALREADY_LOADED (0xC0000000 | 0x010e)
+#define NT_STATUS_ABIOS_NOT_PRESENT (0xC0000000 | 0x010f)
+#define NT_STATUS_ABIOS_LID_NOT_EXIST (0xC0000000 | 0x0110)
+#define NT_STATUS_ABIOS_LID_ALREADY_OWNED (0xC0000000 | 0x0111)
+#define NT_STATUS_ABIOS_NOT_LID_OWNER (0xC0000000 | 0x0112)
+#define NT_STATUS_ABIOS_INVALID_COMMAND (0xC0000000 | 0x0113)
+#define NT_STATUS_ABIOS_INVALID_LID (0xC0000000 | 0x0114)
+#define NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE (0xC0000000 | 0x0115)
+#define NT_STATUS_ABIOS_INVALID_SELECTOR (0xC0000000 | 0x0116)
+#define NT_STATUS_NO_LDT (0xC0000000 | 0x0117)
+#define NT_STATUS_INVALID_LDT_SIZE (0xC0000000 | 0x0118)
+#define NT_STATUS_INVALID_LDT_OFFSET (0xC0000000 | 0x0119)
+#define NT_STATUS_INVALID_LDT_DESCRIPTOR (0xC0000000 | 0x011a)
+#define NT_STATUS_INVALID_IMAGE_NE_FORMAT (0xC0000000 | 0x011b)
+#define NT_STATUS_RXACT_INVALID_STATE (0xC0000000 | 0x011c)
+#define NT_STATUS_RXACT_COMMIT_FAILURE (0xC0000000 | 0x011d)
+#define NT_STATUS_MAPPED_FILE_SIZE_ZERO (0xC0000000 | 0x011e)
+#define NT_STATUS_TOO_MANY_OPENED_FILES (0xC0000000 | 0x011f)
+#define NT_STATUS_CANCELLED (0xC0000000 | 0x0120)
+#define NT_STATUS_CANNOT_DELETE (0xC0000000 | 0x0121)
+#define NT_STATUS_INVALID_COMPUTER_NAME (0xC0000000 | 0x0122)
+#define NT_STATUS_FILE_DELETED (0xC0000000 | 0x0123)
+#define NT_STATUS_SPECIAL_ACCOUNT (0xC0000000 | 0x0124)
+#define NT_STATUS_SPECIAL_GROUP (0xC0000000 | 0x0125)
+#define NT_STATUS_SPECIAL_USER (0xC0000000 | 0x0126)
+#define NT_STATUS_MEMBERS_PRIMARY_GROUP (0xC0000000 | 0x0127)
+#define NT_STATUS_FILE_CLOSED (0xC0000000 | 0x0128)
+#define NT_STATUS_TOO_MANY_THREADS (0xC0000000 | 0x0129)
+#define NT_STATUS_THREAD_NOT_IN_PROCESS (0xC0000000 | 0x012a)
+#define NT_STATUS_TOKEN_ALREADY_IN_USE (0xC0000000 | 0x012b)
+#define NT_STATUS_PAGEFILE_QUOTA_EXCEEDED (0xC0000000 | 0x012c)
+#define NT_STATUS_COMMITMENT_LIMIT (0xC0000000 | 0x012d)
+#define NT_STATUS_INVALID_IMAGE_LE_FORMAT (0xC0000000 | 0x012e)
+#define NT_STATUS_INVALID_IMAGE_NOT_MZ (0xC0000000 | 0x012f)
+#define NT_STATUS_INVALID_IMAGE_PROTECT (0xC0000000 | 0x0130)
+#define NT_STATUS_INVALID_IMAGE_WIN_16 (0xC0000000 | 0x0131)
+#define NT_STATUS_LOGON_SERVER_CONFLICT (0xC0000000 | 0x0132)
+#define NT_STATUS_TIME_DIFFERENCE_AT_DC (0xC0000000 | 0x0133)
+#define NT_STATUS_SYNCHRONIZATION_REQUIRED (0xC0000000 | 0x0134)
+#define NT_STATUS_DLL_NOT_FOUND (0xC0000000 | 0x0135)
+#define NT_STATUS_OPEN_FAILED (0xC0000000 | 0x0136)
+#define NT_STATUS_IO_PRIVILEGE_FAILED (0xC0000000 | 0x0137)
+#define NT_STATUS_ORDINAL_NOT_FOUND (0xC0000000 | 0x0138)
+#define NT_STATUS_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0139)
+#define NT_STATUS_CONTROL_C_EXIT (0xC0000000 | 0x013a)
+#define NT_STATUS_LOCAL_DISCONNECT (0xC0000000 | 0x013b)
+#define NT_STATUS_REMOTE_DISCONNECT (0xC0000000 | 0x013c)
+#define NT_STATUS_REMOTE_RESOURCES (0xC0000000 | 0x013d)
+#define NT_STATUS_LINK_FAILED (0xC0000000 | 0x013e)
+#define NT_STATUS_LINK_TIMEOUT (0xC0000000 | 0x013f)
+#define NT_STATUS_INVALID_CONNECTION (0xC0000000 | 0x0140)
+#define NT_STATUS_INVALID_ADDRESS (0xC0000000 | 0x0141)
+#define NT_STATUS_DLL_INIT_FAILED (0xC0000000 | 0x0142)
+#define NT_STATUS_MISSING_SYSTEMFILE (0xC0000000 | 0x0143)
+#define NT_STATUS_UNHANDLED_EXCEPTION (0xC0000000 | 0x0144)
+#define NT_STATUS_APP_INIT_FAILURE (0xC0000000 | 0x0145)
+#define NT_STATUS_PAGEFILE_CREATE_FAILED (0xC0000000 | 0x0146)
+#define NT_STATUS_NO_PAGEFILE (0xC0000000 | 0x0147)
+#define NT_STATUS_INVALID_LEVEL (0xC0000000 | 0x0148)
+#define NT_STATUS_WRONG_PASSWORD_CORE (0xC0000000 | 0x0149)
+#define NT_STATUS_ILLEGAL_FLOAT_CONTEXT (0xC0000000 | 0x014a)
+#define NT_STATUS_PIPE_BROKEN (0xC0000000 | 0x014b)
+#define NT_STATUS_REGISTRY_CORRUPT (0xC0000000 | 0x014c)
+#define NT_STATUS_REGISTRY_IO_FAILED (0xC0000000 | 0x014d)
+#define NT_STATUS_NO_EVENT_PAIR (0xC0000000 | 0x014e)
+#define NT_STATUS_UNRECOGNIZED_VOLUME (0xC0000000 | 0x014f)
+#define NT_STATUS_SERIAL_NO_DEVICE_INITED (0xC0000000 | 0x0150)
+#define NT_STATUS_NO_SUCH_ALIAS (0xC0000000 | 0x0151)
+#define NT_STATUS_MEMBER_NOT_IN_ALIAS (0xC0000000 | 0x0152)
+#define NT_STATUS_MEMBER_IN_ALIAS (0xC0000000 | 0x0153)
+#define NT_STATUS_ALIAS_EXISTS (0xC0000000 | 0x0154)
+#define NT_STATUS_LOGON_NOT_GRANTED (0xC0000000 | 0x0155)
+#define NT_STATUS_TOO_MANY_SECRETS (0xC0000000 | 0x0156)
+#define NT_STATUS_SECRET_TOO_LONG (0xC0000000 | 0x0157)
+#define NT_STATUS_INTERNAL_DB_ERROR (0xC0000000 | 0x0158)
+#define NT_STATUS_FULLSCREEN_MODE (0xC0000000 | 0x0159)
+#define NT_STATUS_TOO_MANY_CONTEXT_IDS (0xC0000000 | 0x015a)
+#define NT_STATUS_LOGON_TYPE_NOT_GRANTED (0xC0000000 | 0x015b)
+#define NT_STATUS_NOT_REGISTRY_FILE (0xC0000000 | 0x015c)
+#define NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x015d)
+#define NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR (0xC0000000 | 0x015e)
+#define NT_STATUS_FT_MISSING_MEMBER (0xC0000000 | 0x015f)
+#define NT_STATUS_ILL_FORMED_SERVICE_ENTRY (0xC0000000 | 0x0160)
+#define NT_STATUS_ILLEGAL_CHARACTER (0xC0000000 | 0x0161)
+#define NT_STATUS_UNMAPPABLE_CHARACTER (0xC0000000 | 0x0162)
+#define NT_STATUS_UNDEFINED_CHARACTER (0xC0000000 | 0x0163)
+#define NT_STATUS_FLOPPY_VOLUME (0xC0000000 | 0x0164)
+#define NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND (0xC0000000 | 0x0165)
+#define NT_STATUS_FLOPPY_WRONG_CYLINDER (0xC0000000 | 0x0166)
+#define NT_STATUS_FLOPPY_UNKNOWN_ERROR (0xC0000000 | 0x0167)
+#define NT_STATUS_FLOPPY_BAD_REGISTERS (0xC0000000 | 0x0168)
+#define NT_STATUS_DISK_RECALIBRATE_FAILED (0xC0000000 | 0x0169)
+#define NT_STATUS_DISK_OPERATION_FAILED (0xC0000000 | 0x016a)
+#define NT_STATUS_DISK_RESET_FAILED (0xC0000000 | 0x016b)
+#define NT_STATUS_SHARED_IRQ_BUSY (0xC0000000 | 0x016c)
+#define NT_STATUS_FT_ORPHANING (0xC0000000 | 0x016d)
+#define NT_STATUS_PARTITION_FAILURE (0xC0000000 | 0x0172)
+#define NT_STATUS_INVALID_BLOCK_LENGTH (0xC0000000 | 0x0173)
+#define NT_STATUS_DEVICE_NOT_PARTITIONED (0xC0000000 | 0x0174)
+#define NT_STATUS_UNABLE_TO_LOCK_MEDIA (0xC0000000 | 0x0175)
+#define NT_STATUS_UNABLE_TO_UNLOAD_MEDIA (0xC0000000 | 0x0176)
+#define NT_STATUS_EOM_OVERFLOW (0xC0000000 | 0x0177)
+#define NT_STATUS_NO_MEDIA (0xC0000000 | 0x0178)
+#define NT_STATUS_NO_SUCH_MEMBER (0xC0000000 | 0x017a)
+#define NT_STATUS_INVALID_MEMBER (0xC0000000 | 0x017b)
+#define NT_STATUS_KEY_DELETED (0xC0000000 | 0x017c)
+#define NT_STATUS_NO_LOG_SPACE (0xC0000000 | 0x017d)
+#define NT_STATUS_TOO_MANY_SIDS (0xC0000000 | 0x017e)
+#define NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED (0xC0000000 | 0x017f)
+#define NT_STATUS_KEY_HAS_CHILDREN (0xC0000000 | 0x0180)
+#define NT_STATUS_CHILD_MUST_BE_VOLATILE (0xC0000000 | 0x0181)
+#define NT_STATUS_DEVICE_CONFIGURATION_ERROR (0xC0000000 | 0x0182)
+#define NT_STATUS_DRIVER_INTERNAL_ERROR (0xC0000000 | 0x0183)
+#define NT_STATUS_INVALID_DEVICE_STATE (0xC0000000 | 0x0184)
+#define NT_STATUS_IO_DEVICE_ERROR (0xC0000000 | 0x0185)
+#define NT_STATUS_DEVICE_PROTOCOL_ERROR (0xC0000000 | 0x0186)
+#define NT_STATUS_BACKUP_CONTROLLER (0xC0000000 | 0x0187)
+#define NT_STATUS_LOG_FILE_FULL (0xC0000000 | 0x0188)
+#define NT_STATUS_TOO_LATE (0xC0000000 | 0x0189)
+#define NT_STATUS_NO_TRUST_LSA_SECRET (0xC0000000 | 0x018a)
+#define NT_STATUS_NO_TRUST_SAM_ACCOUNT (0xC0000000 | 0x018b)
+#define NT_STATUS_TRUSTED_DOMAIN_FAILURE (0xC0000000 | 0x018c)
+#define NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE (0xC0000000 | 0x018d)
+#define NT_STATUS_EVENTLOG_FILE_CORRUPT (0xC0000000 | 0x018e)
+#define NT_STATUS_EVENTLOG_CANT_START (0xC0000000 | 0x018f)
+#define NT_STATUS_TRUST_FAILURE (0xC0000000 | 0x0190)
+#define NT_STATUS_MUTANT_LIMIT_EXCEEDED (0xC0000000 | 0x0191)
+#define NT_STATUS_NETLOGON_NOT_STARTED (0xC0000000 | 0x0192)
+#define NT_STATUS_ACCOUNT_EXPIRED (0xC0000000 | 0x0193)
+#define NT_STATUS_POSSIBLE_DEADLOCK (0xC0000000 | 0x0194)
+#define NT_STATUS_NETWORK_CREDENTIAL_CONFLICT (0xC0000000 | 0x0195)
+#define NT_STATUS_REMOTE_SESSION_LIMIT (0xC0000000 | 0x0196)
+#define NT_STATUS_EVENTLOG_FILE_CHANGED (0xC0000000 | 0x0197)
+#define NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT (0xC0000000 | 0x0198)
+#define NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT (0xC0000000 | 0x0199)
+#define NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT (0xC0000000 | 0x019a)
+#define NT_STATUS_DOMAIN_TRUST_INCONSISTENT (0xC0000000 | 0x019b)
+#define NT_STATUS_FS_DRIVER_REQUIRED (0xC0000000 | 0x019c)
+#define NT_STATUS_NO_USER_SESSION_KEY (0xC0000000 | 0x0202)
+#define NT_STATUS_USER_SESSION_DELETED (0xC0000000 | 0x0203)
+#define NT_STATUS_RESOURCE_LANG_NOT_FOUND (0xC0000000 | 0x0204)
+#define NT_STATUS_INSUFF_SERVER_RESOURCES (0xC0000000 | 0x0205)
+#define NT_STATUS_INVALID_BUFFER_SIZE (0xC0000000 | 0x0206)
+#define NT_STATUS_INVALID_ADDRESS_COMPONENT (0xC0000000 | 0x0207)
+#define NT_STATUS_INVALID_ADDRESS_WILDCARD (0xC0000000 | 0x0208)
+#define NT_STATUS_TOO_MANY_ADDRESSES (0xC0000000 | 0x0209)
+#define NT_STATUS_ADDRESS_ALREADY_EXISTS (0xC0000000 | 0x020a)
+#define NT_STATUS_ADDRESS_CLOSED (0xC0000000 | 0x020b)
+#define NT_STATUS_CONNECTION_DISCONNECTED (0xC0000000 | 0x020c)
+#define NT_STATUS_CONNECTION_RESET (0xC0000000 | 0x020d)
+#define NT_STATUS_TOO_MANY_NODES (0xC0000000 | 0x020e)
+#define NT_STATUS_TRANSACTION_ABORTED (0xC0000000 | 0x020f)
+#define NT_STATUS_TRANSACTION_TIMED_OUT (0xC0000000 | 0x0210)
+#define NT_STATUS_TRANSACTION_NO_RELEASE (0xC0000000 | 0x0211)
+#define NT_STATUS_TRANSACTION_NO_MATCH (0xC0000000 | 0x0212)
+#define NT_STATUS_TRANSACTION_RESPONDED (0xC0000000 | 0x0213)
+#define NT_STATUS_TRANSACTION_INVALID_ID (0xC0000000 | 0x0214)
+#define NT_STATUS_TRANSACTION_INVALID_TYPE (0xC0000000 | 0x0215)
+#define NT_STATUS_NOT_SERVER_SESSION (0xC0000000 | 0x0216)
+#define NT_STATUS_NOT_CLIENT_SESSION (0xC0000000 | 0x0217)
+#define NT_STATUS_CANNOT_LOAD_REGISTRY_FILE (0xC0000000 | 0x0218)
+#define NT_STATUS_DEBUG_ATTACH_FAILED (0xC0000000 | 0x0219)
+#define NT_STATUS_SYSTEM_PROCESS_TERMINATED (0xC0000000 | 0x021a)
+#define NT_STATUS_DATA_NOT_ACCEPTED (0xC0000000 | 0x021b)
+#define NT_STATUS_NO_BROWSER_SERVERS_FOUND (0xC0000000 | 0x021c)
+#define NT_STATUS_VDM_HARD_ERROR (0xC0000000 | 0x021d)
+#define NT_STATUS_DRIVER_CANCEL_TIMEOUT (0xC0000000 | 0x021e)
+#define NT_STATUS_REPLY_MESSAGE_MISMATCH (0xC0000000 | 0x021f)
+#define NT_STATUS_MAPPED_ALIGNMENT (0xC0000000 | 0x0220)
+#define NT_STATUS_IMAGE_CHECKSUM_MISMATCH (0xC0000000 | 0x0221)
+#define NT_STATUS_LOST_WRITEBEHIND_DATA (0xC0000000 | 0x0222)
+#define NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID (0xC0000000 | 0x0223)
+#define NT_STATUS_PASSWORD_MUST_CHANGE (0xC0000000 | 0x0224)
+#define NT_STATUS_NOT_FOUND (0xC0000000 | 0x0225)
+#define NT_STATUS_NOT_TINY_STREAM (0xC0000000 | 0x0226)
+#define NT_STATUS_RECOVERY_FAILURE (0xC0000000 | 0x0227)
+#define NT_STATUS_STACK_OVERFLOW_READ (0xC0000000 | 0x0228)
+#define NT_STATUS_FAIL_CHECK (0xC0000000 | 0x0229)
+#define NT_STATUS_DUPLICATE_OBJECTID (0xC0000000 | 0x022a)
+#define NT_STATUS_OBJECTID_EXISTS (0xC0000000 | 0x022b)
+#define NT_STATUS_CONVERT_TO_LARGE (0xC0000000 | 0x022c)
+#define NT_STATUS_RETRY (0xC0000000 | 0x022d)
+#define NT_STATUS_FOUND_OUT_OF_SCOPE (0xC0000000 | 0x022e)
+#define NT_STATUS_ALLOCATE_BUCKET (0xC0000000 | 0x022f)
+#define NT_STATUS_PROPSET_NOT_FOUND (0xC0000000 | 0x0230)
+#define NT_STATUS_MARSHALL_OVERFLOW (0xC0000000 | 0x0231)
+#define NT_STATUS_INVALID_VARIANT (0xC0000000 | 0x0232)
+#define NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND (0xC0000000 | 0x0233)
+#define NT_STATUS_ACCOUNT_LOCKED_OUT (0xC0000000 | 0x0234)
+#define NT_STATUS_HANDLE_NOT_CLOSABLE (0xC0000000 | 0x0235)
+#define NT_STATUS_CONNECTION_REFUSED (0xC0000000 | 0x0236)
+#define NT_STATUS_GRACEFUL_DISCONNECT (0xC0000000 | 0x0237)
+#define NT_STATUS_ADDRESS_ALREADY_ASSOCIATED (0xC0000000 | 0x0238)
+#define NT_STATUS_ADDRESS_NOT_ASSOCIATED (0xC0000000 | 0x0239)
+#define NT_STATUS_CONNECTION_INVALID (0xC0000000 | 0x023a)
+#define NT_STATUS_CONNECTION_ACTIVE (0xC0000000 | 0x023b)
+#define NT_STATUS_NETWORK_UNREACHABLE (0xC0000000 | 0x023c)
+#define NT_STATUS_HOST_UNREACHABLE (0xC0000000 | 0x023d)
+#define NT_STATUS_PROTOCOL_UNREACHABLE (0xC0000000 | 0x023e)
+#define NT_STATUS_PORT_UNREACHABLE (0xC0000000 | 0x023f)
+#define NT_STATUS_REQUEST_ABORTED (0xC0000000 | 0x0240)
+#define NT_STATUS_CONNECTION_ABORTED (0xC0000000 | 0x0241)
+#define NT_STATUS_BAD_COMPRESSION_BUFFER (0xC0000000 | 0x0242)
+#define NT_STATUS_USER_MAPPED_FILE (0xC0000000 | 0x0243)
+#define NT_STATUS_AUDIT_FAILED (0xC0000000 | 0x0244)
+#define NT_STATUS_TIMER_RESOLUTION_NOT_SET (0xC0000000 | 0x0245)
+#define NT_STATUS_CONNECTION_COUNT_LIMIT (0xC0000000 | 0x0246)
+#define NT_STATUS_LOGIN_TIME_RESTRICTION (0xC0000000 | 0x0247)
+#define NT_STATUS_LOGIN_WKSTA_RESTRICTION (0xC0000000 | 0x0248)
+#define NT_STATUS_IMAGE_MP_UP_MISMATCH (0xC0000000 | 0x0249)
+#define NT_STATUS_INSUFFICIENT_LOGON_INFO (0xC0000000 | 0x0250)
+#define NT_STATUS_BAD_DLL_ENTRYPOINT (0xC0000000 | 0x0251)
+#define NT_STATUS_BAD_SERVICE_ENTRYPOINT (0xC0000000 | 0x0252)
+#define NT_STATUS_LPC_REPLY_LOST (0xC0000000 | 0x0253)
+#define NT_STATUS_IP_ADDRESS_CONFLICT1 (0xC0000000 | 0x0254)
+#define NT_STATUS_IP_ADDRESS_CONFLICT2 (0xC0000000 | 0x0255)
+#define NT_STATUS_REGISTRY_QUOTA_LIMIT (0xC0000000 | 0x0256)
+#define NT_STATUS_PATH_NOT_COVERED (0xC0000000 | 0x0257)
+#define NT_STATUS_NO_CALLBACK_ACTIVE (0xC0000000 | 0x0258)
+#define NT_STATUS_LICENSE_QUOTA_EXCEEDED (0xC0000000 | 0x0259)
+#define NT_STATUS_PWD_TOO_SHORT (0xC0000000 | 0x025a)
+#define NT_STATUS_PWD_TOO_RECENT (0xC0000000 | 0x025b)
+#define NT_STATUS_PWD_HISTORY_CONFLICT (0xC0000000 | 0x025c)
+#define NT_STATUS_PLUGPLAY_NO_DEVICE (0xC0000000 | 0x025e)
+#define NT_STATUS_UNSUPPORTED_COMPRESSION (0xC0000000 | 0x025f)
+#define NT_STATUS_INVALID_HW_PROFILE (0xC0000000 | 0x0260)
+#define NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH (0xC0000000 | 0x0261)
+#define NT_STATUS_DRIVER_ORDINAL_NOT_FOUND (0xC0000000 | 0x0262)
+#define NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND (0xC0000000 | 0x0263)
+#define NT_STATUS_RESOURCE_NOT_OWNED (0xC0000000 | 0x0264)
+#define NT_STATUS_TOO_MANY_LINKS (0xC0000000 | 0x0265)
+#define NT_STATUS_QUOTA_LIST_INCONSISTENT (0xC0000000 | 0x0266)
+#define NT_STATUS_FILE_IS_OFFLINE (0xC0000000 | 0x0267)
+#define NT_STATUS_NETWORK_SESSION_EXPIRED (0xC0000000 | 0x035c)
+#define NT_STATUS_NO_SUCH_JOB (0xC0000000 | 0xEDE) /* scheduler */
+#define NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP (0xC0000000 | 0x5D0000)
+#define NT_STATUS_PENDING 0x00000103
+#endif /* _NTERR_H */
diff --git a/fs/ksmbd/ntlmssp.h b/fs/ksmbd/ntlmssp.h
new file mode 100644
index 000000000000..adaf4c0cbe8f
--- /dev/null
+++ b/fs/ksmbd/ntlmssp.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (c) International Business Machines Corp., 2002,2007
+ * Author(s): Steve French (sfrench@us.ibm.com)
+ */
+
+#ifndef __KSMBD_NTLMSSP_H
+#define __KSMBD_NTLMSSP_H
+
+#define NTLMSSP_SIGNATURE "NTLMSSP"
+
+/* Security blob target info data */
+#define TGT_Name "KSMBD"
+
+/*
+ * Size of the crypto key returned on the negotiate SMB in bytes
+ */
+#define CIFS_CRYPTO_KEY_SIZE (8)
+#define CIFS_KEY_SIZE (40)
+
+/*
+ * Size of encrypted user password in bytes
+ */
+#define CIFS_ENCPWD_SIZE (16)
+#define CIFS_CPHTXT_SIZE (16)
+
+/* Message Types */
+#define NtLmNegotiate cpu_to_le32(1)
+#define NtLmChallenge cpu_to_le32(2)
+#define NtLmAuthenticate cpu_to_le32(3)
+#define UnknownMessage cpu_to_le32(8)
+
+/* Negotiate Flags */
+#define NTLMSSP_NEGOTIATE_UNICODE 0x01 /* Text strings are unicode */
+#define NTLMSSP_NEGOTIATE_OEM 0x02 /* Text strings are in OEM */
+#define NTLMSSP_REQUEST_TARGET 0x04 /* Srv returns its auth realm */
+/* define reserved9 0x08 */
+#define NTLMSSP_NEGOTIATE_SIGN 0x0010 /* Request signing capability */
+#define NTLMSSP_NEGOTIATE_SEAL 0x0020 /* Request confidentiality */
+#define NTLMSSP_NEGOTIATE_DGRAM 0x0040
+#define NTLMSSP_NEGOTIATE_LM_KEY 0x0080 /* Use LM session key */
+/* defined reserved 8 0x0100 */
+#define NTLMSSP_NEGOTIATE_NTLM 0x0200 /* NTLM authentication */
+#define NTLMSSP_NEGOTIATE_NT_ONLY 0x0400 /* Lanman not allowed */
+#define NTLMSSP_ANONYMOUS 0x0800
+#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x1000 /* reserved6 */
+#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x2000
+#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x4000 /* client/server same machine */
+#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x8000 /* Sign. All security levels */
+#define NTLMSSP_TARGET_TYPE_DOMAIN 0x10000
+#define NTLMSSP_TARGET_TYPE_SERVER 0x20000
+#define NTLMSSP_TARGET_TYPE_SHARE 0x40000
+#define NTLMSSP_NEGOTIATE_EXTENDED_SEC 0x80000 /* NB:not related to NTLMv2 pwd*/
+/* #define NTLMSSP_REQUEST_INIT_RESP 0x100000 */
+#define NTLMSSP_NEGOTIATE_IDENTIFY 0x100000
+#define NTLMSSP_REQUEST_ACCEPT_RESP 0x200000 /* reserved5 */
+#define NTLMSSP_REQUEST_NON_NT_KEY 0x400000
+#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x800000
+/* #define reserved4 0x1000000 */
+#define NTLMSSP_NEGOTIATE_VERSION 0x2000000 /* we do not set */
+/* #define reserved3 0x4000000 */
+/* #define reserved2 0x8000000 */
+/* #define reserved1 0x10000000 */
+#define NTLMSSP_NEGOTIATE_128 0x20000000
+#define NTLMSSP_NEGOTIATE_KEY_XCH 0x40000000
+#define NTLMSSP_NEGOTIATE_56 0x80000000
+
+/* Define AV Pair Field IDs */
+enum av_field_type {
+ NTLMSSP_AV_EOL = 0,
+ NTLMSSP_AV_NB_COMPUTER_NAME,
+ NTLMSSP_AV_NB_DOMAIN_NAME,
+ NTLMSSP_AV_DNS_COMPUTER_NAME,
+ NTLMSSP_AV_DNS_DOMAIN_NAME,
+ NTLMSSP_AV_DNS_TREE_NAME,
+ NTLMSSP_AV_FLAGS,
+ NTLMSSP_AV_TIMESTAMP,
+ NTLMSSP_AV_RESTRICTION,
+ NTLMSSP_AV_TARGET_NAME,
+ NTLMSSP_AV_CHANNEL_BINDINGS
+};
+
+/* Although typedefs are not commonly used for structure definitions */
+/* in the Linux kernel, in this particular case they are useful */
+/* to more closely match the standards document for NTLMSSP from */
+/* OpenGroup and to make the code more closely match the standard in */
+/* appearance */
+
+struct security_buffer {
+ __le16 Length;
+ __le16 MaximumLength;
+ __le32 BufferOffset; /* offset to buffer */
+} __packed;
+
+struct target_info {
+ __le16 Type;
+ __le16 Length;
+ __u8 Content[0];
+} __packed;
+
+struct negotiate_message {
+ __u8 Signature[sizeof(NTLMSSP_SIGNATURE)];
+ __le32 MessageType; /* NtLmNegotiate = 1 */
+ __le32 NegotiateFlags;
+ struct security_buffer DomainName; /* RFC 1001 style and ASCII */
+ struct security_buffer WorkstationName; /* RFC 1001 and ASCII */
+ /*
+ * struct security_buffer for version info not present since we
+ * do not set the version is present flag
+ */
+ char DomainString[0];
+ /* followed by WorkstationString */
+} __packed;
+
+struct challenge_message {
+ __u8 Signature[sizeof(NTLMSSP_SIGNATURE)];
+ __le32 MessageType; /* NtLmChallenge = 2 */
+ struct security_buffer TargetName;
+ __le32 NegotiateFlags;
+ __u8 Challenge[CIFS_CRYPTO_KEY_SIZE];
+ __u8 Reserved[8];
+ struct security_buffer TargetInfoArray;
+ /*
+ * struct security_buffer for version info not present since we
+ * do not set the version is present flag
+ */
+} __packed;
+
+struct authenticate_message {
+ __u8 Signature[sizeof(NTLMSSP_SIGNATURE)];
+ __le32 MessageType; /* NtLmsAuthenticate = 3 */
+ struct security_buffer LmChallengeResponse;
+ struct security_buffer NtChallengeResponse;
+ struct security_buffer DomainName;
+ struct security_buffer UserName;
+ struct security_buffer WorkstationName;
+ struct security_buffer SessionKey;
+ __le32 NegotiateFlags;
+ /*
+ * struct security_buffer for version info not present since we
+ * do not set the version is present flag
+ */
+ char UserString[0];
+} __packed;
+
+struct ntlmv2_resp {
+ char ntlmv2_hash[CIFS_ENCPWD_SIZE];
+ __le32 blob_signature;
+ __u32 reserved;
+ __le64 time;
+ __u64 client_chal; /* random */
+ __u32 reserved2;
+ /* array of name entries could follow ending in minimum 4 byte struct */
+} __packed;
+
+/* per smb session structure/fields */
+struct ntlmssp_auth {
+ /* whether session key is per smb session */
+ bool sesskey_per_smbsess;
+ /* sent by client in type 1 ntlmsssp exchange */
+ __u32 client_flags;
+ /* sent by server in type 2 ntlmssp exchange */
+ __u32 conn_flags;
+ /* sent to server */
+ unsigned char ciphertext[CIFS_CPHTXT_SIZE];
+ /* used by ntlmssp */
+ char cryptkey[CIFS_CRYPTO_KEY_SIZE];
+};
+#endif /* __KSMBD_NTLMSSP_H */
diff --git a/fs/ksmbd/oplock.c b/fs/ksmbd/oplock.c
new file mode 100644
index 000000000000..6ace6c2f22dc
--- /dev/null
+++ b/fs/ksmbd/oplock.c
@@ -0,0 +1,1709 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/moduleparam.h>
+
+#include "glob.h"
+#include "oplock.h"
+
+#include "smb_common.h"
+#include "smbstatus.h"
+#include "connection.h"
+#include "mgmt/user_session.h"
+#include "mgmt/share_config.h"
+#include "mgmt/tree_connect.h"
+
+static LIST_HEAD(lease_table_list);
+static DEFINE_RWLOCK(lease_list_lock);
+
+/**
+ * alloc_opinfo() - allocate a new opinfo object for oplock info
+ * @work: smb work
+ * @id: fid of open file
+ * @Tid: tree id of connection
+ *
+ * Return: allocated opinfo object on success, otherwise NULL
+ */
+static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
+ u64 id, __u16 Tid)
+{
+ struct ksmbd_session *sess = work->sess;
+ struct oplock_info *opinfo;
+
+ opinfo = kzalloc(sizeof(struct oplock_info), GFP_KERNEL);
+ if (!opinfo)
+ return NULL;
+
+ opinfo->sess = sess;
+ opinfo->conn = sess->conn;
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ opinfo->pending_break = 0;
+ opinfo->fid = id;
+ opinfo->Tid = Tid;
+ INIT_LIST_HEAD(&opinfo->op_entry);
+ INIT_LIST_HEAD(&opinfo->interim_list);
+ init_waitqueue_head(&opinfo->oplock_q);
+ init_waitqueue_head(&opinfo->oplock_brk);
+ atomic_set(&opinfo->refcount, 1);
+ atomic_set(&opinfo->breaking_cnt, 0);
+
+ return opinfo;
+}
+
+static void lease_add_list(struct oplock_info *opinfo)
+{
+ struct lease_table *lb = opinfo->o_lease->l_lb;
+
+ spin_lock(&lb->lb_lock);
+ list_add_rcu(&opinfo->lease_entry, &lb->lease_list);
+ spin_unlock(&lb->lb_lock);
+}
+
+static void lease_del_list(struct oplock_info *opinfo)
+{
+ struct lease_table *lb = opinfo->o_lease->l_lb;
+
+ if (!lb)
+ return;
+
+ spin_lock(&lb->lb_lock);
+ if (list_empty(&opinfo->lease_entry)) {
+ spin_unlock(&lb->lb_lock);
+ return;
+ }
+
+ list_del_init(&opinfo->lease_entry);
+ opinfo->o_lease->l_lb = NULL;
+ spin_unlock(&lb->lb_lock);
+}
+
+static void lb_add(struct lease_table *lb)
+{
+ write_lock(&lease_list_lock);
+ list_add(&lb->l_entry, &lease_table_list);
+ write_unlock(&lease_list_lock);
+}
+
+static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
+{
+ struct lease *lease;
+
+ lease = kmalloc(sizeof(struct lease), GFP_KERNEL);
+ if (!lease)
+ return -ENOMEM;
+
+ memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
+ lease->state = lctx->req_state;
+ lease->new_state = 0;
+ lease->flags = lctx->flags;
+ lease->duration = lctx->duration;
+ memcpy(lease->parent_lease_key, lctx->parent_lease_key, SMB2_LEASE_KEY_SIZE);
+ lease->version = lctx->version;
+ lease->epoch = 0;
+ INIT_LIST_HEAD(&opinfo->lease_entry);
+ opinfo->o_lease = lease;
+
+ return 0;
+}
+
+static void free_lease(struct oplock_info *opinfo)
+{
+ struct lease *lease;
+
+ lease = opinfo->o_lease;
+ kfree(lease);
+}
+
+static void free_opinfo(struct oplock_info *opinfo)
+{
+ if (opinfo->is_lease)
+ free_lease(opinfo);
+ kfree(opinfo);
+}
+
+static inline void opinfo_free_rcu(struct rcu_head *rcu_head)
+{
+ struct oplock_info *opinfo;
+
+ opinfo = container_of(rcu_head, struct oplock_info, rcu_head);
+ free_opinfo(opinfo);
+}
+
+struct oplock_info *opinfo_get(struct ksmbd_file *fp)
+{
+ struct oplock_info *opinfo;
+
+ rcu_read_lock();
+ opinfo = rcu_dereference(fp->f_opinfo);
+ if (opinfo && !atomic_inc_not_zero(&opinfo->refcount))
+ opinfo = NULL;
+ rcu_read_unlock();
+
+ return opinfo;
+}
+
+static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
+{
+ struct oplock_info *opinfo;
+
+ if (list_empty(&ci->m_op_list))
+ return NULL;
+
+ rcu_read_lock();
+ opinfo = list_first_or_null_rcu(&ci->m_op_list, struct oplock_info,
+ op_entry);
+ if (opinfo && !atomic_inc_not_zero(&opinfo->refcount))
+ opinfo = NULL;
+ rcu_read_unlock();
+
+ return opinfo;
+}
+
+void opinfo_put(struct oplock_info *opinfo)
+{
+ if (!atomic_dec_and_test(&opinfo->refcount))
+ return;
+
+ call_rcu(&opinfo->rcu_head, opinfo_free_rcu);
+}
+
+static void opinfo_add(struct oplock_info *opinfo)
+{
+ struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
+
+ write_lock(&ci->m_lock);
+ list_add_rcu(&opinfo->op_entry, &ci->m_op_list);
+ write_unlock(&ci->m_lock);
+}
+
+static void opinfo_del(struct oplock_info *opinfo)
+{
+ struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
+
+ if (opinfo->is_lease) {
+ write_lock(&lease_list_lock);
+ lease_del_list(opinfo);
+ write_unlock(&lease_list_lock);
+ }
+ write_lock(&ci->m_lock);
+ list_del_rcu(&opinfo->op_entry);
+ write_unlock(&ci->m_lock);
+}
+
+static unsigned long opinfo_count(struct ksmbd_file *fp)
+{
+ if (ksmbd_stream_fd(fp))
+ return atomic_read(&fp->f_ci->sop_count);
+ else
+ return atomic_read(&fp->f_ci->op_count);
+}
+
+static void opinfo_count_inc(struct ksmbd_file *fp)
+{
+ if (ksmbd_stream_fd(fp))
+ return atomic_inc(&fp->f_ci->sop_count);
+ else
+ return atomic_inc(&fp->f_ci->op_count);
+}
+
+static void opinfo_count_dec(struct ksmbd_file *fp)
+{
+ if (ksmbd_stream_fd(fp))
+ return atomic_dec(&fp->f_ci->sop_count);
+ else
+ return atomic_dec(&fp->f_ci->op_count);
+}
+
+/**
+ * opinfo_write_to_read() - convert a write oplock to read oplock
+ * @opinfo: current oplock info
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+int opinfo_write_to_read(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (!(opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
+ opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)) {
+ pr_err("bad oplock(0x%x)\n", opinfo->level);
+ if (opinfo->is_lease)
+ pr_err("lease state(0x%x)\n", lease->state);
+ return -EINVAL;
+ }
+ opinfo->level = SMB2_OPLOCK_LEVEL_II;
+
+ if (opinfo->is_lease)
+ lease->state = lease->new_state;
+ return 0;
+}
+
+/**
+ * opinfo_read_handle_to_read() - convert a read/handle oplock to read oplock
+ * @opinfo: current oplock info
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+int opinfo_read_handle_to_read(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ lease->state = lease->new_state;
+ opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ return 0;
+}
+
+/**
+ * opinfo_write_to_none() - convert a write oplock to none
+ * @opinfo: current oplock info
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+int opinfo_write_to_none(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (!(opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
+ opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)) {
+ pr_err("bad oplock(0x%x)\n", opinfo->level);
+ if (opinfo->is_lease)
+ pr_err("lease state(0x%x)\n", lease->state);
+ return -EINVAL;
+ }
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ if (opinfo->is_lease)
+ lease->state = lease->new_state;
+ return 0;
+}
+
+/**
+ * opinfo_read_to_none() - convert a write read to none
+ * @opinfo: current oplock info
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+int opinfo_read_to_none(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (opinfo->level != SMB2_OPLOCK_LEVEL_II) {
+ pr_err("bad oplock(0x%x)\n", opinfo->level);
+ if (opinfo->is_lease)
+ pr_err("lease state(0x%x)\n", lease->state);
+ return -EINVAL;
+ }
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ if (opinfo->is_lease)
+ lease->state = lease->new_state;
+ return 0;
+}
+
+/**
+ * lease_read_to_write() - upgrade lease state from read to write
+ * @opinfo: current lease info
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+int lease_read_to_write(struct oplock_info *opinfo)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (!(lease->state & SMB2_LEASE_READ_CACHING_LE)) {
+ ksmbd_debug(OPLOCK, "bad lease state(0x%x)\n", lease->state);
+ return -EINVAL;
+ }
+
+ lease->new_state = SMB2_LEASE_NONE_LE;
+ lease->state |= SMB2_LEASE_WRITE_CACHING_LE;
+ if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
+ opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
+ else
+ opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ return 0;
+}
+
+/**
+ * lease_none_upgrade() - upgrade lease state from none
+ * @opinfo: current lease info
+ * @new_state: new lease state
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+static int lease_none_upgrade(struct oplock_info *opinfo, __le32 new_state)
+{
+ struct lease *lease = opinfo->o_lease;
+
+ if (!(lease->state == SMB2_LEASE_NONE_LE)) {
+ ksmbd_debug(OPLOCK, "bad lease state(0x%x)\n", lease->state);
+ return -EINVAL;
+ }
+
+ lease->new_state = SMB2_LEASE_NONE_LE;
+ lease->state = new_state;
+ if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
+ if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
+ opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
+ else
+ opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ else if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
+ opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ else if (lease->state & SMB2_LEASE_READ_CACHING_LE)
+ opinfo->level = SMB2_OPLOCK_LEVEL_II;
+
+ return 0;
+}
+
+/**
+ * close_id_del_oplock() - release oplock object at file close time
+ * @fp: ksmbd file pointer
+ */
+void close_id_del_oplock(struct ksmbd_file *fp)
+{
+ struct oplock_info *opinfo;
+
+ if (S_ISDIR(file_inode(fp->filp)->i_mode))
+ return;
+
+ opinfo = opinfo_get(fp);
+ if (!opinfo)
+ return;
+
+ opinfo_del(opinfo);
+
+ rcu_assign_pointer(fp->f_opinfo, NULL);
+ if (opinfo->op_state == OPLOCK_ACK_WAIT) {
+ opinfo->op_state = OPLOCK_CLOSING;
+ wake_up_interruptible_all(&opinfo->oplock_q);
+ if (opinfo->is_lease) {
+ atomic_set(&opinfo->breaking_cnt, 0);
+ wake_up_interruptible_all(&opinfo->oplock_brk);
+ }
+ }
+
+ opinfo_count_dec(fp);
+ atomic_dec(&opinfo->refcount);
+ opinfo_put(opinfo);
+}
+
+/**
+ * grant_write_oplock() - grant exclusive/batch oplock or write lease
+ * @opinfo_new: new oplock info object
+ * @req_oplock: request oplock
+ * @lctx: lease context information
+ *
+ * Return: 0
+ */
+static void grant_write_oplock(struct oplock_info *opinfo_new, int req_oplock,
+ struct lease_ctx_info *lctx)
+{
+ struct lease *lease = opinfo_new->o_lease;
+
+ if (req_oplock == SMB2_OPLOCK_LEVEL_BATCH)
+ opinfo_new->level = SMB2_OPLOCK_LEVEL_BATCH;
+ else
+ opinfo_new->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+
+ if (lctx) {
+ lease->state = lctx->req_state;
+ memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
+ }
+}
+
+/**
+ * grant_read_oplock() - grant level2 oplock or read lease
+ * @opinfo_new: new oplock info object
+ * @lctx: lease context information
+ *
+ * Return: 0
+ */
+static void grant_read_oplock(struct oplock_info *opinfo_new,
+ struct lease_ctx_info *lctx)
+{
+ struct lease *lease = opinfo_new->o_lease;
+
+ opinfo_new->level = SMB2_OPLOCK_LEVEL_II;
+
+ if (lctx) {
+ lease->state = SMB2_LEASE_READ_CACHING_LE;
+ if (lctx->req_state & SMB2_LEASE_HANDLE_CACHING_LE)
+ lease->state |= SMB2_LEASE_HANDLE_CACHING_LE;
+ memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
+ }
+}
+
+/**
+ * grant_none_oplock() - grant none oplock or none lease
+ * @opinfo_new: new oplock info object
+ * @lctx: lease context information
+ *
+ * Return: 0
+ */
+static void grant_none_oplock(struct oplock_info *opinfo_new,
+ struct lease_ctx_info *lctx)
+{
+ struct lease *lease = opinfo_new->o_lease;
+
+ opinfo_new->level = SMB2_OPLOCK_LEVEL_NONE;
+
+ if (lctx) {
+ lease->state = 0;
+ memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
+ }
+}
+
+static inline int compare_guid_key(struct oplock_info *opinfo,
+ const char *guid1, const char *key1)
+{
+ const char *guid2, *key2;
+
+ guid2 = opinfo->conn->ClientGUID;
+ key2 = opinfo->o_lease->lease_key;
+ if (!memcmp(guid1, guid2, SMB2_CLIENT_GUID_SIZE) &&
+ !memcmp(key1, key2, SMB2_LEASE_KEY_SIZE))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * same_client_has_lease() - check whether current lease request is
+ * from lease owner of file
+ * @ci: master file pointer
+ * @client_guid: Client GUID
+ * @lctx: lease context information
+ *
+ * Return: oplock(lease) object on success, otherwise NULL
+ */
+static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
+ char *client_guid,
+ struct lease_ctx_info *lctx)
+{
+ int ret;
+ struct lease *lease;
+ struct oplock_info *opinfo;
+ struct oplock_info *m_opinfo = NULL;
+
+ if (!lctx)
+ return NULL;
+
+ /*
+ * Compare lease key and client_guid to know request from same owner
+ * of same client
+ */
+ read_lock(&ci->m_lock);
+ list_for_each_entry(opinfo, &ci->m_op_list, op_entry) {
+ if (!opinfo->is_lease)
+ continue;
+ read_unlock(&ci->m_lock);
+ lease = opinfo->o_lease;
+
+ ret = compare_guid_key(opinfo, client_guid, lctx->lease_key);
+ if (ret) {
+ m_opinfo = opinfo;
+ /* skip upgrading lease about breaking lease */
+ if (atomic_read(&opinfo->breaking_cnt)) {
+ read_lock(&ci->m_lock);
+ continue;
+ }
+
+ /* upgrading lease */
+ if ((atomic_read(&ci->op_count) +
+ atomic_read(&ci->sop_count)) == 1) {
+ if (lease->state ==
+ (lctx->req_state & lease->state)) {
+ lease->state |= lctx->req_state;
+ if (lctx->req_state &
+ SMB2_LEASE_WRITE_CACHING_LE)
+ lease_read_to_write(opinfo);
+ }
+ } else if ((atomic_read(&ci->op_count) +
+ atomic_read(&ci->sop_count)) > 1) {
+ if (lctx->req_state ==
+ (SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE))
+ lease->state = lctx->req_state;
+ }
+
+ if (lctx->req_state && lease->state ==
+ SMB2_LEASE_NONE_LE)
+ lease_none_upgrade(opinfo, lctx->req_state);
+ }
+ read_lock(&ci->m_lock);
+ }
+ read_unlock(&ci->m_lock);
+
+ return m_opinfo;
+}
+
+static void wait_for_break_ack(struct oplock_info *opinfo)
+{
+ int rc = 0;
+
+ rc = wait_event_interruptible_timeout(opinfo->oplock_q,
+ opinfo->op_state == OPLOCK_STATE_NONE ||
+ opinfo->op_state == OPLOCK_CLOSING,
+ OPLOCK_WAIT_TIME);
+
+ /* is this a timeout ? */
+ if (!rc) {
+ if (opinfo->is_lease)
+ opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ }
+}
+
+static void wake_up_oplock_break(struct oplock_info *opinfo)
+{
+ clear_bit_unlock(0, &opinfo->pending_break);
+ /* memory barrier is needed for wake_up_bit() */
+ smp_mb__after_atomic();
+ wake_up_bit(&opinfo->pending_break, 0);
+}
+
+static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
+{
+ while (test_and_set_bit(0, &opinfo->pending_break)) {
+ wait_on_bit(&opinfo->pending_break, 0, TASK_UNINTERRUPTIBLE);
+
+ /* Not immediately break to none. */
+ opinfo->open_trunc = 0;
+
+ if (opinfo->op_state == OPLOCK_CLOSING)
+ return -ENOENT;
+ else if (!opinfo->is_lease && opinfo->level <= req_op_level)
+ return 1;
+ }
+
+ if (!opinfo->is_lease && opinfo->level <= req_op_level) {
+ wake_up_oplock_break(opinfo);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int allocate_oplock_break_buf(struct ksmbd_work *work)
+{
+ work->response_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, GFP_KERNEL);
+ if (!work->response_buf)
+ return -ENOMEM;
+ work->response_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
+ return 0;
+}
+
+/**
+ * __smb2_oplock_break_noti() - send smb2 oplock break cmd from conn
+ * to client
+ * @wk: smb work object
+ *
+ * There are two ways this function can be called. 1- while file open we break
+ * from exclusive/batch lock to levelII oplock and 2- while file write/truncate
+ * we break from levelII oplock no oplock.
+ * work->request_buf contains oplock_info.
+ */
+static void __smb2_oplock_break_noti(struct work_struct *wk)
+{
+ struct smb2_oplock_break *rsp = NULL;
+ struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
+ struct ksmbd_conn *conn = work->conn;
+ struct oplock_break_info *br_info = work->request_buf;
+ struct smb2_hdr *rsp_hdr;
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_durable_fd(br_info->fid);
+ if (!fp) {
+ atomic_dec(&conn->r_count);
+ ksmbd_free_work_struct(work);
+ return;
+ }
+
+ if (allocate_oplock_break_buf(work)) {
+ pr_err("smb2_allocate_rsp_buf failed! ");
+ atomic_dec(&conn->r_count);
+ ksmbd_fd_put(work, fp);
+ ksmbd_free_work_struct(work);
+ return;
+ }
+
+ rsp_hdr = work->response_buf;
+ memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
+ rsp_hdr->smb2_buf_length =
+ cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
+ rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
+ rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
+ rsp_hdr->CreditRequest = cpu_to_le16(0);
+ rsp_hdr->Command = SMB2_OPLOCK_BREAK;
+ rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ rsp_hdr->NextCommand = 0;
+ rsp_hdr->MessageId = cpu_to_le64(-1);
+ rsp_hdr->Id.SyncId.ProcessId = 0;
+ rsp_hdr->Id.SyncId.TreeId = 0;
+ rsp_hdr->SessionId = 0;
+ memset(rsp_hdr->Signature, 0, 16);
+
+ rsp = work->response_buf;
+
+ rsp->StructureSize = cpu_to_le16(24);
+ if (!br_info->open_trunc &&
+ (br_info->level == SMB2_OPLOCK_LEVEL_BATCH ||
+ br_info->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE))
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_II;
+ else
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ rsp->Reserved = 0;
+ rsp->Reserved2 = 0;
+ rsp->PersistentFid = cpu_to_le64(fp->persistent_id);
+ rsp->VolatileFid = cpu_to_le64(fp->volatile_id);
+
+ inc_rfc1001_len(rsp, 24);
+
+ ksmbd_debug(OPLOCK,
+ "sending oplock break v_id %llu p_id = %llu lock level = %d\n",
+ rsp->VolatileFid, rsp->PersistentFid, rsp->OplockLevel);
+
+ ksmbd_fd_put(work, fp);
+ ksmbd_conn_write(work);
+ ksmbd_free_work_struct(work);
+ atomic_dec(&conn->r_count);
+}
+
+/**
+ * smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
+ * break command from server to client
+ * @opinfo: oplock info object
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int smb2_oplock_break_noti(struct oplock_info *opinfo)
+{
+ struct ksmbd_conn *conn = opinfo->conn;
+ struct oplock_break_info *br_info;
+ int ret = 0;
+ struct ksmbd_work *work = ksmbd_alloc_work_struct();
+
+ if (!work)
+ return -ENOMEM;
+
+ br_info = kmalloc(sizeof(struct oplock_break_info), GFP_KERNEL);
+ if (!br_info) {
+ ksmbd_free_work_struct(work);
+ return -ENOMEM;
+ }
+
+ br_info->level = opinfo->level;
+ br_info->fid = opinfo->fid;
+ br_info->open_trunc = opinfo->open_trunc;
+
+ work->request_buf = (char *)br_info;
+ work->conn = conn;
+ work->sess = opinfo->sess;
+
+ atomic_inc(&conn->r_count);
+ if (opinfo->op_state == OPLOCK_ACK_WAIT) {
+ INIT_WORK(&work->work, __smb2_oplock_break_noti);
+ ksmbd_queue_work(work);
+
+ wait_for_break_ack(opinfo);
+ } else {
+ __smb2_oplock_break_noti(&work->work);
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_II)
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+ return ret;
+}
+
+/**
+ * __smb2_lease_break_noti() - send lease break command from server
+ * to client
+ * @wk: smb work object
+ */
+static void __smb2_lease_break_noti(struct work_struct *wk)
+{
+ struct smb2_lease_break *rsp = NULL;
+ struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
+ struct lease_break_info *br_info = work->request_buf;
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_hdr *rsp_hdr;
+
+ if (allocate_oplock_break_buf(work)) {
+ ksmbd_debug(OPLOCK, "smb2_allocate_rsp_buf failed! ");
+ ksmbd_free_work_struct(work);
+ atomic_dec(&conn->r_count);
+ return;
+ }
+
+ rsp_hdr = work->response_buf;
+ memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
+ rsp_hdr->smb2_buf_length =
+ cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
+ rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
+ rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
+ rsp_hdr->CreditRequest = cpu_to_le16(0);
+ rsp_hdr->Command = SMB2_OPLOCK_BREAK;
+ rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ rsp_hdr->NextCommand = 0;
+ rsp_hdr->MessageId = cpu_to_le64(-1);
+ rsp_hdr->Id.SyncId.ProcessId = 0;
+ rsp_hdr->Id.SyncId.TreeId = 0;
+ rsp_hdr->SessionId = 0;
+ memset(rsp_hdr->Signature, 0, 16);
+
+ rsp = work->response_buf;
+ rsp->StructureSize = cpu_to_le16(44);
+ rsp->Epoch = br_info->epoch;
+ rsp->Flags = 0;
+
+ if (br_info->curr_state & (SMB2_LEASE_WRITE_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE))
+ rsp->Flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+
+ memcpy(rsp->LeaseKey, br_info->lease_key, SMB2_LEASE_KEY_SIZE);
+ rsp->CurrentLeaseState = br_info->curr_state;
+ rsp->NewLeaseState = br_info->new_state;
+ rsp->BreakReason = 0;
+ rsp->AccessMaskHint = 0;
+ rsp->ShareMaskHint = 0;
+
+ inc_rfc1001_len(rsp, 44);
+
+ ksmbd_conn_write(work);
+ ksmbd_free_work_struct(work);
+ atomic_dec(&conn->r_count);
+}
+
+/**
+ * smb2_lease_break_noti() - break lease when a new client request
+ * write lease
+ * @opinfo: conains lease state information
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int smb2_lease_break_noti(struct oplock_info *opinfo)
+{
+ struct ksmbd_conn *conn = opinfo->conn;
+ struct list_head *tmp, *t;
+ struct ksmbd_work *work;
+ struct lease_break_info *br_info;
+ struct lease *lease = opinfo->o_lease;
+
+ work = ksmbd_alloc_work_struct();
+ if (!work)
+ return -ENOMEM;
+
+ br_info = kmalloc(sizeof(struct lease_break_info), GFP_KERNEL);
+ if (!br_info) {
+ ksmbd_free_work_struct(work);
+ return -ENOMEM;
+ }
+
+ br_info->curr_state = lease->state;
+ br_info->new_state = lease->new_state;
+ if (lease->version == 2)
+ br_info->epoch = cpu_to_le16(++lease->epoch);
+ else
+ br_info->epoch = 0;
+ memcpy(br_info->lease_key, lease->lease_key, SMB2_LEASE_KEY_SIZE);
+
+ work->request_buf = (char *)br_info;
+ work->conn = conn;
+ work->sess = opinfo->sess;
+
+ atomic_inc(&conn->r_count);
+ if (opinfo->op_state == OPLOCK_ACK_WAIT) {
+ list_for_each_safe(tmp, t, &opinfo->interim_list) {
+ struct ksmbd_work *in_work;
+
+ in_work = list_entry(tmp, struct ksmbd_work,
+ interim_entry);
+ setup_async_work(in_work, NULL, NULL);
+ smb2_send_interim_resp(in_work, STATUS_PENDING);
+ list_del(&in_work->interim_entry);
+ }
+ INIT_WORK(&work->work, __smb2_lease_break_noti);
+ ksmbd_queue_work(work);
+ wait_for_break_ack(opinfo);
+ } else {
+ __smb2_lease_break_noti(&work->work);
+ if (opinfo->o_lease->new_state == SMB2_LEASE_NONE_LE) {
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ }
+ }
+ return 0;
+}
+
+static void wait_lease_breaking(struct oplock_info *opinfo)
+{
+ if (!opinfo->is_lease)
+ return;
+
+ wake_up_interruptible_all(&opinfo->oplock_brk);
+ if (atomic_read(&opinfo->breaking_cnt)) {
+ int ret = 0;
+
+ ret = wait_event_interruptible_timeout(opinfo->oplock_brk,
+ atomic_read(&opinfo->breaking_cnt) == 0,
+ HZ);
+ if (!ret)
+ atomic_set(&opinfo->breaking_cnt, 0);
+ }
+}
+
+static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level)
+{
+ int err = 0;
+
+ /* Need to break exclusive/batch oplock, write lease or overwrite_if */
+ ksmbd_debug(OPLOCK,
+ "request to send oplock(level : 0x%x) break notification\n",
+ brk_opinfo->level);
+
+ if (brk_opinfo->is_lease) {
+ struct lease *lease = brk_opinfo->o_lease;
+
+ atomic_inc(&brk_opinfo->breaking_cnt);
+
+ err = oplock_break_pending(brk_opinfo, req_op_level);
+ if (err)
+ return err < 0 ? err : 0;
+
+ if (brk_opinfo->open_trunc) {
+ /*
+ * Create overwrite break trigger the lease break to
+ * none.
+ */
+ lease->new_state = SMB2_LEASE_NONE_LE;
+ } else {
+ if (lease->state & SMB2_LEASE_WRITE_CACHING_LE) {
+ if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
+ lease->new_state =
+ SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE;
+ else
+ lease->new_state =
+ SMB2_LEASE_READ_CACHING_LE;
+ } else {
+ if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
+ lease->new_state =
+ SMB2_LEASE_READ_CACHING_LE;
+ else
+ lease->new_state = SMB2_LEASE_NONE_LE;
+ }
+ }
+
+ if (lease->state & (SMB2_LEASE_WRITE_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE))
+ brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+ else
+ atomic_dec(&brk_opinfo->breaking_cnt);
+ } else {
+ err = oplock_break_pending(brk_opinfo, req_op_level);
+ if (err)
+ return err < 0 ? err : 0;
+
+ if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
+ brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
+ brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+ }
+
+ if (brk_opinfo->is_lease)
+ err = smb2_lease_break_noti(brk_opinfo);
+ else
+ err = smb2_oplock_break_noti(brk_opinfo);
+
+ ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
+ if (brk_opinfo->op_state == OPLOCK_CLOSING)
+ err = -ENOENT;
+ wake_up_oplock_break(brk_opinfo);
+
+ wait_lease_breaking(brk_opinfo);
+
+ return err;
+}
+
+void destroy_lease_table(struct ksmbd_conn *conn)
+{
+ struct lease_table *lb, *lbtmp;
+ struct oplock_info *opinfo;
+
+ write_lock(&lease_list_lock);
+ if (list_empty(&lease_table_list)) {
+ write_unlock(&lease_list_lock);
+ return;
+ }
+
+ list_for_each_entry_safe(lb, lbtmp, &lease_table_list, l_entry) {
+ if (conn && memcmp(lb->client_guid, conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE))
+ continue;
+again:
+ rcu_read_lock();
+ list_for_each_entry_rcu(opinfo, &lb->lease_list,
+ lease_entry) {
+ rcu_read_unlock();
+ lease_del_list(opinfo);
+ goto again;
+ }
+ rcu_read_unlock();
+ list_del(&lb->l_entry);
+ kfree(lb);
+ }
+ write_unlock(&lease_list_lock);
+}
+
+int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
+ struct lease_ctx_info *lctx)
+{
+ struct oplock_info *opinfo;
+ int err = 0;
+ struct lease_table *lb;
+
+ if (!lctx)
+ return err;
+
+ read_lock(&lease_list_lock);
+ if (list_empty(&lease_table_list)) {
+ read_unlock(&lease_list_lock);
+ return 0;
+ }
+
+ list_for_each_entry(lb, &lease_table_list, l_entry) {
+ if (!memcmp(lb->client_guid, sess->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE))
+ goto found;
+ }
+ read_unlock(&lease_list_lock);
+
+ return 0;
+
+found:
+ rcu_read_lock();
+ list_for_each_entry_rcu(opinfo, &lb->lease_list, lease_entry) {
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
+ rcu_read_unlock();
+ if (opinfo->o_fp->f_ci == ci)
+ goto op_next;
+ err = compare_guid_key(opinfo, sess->conn->ClientGUID,
+ lctx->lease_key);
+ if (err) {
+ err = -EINVAL;
+ ksmbd_debug(OPLOCK,
+ "found same lease key is already used in other files\n");
+ opinfo_put(opinfo);
+ goto out;
+ }
+op_next:
+ opinfo_put(opinfo);
+ rcu_read_lock();
+ }
+ rcu_read_unlock();
+
+out:
+ read_unlock(&lease_list_lock);
+ return err;
+}
+
+static void copy_lease(struct oplock_info *op1, struct oplock_info *op2)
+{
+ struct lease *lease1 = op1->o_lease;
+ struct lease *lease2 = op2->o_lease;
+
+ op2->level = op1->level;
+ lease2->state = lease1->state;
+ memcpy(lease2->lease_key, lease1->lease_key,
+ SMB2_LEASE_KEY_SIZE);
+ lease2->duration = lease1->duration;
+ lease2->flags = lease1->flags;
+}
+
+static int add_lease_global_list(struct oplock_info *opinfo)
+{
+ struct lease_table *lb;
+
+ read_lock(&lease_list_lock);
+ list_for_each_entry(lb, &lease_table_list, l_entry) {
+ if (!memcmp(lb->client_guid, opinfo->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE)) {
+ opinfo->o_lease->l_lb = lb;
+ lease_add_list(opinfo);
+ read_unlock(&lease_list_lock);
+ return 0;
+ }
+ }
+ read_unlock(&lease_list_lock);
+
+ lb = kmalloc(sizeof(struct lease_table), GFP_KERNEL);
+ if (!lb)
+ return -ENOMEM;
+
+ memcpy(lb->client_guid, opinfo->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE);
+ INIT_LIST_HEAD(&lb->lease_list);
+ spin_lock_init(&lb->lb_lock);
+ opinfo->o_lease->l_lb = lb;
+ lease_add_list(opinfo);
+ lb_add(lb);
+ return 0;
+}
+
+static void set_oplock_level(struct oplock_info *opinfo, int level,
+ struct lease_ctx_info *lctx)
+{
+ switch (level) {
+ case SMB2_OPLOCK_LEVEL_BATCH:
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+ grant_write_oplock(opinfo, level, lctx);
+ break;
+ case SMB2_OPLOCK_LEVEL_II:
+ grant_read_oplock(opinfo, lctx);
+ break;
+ default:
+ grant_none_oplock(opinfo, lctx);
+ break;
+ }
+}
+
+/**
+ * smb_grant_oplock() - handle oplock/lease request on file open
+ * @work: smb work
+ * @req_op_level: oplock level
+ * @pid: id of open file
+ * @fp: ksmbd file pointer
+ * @tid: Tree id of connection
+ * @lctx: lease context information on file open
+ * @share_ret: share mode
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
+ struct ksmbd_file *fp, __u16 tid,
+ struct lease_ctx_info *lctx, int share_ret)
+{
+ struct ksmbd_session *sess = work->sess;
+ int err = 0;
+ struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
+ struct ksmbd_inode *ci = fp->f_ci;
+ bool prev_op_has_lease;
+ __le32 prev_op_state = 0;
+
+ /* not support directory lease */
+ if (S_ISDIR(file_inode(fp->filp)->i_mode))
+ return 0;
+
+ opinfo = alloc_opinfo(work, pid, tid);
+ if (!opinfo)
+ return -ENOMEM;
+
+ if (lctx) {
+ err = alloc_lease(opinfo, lctx);
+ if (err)
+ goto err_out;
+ opinfo->is_lease = 1;
+ }
+
+ /* ci does not have any oplock */
+ if (!opinfo_count(fp))
+ goto set_lev;
+
+ /* grant none-oplock if second open is trunc */
+ if (fp->attrib_only && fp->cdoption != FILE_OVERWRITE_IF_LE &&
+ fp->cdoption != FILE_OVERWRITE_LE &&
+ fp->cdoption != FILE_SUPERSEDE_LE) {
+ req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+ goto set_lev;
+ }
+
+ if (lctx) {
+ struct oplock_info *m_opinfo;
+
+ /* is lease already granted ? */
+ m_opinfo = same_client_has_lease(ci, sess->conn->ClientGUID,
+ lctx);
+ if (m_opinfo) {
+ copy_lease(m_opinfo, opinfo);
+ if (atomic_read(&m_opinfo->breaking_cnt))
+ opinfo->o_lease->flags =
+ SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
+ goto out;
+ }
+ }
+ prev_opinfo = opinfo_get_list(ci);
+ if (!prev_opinfo ||
+ (prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx))
+ goto set_lev;
+ prev_op_has_lease = prev_opinfo->is_lease;
+ if (prev_op_has_lease)
+ prev_op_state = prev_opinfo->o_lease->state;
+
+ if (share_ret < 0 &&
+ prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ err = share_ret;
+ opinfo_put(prev_opinfo);
+ goto err_out;
+ }
+
+ if (prev_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
+ prev_opinfo->level != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ opinfo_put(prev_opinfo);
+ goto op_break_not_needed;
+ }
+
+ list_add(&work->interim_entry, &prev_opinfo->interim_list);
+ err = oplock_break(prev_opinfo, SMB2_OPLOCK_LEVEL_II);
+ opinfo_put(prev_opinfo);
+ if (err == -ENOENT)
+ goto set_lev;
+ /* Check all oplock was freed by close */
+ else if (err < 0)
+ goto err_out;
+
+op_break_not_needed:
+ if (share_ret < 0) {
+ err = share_ret;
+ goto err_out;
+ }
+
+ if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+ req_op_level = SMB2_OPLOCK_LEVEL_II;
+
+ /* grant fixed oplock on stacked locking between lease and oplock */
+ if (prev_op_has_lease && !lctx)
+ if (prev_op_state & SMB2_LEASE_HANDLE_CACHING_LE)
+ req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+
+ if (!prev_op_has_lease && lctx) {
+ req_op_level = SMB2_OPLOCK_LEVEL_II;
+ lctx->req_state = SMB2_LEASE_READ_CACHING_LE;
+ }
+
+set_lev:
+ set_oplock_level(opinfo, req_op_level, lctx);
+
+out:
+ rcu_assign_pointer(fp->f_opinfo, opinfo);
+ opinfo->o_fp = fp;
+
+ opinfo_count_inc(fp);
+ opinfo_add(opinfo);
+ if (opinfo->is_lease) {
+ err = add_lease_global_list(opinfo);
+ if (err)
+ goto err_out;
+ }
+
+ return 0;
+err_out:
+ free_opinfo(opinfo);
+ return err;
+}
+
+/**
+ * smb_break_all_write_oplock() - break batch/exclusive oplock to level2
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ * @is_trunc: truncate on open
+ */
+static void smb_break_all_write_oplock(struct ksmbd_work *work,
+ struct ksmbd_file *fp, int is_trunc)
+{
+ struct oplock_info *brk_opinfo;
+
+ brk_opinfo = opinfo_get_list(fp->f_ci);
+ if (!brk_opinfo)
+ return;
+ if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
+ brk_opinfo->level != SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+ opinfo_put(brk_opinfo);
+ return;
+ }
+
+ brk_opinfo->open_trunc = is_trunc;
+ list_add(&work->interim_entry, &brk_opinfo->interim_list);
+ oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II);
+ opinfo_put(brk_opinfo);
+}
+
+/**
+ * smb_break_all_levII_oplock() - send level2 oplock or read lease break command
+ * from server to client
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ * @is_trunc: truncate on open
+ */
+void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
+ int is_trunc)
+{
+ struct oplock_info *op, *brk_op;
+ struct ksmbd_inode *ci;
+ struct ksmbd_conn *conn = work->sess->conn;
+
+ if (!test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_OPLOCKS))
+ return;
+
+ ci = fp->f_ci;
+ op = opinfo_get(fp);
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(brk_op, &ci->m_op_list, op_entry) {
+ if (!atomic_inc_not_zero(&brk_op->refcount))
+ continue;
+ rcu_read_unlock();
+ if (brk_op->is_lease && (brk_op->o_lease->state &
+ (~(SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_HANDLE_CACHING_LE)))) {
+ ksmbd_debug(OPLOCK, "unexpected lease state(0x%x)\n",
+ brk_op->o_lease->state);
+ goto next;
+ } else if (brk_op->level !=
+ SMB2_OPLOCK_LEVEL_II) {
+ ksmbd_debug(OPLOCK, "unexpected oplock(0x%x)\n",
+ brk_op->level);
+ goto next;
+ }
+
+ /* Skip oplock being break to none */
+ if (brk_op->is_lease &&
+ brk_op->o_lease->new_state == SMB2_LEASE_NONE_LE &&
+ atomic_read(&brk_op->breaking_cnt))
+ goto next;
+
+ if (op && op->is_lease && brk_op->is_lease &&
+ !memcmp(conn->ClientGUID, brk_op->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE) &&
+ !memcmp(op->o_lease->lease_key, brk_op->o_lease->lease_key,
+ SMB2_LEASE_KEY_SIZE))
+ goto next;
+ brk_op->open_trunc = is_trunc;
+ oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE);
+next:
+ opinfo_put(brk_op);
+ rcu_read_lock();
+ }
+ rcu_read_unlock();
+
+ if (op)
+ opinfo_put(op);
+}
+
+/**
+ * smb_break_all_oplock() - break both batch/exclusive and level2 oplock
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ */
+void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
+{
+ if (!test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_OPLOCKS))
+ return;
+
+ smb_break_all_write_oplock(work, fp, 1);
+ smb_break_all_levII_oplock(work, fp, 1);
+}
+
+/**
+ * smb2_map_lease_to_oplock() - map lease state to corresponding oplock type
+ * @lease_state: lease type
+ *
+ * Return: 0 if no mapping, otherwise corresponding oplock type
+ */
+__u8 smb2_map_lease_to_oplock(__le32 lease_state)
+{
+ if (lease_state == (SMB2_LEASE_HANDLE_CACHING_LE |
+ SMB2_LEASE_READ_CACHING_LE |
+ SMB2_LEASE_WRITE_CACHING_LE)) {
+ return SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (lease_state != SMB2_LEASE_WRITE_CACHING_LE &&
+ lease_state & SMB2_LEASE_WRITE_CACHING_LE) {
+ if (!(lease_state & SMB2_LEASE_HANDLE_CACHING_LE))
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (lease_state & SMB2_LEASE_READ_CACHING_LE) {
+ return SMB2_OPLOCK_LEVEL_II;
+ }
+ return 0;
+}
+
+/**
+ * create_lease_buf() - create lease context for open cmd response
+ * @rbuf: buffer to create lease context response
+ * @lease: buffer to stored parsed lease state information
+ */
+void create_lease_buf(u8 *rbuf, struct lease *lease)
+{
+ char *LeaseKey = (char *)&lease->lease_key;
+
+ if (lease->version == 2) {
+ struct create_lease_v2 *buf = (struct create_lease_v2 *)rbuf;
+ char *ParentLeaseKey = (char *)&lease->parent_lease_key;
+
+ memset(buf, 0, sizeof(struct create_lease_v2));
+ buf->lcontext.LeaseKeyLow = *((__le64 *)LeaseKey);
+ buf->lcontext.LeaseKeyHigh = *((__le64 *)(LeaseKey + 8));
+ buf->lcontext.LeaseFlags = lease->flags;
+ buf->lcontext.LeaseState = lease->state;
+ buf->lcontext.ParentLeaseKeyLow = *((__le64 *)ParentLeaseKey);
+ buf->lcontext.ParentLeaseKeyHigh = *((__le64 *)(ParentLeaseKey + 8));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_lease_v2, lcontext));
+ buf->ccontext.DataLength = cpu_to_le32(sizeof(struct lease_context_v2));
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_lease_v2, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ buf->Name[0] = 'R';
+ buf->Name[1] = 'q';
+ buf->Name[2] = 'L';
+ buf->Name[3] = 's';
+ } else {
+ struct create_lease *buf = (struct create_lease *)rbuf;
+
+ memset(buf, 0, sizeof(struct create_lease));
+ buf->lcontext.LeaseKeyLow = *((__le64 *)LeaseKey);
+ buf->lcontext.LeaseKeyHigh = *((__le64 *)(LeaseKey + 8));
+ buf->lcontext.LeaseFlags = lease->flags;
+ buf->lcontext.LeaseState = lease->state;
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_lease, lcontext));
+ buf->ccontext.DataLength = cpu_to_le32(sizeof(struct lease_context));
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_lease, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ buf->Name[0] = 'R';
+ buf->Name[1] = 'q';
+ buf->Name[2] = 'L';
+ buf->Name[3] = 's';
+ }
+}
+
+/**
+ * parse_lease_state() - parse lease context containted in file open request
+ * @open_req: buffer containing smb2 file open(create) request
+ *
+ * Return: oplock state, -ENOENT if create lease context not found
+ */
+struct lease_ctx_info *parse_lease_state(void *open_req)
+{
+ char *data_offset;
+ struct create_context *cc;
+ unsigned int next = 0;
+ char *name;
+ bool found = false;
+ struct smb2_create_req *req = (struct smb2_create_req *)open_req;
+ struct lease_ctx_info *lreq = kzalloc(sizeof(struct lease_ctx_info),
+ GFP_KERNEL);
+ if (!lreq)
+ return NULL;
+
+ data_offset = (char *)req + 4 + le32_to_cpu(req->CreateContextsOffset);
+ cc = (struct create_context *)data_offset;
+ do {
+ cc = (struct create_context *)((char *)cc + next);
+ name = le16_to_cpu(cc->NameOffset) + (char *)cc;
+ if (le16_to_cpu(cc->NameLength) != 4 ||
+ strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) {
+ next = le32_to_cpu(cc->Next);
+ continue;
+ }
+ found = true;
+ break;
+ } while (next != 0);
+
+ if (found) {
+ if (sizeof(struct lease_context_v2) == le32_to_cpu(cc->DataLength)) {
+ struct create_lease_v2 *lc = (struct create_lease_v2 *)cc;
+
+ *((__le64 *)lreq->lease_key) = lc->lcontext.LeaseKeyLow;
+ *((__le64 *)(lreq->lease_key + 8)) = lc->lcontext.LeaseKeyHigh;
+ lreq->req_state = lc->lcontext.LeaseState;
+ lreq->flags = lc->lcontext.LeaseFlags;
+ lreq->duration = lc->lcontext.LeaseDuration;
+ *((__le64 *)lreq->parent_lease_key) = lc->lcontext.ParentLeaseKeyLow;
+ *((__le64 *)(lreq->parent_lease_key + 8)) = lc->lcontext.ParentLeaseKeyHigh;
+ lreq->version = 2;
+ } else {
+ struct create_lease *lc = (struct create_lease *)cc;
+
+ *((__le64 *)lreq->lease_key) = lc->lcontext.LeaseKeyLow;
+ *((__le64 *)(lreq->lease_key + 8)) = lc->lcontext.LeaseKeyHigh;
+ lreq->req_state = lc->lcontext.LeaseState;
+ lreq->flags = lc->lcontext.LeaseFlags;
+ lreq->duration = lc->lcontext.LeaseDuration;
+ lreq->version = 1;
+ }
+ return lreq;
+ }
+
+ kfree(lreq);
+ return NULL;
+}
+
+/**
+ * smb2_find_context_vals() - find a particular context info in open request
+ * @open_req: buffer containing smb2 file open(create) request
+ * @tag: context name to search for
+ *
+ * Return: pointer to requested context, NULL if @str context not found
+ * or error pointer if name length is invalid.
+ */
+struct create_context *smb2_find_context_vals(void *open_req, const char *tag)
+{
+ char *data_offset;
+ struct create_context *cc;
+ unsigned int next = 0;
+ char *name;
+ struct smb2_create_req *req = (struct smb2_create_req *)open_req;
+
+ data_offset = (char *)req + 4 + le32_to_cpu(req->CreateContextsOffset);
+ cc = (struct create_context *)data_offset;
+ do {
+ int val;
+
+ cc = (struct create_context *)((char *)cc + next);
+ name = le16_to_cpu(cc->NameOffset) + (char *)cc;
+ val = le16_to_cpu(cc->NameLength);
+ if (val < 4)
+ return ERR_PTR(-EINVAL);
+
+ if (memcmp(name, tag, val) == 0)
+ return cc;
+ next = le32_to_cpu(cc->Next);
+ } while (next != 0);
+
+ return NULL;
+}
+
+/**
+ * create_durable_rsp_buf() - create durable handle context
+ * @cc: buffer to create durable context response
+ */
+void create_durable_rsp_buf(char *cc)
+{
+ struct create_durable_rsp *buf;
+
+ buf = (struct create_durable_rsp *)cc;
+ memset(buf, 0, sizeof(struct create_durable_rsp));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_durable_rsp, Data));
+ buf->ccontext.DataLength = cpu_to_le32(8);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_durable_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ /* SMB2_CREATE_DURABLE_HANDLE_RESPONSE is "DHnQ" */
+ buf->Name[0] = 'D';
+ buf->Name[1] = 'H';
+ buf->Name[2] = 'n';
+ buf->Name[3] = 'Q';
+}
+
+/**
+ * create_durable_v2_rsp_buf() - create durable handle v2 context
+ * @cc: buffer to create durable context response
+ * @fp: ksmbd file pointer
+ */
+void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp)
+{
+ struct create_durable_v2_rsp *buf;
+
+ buf = (struct create_durable_v2_rsp *)cc;
+ memset(buf, 0, sizeof(struct create_durable_rsp));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_durable_rsp, Data));
+ buf->ccontext.DataLength = cpu_to_le32(8);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_durable_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ /* SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2 is "DH2Q" */
+ buf->Name[0] = 'D';
+ buf->Name[1] = 'H';
+ buf->Name[2] = '2';
+ buf->Name[3] = 'Q';
+
+ buf->Timeout = cpu_to_le32(fp->durable_timeout);
+}
+
+/**
+ * create_mxac_rsp_buf() - create query maximal access context
+ * @cc: buffer to create maximal access context response
+ * @maximal_access: maximal access
+ */
+void create_mxac_rsp_buf(char *cc, int maximal_access)
+{
+ struct create_mxac_rsp *buf;
+
+ buf = (struct create_mxac_rsp *)cc;
+ memset(buf, 0, sizeof(struct create_mxac_rsp));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_mxac_rsp, QueryStatus));
+ buf->ccontext.DataLength = cpu_to_le32(8);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_mxac_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ /* SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE is "MxAc" */
+ buf->Name[0] = 'M';
+ buf->Name[1] = 'x';
+ buf->Name[2] = 'A';
+ buf->Name[3] = 'c';
+
+ buf->QueryStatus = STATUS_SUCCESS;
+ buf->MaximalAccess = cpu_to_le32(maximal_access);
+}
+
+void create_disk_id_rsp_buf(char *cc, __u64 file_id, __u64 vol_id)
+{
+ struct create_disk_id_rsp *buf;
+
+ buf = (struct create_disk_id_rsp *)cc;
+ memset(buf, 0, sizeof(struct create_disk_id_rsp));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_disk_id_rsp, DiskFileId));
+ buf->ccontext.DataLength = cpu_to_le32(32);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_mxac_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(4);
+ /* SMB2_CREATE_QUERY_ON_DISK_ID_RESPONSE is "QFid" */
+ buf->Name[0] = 'Q';
+ buf->Name[1] = 'F';
+ buf->Name[2] = 'i';
+ buf->Name[3] = 'd';
+
+ buf->DiskFileId = cpu_to_le64(file_id);
+ buf->VolumeId = cpu_to_le64(vol_id);
+}
+
+/**
+ * create_posix_rsp_buf() - create posix extension context
+ * @cc: buffer to create posix on posix response
+ * @fp: ksmbd file pointer
+ */
+void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp)
+{
+ struct create_posix_rsp *buf;
+ struct inode *inode = file_inode(fp->filp);
+ struct user_namespace *user_ns = file_mnt_user_ns(fp->filp);
+
+ buf = (struct create_posix_rsp *)cc;
+ memset(buf, 0, sizeof(struct create_posix_rsp));
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof
+ (struct create_posix_rsp, nlink));
+ buf->ccontext.DataLength = cpu_to_le32(52);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof
+ (struct create_posix_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(POSIX_CTXT_DATA_LEN);
+ /* SMB2_CREATE_TAG_POSIX is "0x93AD25509CB411E7B42383DE968BCD7C" */
+ buf->Name[0] = 0x93;
+ buf->Name[1] = 0xAD;
+ buf->Name[2] = 0x25;
+ buf->Name[3] = 0x50;
+ buf->Name[4] = 0x9C;
+ buf->Name[5] = 0xB4;
+ buf->Name[6] = 0x11;
+ buf->Name[7] = 0xE7;
+ buf->Name[8] = 0xB4;
+ buf->Name[9] = 0x23;
+ buf->Name[10] = 0x83;
+ buf->Name[11] = 0xDE;
+ buf->Name[12] = 0x96;
+ buf->Name[13] = 0x8B;
+ buf->Name[14] = 0xCD;
+ buf->Name[15] = 0x7C;
+
+ buf->nlink = cpu_to_le32(inode->i_nlink);
+ buf->reparse_tag = cpu_to_le32(fp->volatile_id);
+ buf->mode = cpu_to_le32(inode->i_mode);
+ id_to_sid(from_kuid(user_ns, inode->i_uid),
+ SIDNFS_USER, (struct smb_sid *)&buf->SidBuffer[0]);
+ id_to_sid(from_kgid(user_ns, inode->i_gid),
+ SIDNFS_GROUP, (struct smb_sid *)&buf->SidBuffer[20]);
+}
+
+/*
+ * Find lease object(opinfo) for given lease key/fid from lease
+ * break/file close path.
+ */
+/**
+ * lookup_lease_in_table() - find a matching lease info object
+ * @conn: connection instance
+ * @lease_key: lease key to be searched for
+ *
+ * Return: opinfo if found matching opinfo, otherwise NULL
+ */
+struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
+ char *lease_key)
+{
+ struct oplock_info *opinfo = NULL, *ret_op = NULL;
+ struct lease_table *lt;
+ int ret;
+
+ read_lock(&lease_list_lock);
+ list_for_each_entry(lt, &lease_table_list, l_entry) {
+ if (!memcmp(lt->client_guid, conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE))
+ goto found;
+ }
+
+ read_unlock(&lease_list_lock);
+ return NULL;
+
+found:
+ rcu_read_lock();
+ list_for_each_entry_rcu(opinfo, &lt->lease_list, lease_entry) {
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
+ rcu_read_unlock();
+ if (!opinfo->op_state || opinfo->op_state == OPLOCK_CLOSING)
+ goto op_next;
+ if (!(opinfo->o_lease->state &
+ (SMB2_LEASE_HANDLE_CACHING_LE |
+ SMB2_LEASE_WRITE_CACHING_LE)))
+ goto op_next;
+ ret = compare_guid_key(opinfo, conn->ClientGUID,
+ lease_key);
+ if (ret) {
+ ksmbd_debug(OPLOCK, "found opinfo\n");
+ ret_op = opinfo;
+ goto out;
+ }
+op_next:
+ opinfo_put(opinfo);
+ rcu_read_lock();
+ }
+ rcu_read_unlock();
+
+out:
+ read_unlock(&lease_list_lock);
+ return ret_op;
+}
+
+int smb2_check_durable_oplock(struct ksmbd_file *fp,
+ struct lease_ctx_info *lctx, char *name)
+{
+ struct oplock_info *opinfo = opinfo_get(fp);
+ int ret = 0;
+
+ if (opinfo && opinfo->is_lease) {
+ if (!lctx) {
+ pr_err("open does not include lease\n");
+ ret = -EBADF;
+ goto out;
+ }
+ if (memcmp(opinfo->o_lease->lease_key, lctx->lease_key,
+ SMB2_LEASE_KEY_SIZE)) {
+ pr_err("invalid lease key\n");
+ ret = -EBADF;
+ goto out;
+ }
+ if (name && strcmp(fp->filename, name)) {
+ pr_err("invalid name reconnect %s\n", name);
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+out:
+ if (opinfo)
+ opinfo_put(opinfo);
+ return ret;
+}
diff --git a/fs/ksmbd/oplock.h b/fs/ksmbd/oplock.h
new file mode 100644
index 000000000000..119b8047cfbd
--- /dev/null
+++ b/fs/ksmbd/oplock.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __KSMBD_OPLOCK_H
+#define __KSMBD_OPLOCK_H
+
+#include "smb_common.h"
+
+#define OPLOCK_WAIT_TIME (35 * HZ)
+
+/* SMB2 Oplock levels */
+#define SMB2_OPLOCK_LEVEL_NONE 0x00
+#define SMB2_OPLOCK_LEVEL_II 0x01
+#define SMB2_OPLOCK_LEVEL_EXCLUSIVE 0x08
+#define SMB2_OPLOCK_LEVEL_BATCH 0x09
+#define SMB2_OPLOCK_LEVEL_LEASE 0xFF
+
+/* Oplock states */
+#define OPLOCK_STATE_NONE 0x00
+#define OPLOCK_ACK_WAIT 0x01
+#define OPLOCK_CLOSING 0x02
+
+#define OPLOCK_WRITE_TO_READ 0x01
+#define OPLOCK_READ_HANDLE_TO_READ 0x02
+#define OPLOCK_WRITE_TO_NONE 0x04
+#define OPLOCK_READ_TO_NONE 0x08
+
+#define SMB2_LEASE_KEY_SIZE 16
+
+struct lease_ctx_info {
+ __u8 lease_key[SMB2_LEASE_KEY_SIZE];
+ __le32 req_state;
+ __le32 flags;
+ __le64 duration;
+ __u8 parent_lease_key[SMB2_LEASE_KEY_SIZE];
+ int version;
+};
+
+struct lease_table {
+ char client_guid[SMB2_CLIENT_GUID_SIZE];
+ struct list_head lease_list;
+ struct list_head l_entry;
+ spinlock_t lb_lock;
+};
+
+struct lease {
+ __u8 lease_key[SMB2_LEASE_KEY_SIZE];
+ __le32 state;
+ __le32 new_state;
+ __le32 flags;
+ __le64 duration;
+ __u8 parent_lease_key[SMB2_LEASE_KEY_SIZE];
+ int version;
+ unsigned short epoch;
+ struct lease_table *l_lb;
+};
+
+struct oplock_info {
+ struct ksmbd_conn *conn;
+ struct ksmbd_session *sess;
+ struct ksmbd_work *work;
+ struct ksmbd_file *o_fp;
+ int level;
+ int op_state;
+ unsigned long pending_break;
+ u64 fid;
+ atomic_t breaking_cnt;
+ atomic_t refcount;
+ __u16 Tid;
+ bool is_lease;
+ bool open_trunc; /* truncate on open */
+ struct lease *o_lease;
+ struct list_head interim_list;
+ struct list_head op_entry;
+ struct list_head lease_entry;
+ wait_queue_head_t oplock_q; /* Other server threads */
+ wait_queue_head_t oplock_brk; /* oplock breaking wait */
+ struct rcu_head rcu_head;
+};
+
+struct lease_break_info {
+ __le32 curr_state;
+ __le32 new_state;
+ __le16 epoch;
+ char lease_key[SMB2_LEASE_KEY_SIZE];
+};
+
+struct oplock_break_info {
+ int level;
+ int open_trunc;
+ int fid;
+};
+
+int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
+ u64 pid, struct ksmbd_file *fp, __u16 tid,
+ struct lease_ctx_info *lctx, int share_ret);
+void smb_break_all_levII_oplock(struct ksmbd_work *work,
+ struct ksmbd_file *fp, int is_trunc);
+int opinfo_write_to_read(struct oplock_info *opinfo);
+int opinfo_read_handle_to_read(struct oplock_info *opinfo);
+int opinfo_write_to_none(struct oplock_info *opinfo);
+int opinfo_read_to_none(struct oplock_info *opinfo);
+void close_id_del_oplock(struct ksmbd_file *fp);
+void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp);
+struct oplock_info *opinfo_get(struct ksmbd_file *fp);
+void opinfo_put(struct oplock_info *opinfo);
+
+/* Lease related functions */
+void create_lease_buf(u8 *rbuf, struct lease *lease);
+struct lease_ctx_info *parse_lease_state(void *open_req);
+__u8 smb2_map_lease_to_oplock(__le32 lease_state);
+int lease_read_to_write(struct oplock_info *opinfo);
+
+/* Durable related functions */
+void create_durable_rsp_buf(char *cc);
+void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp);
+void create_mxac_rsp_buf(char *cc, int maximal_access);
+void create_disk_id_rsp_buf(char *cc, __u64 file_id, __u64 vol_id);
+void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp);
+struct create_context *smb2_find_context_vals(void *open_req, const char *str);
+struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
+ char *lease_key);
+int find_same_lease_key(struct ksmbd_session *sess, struct ksmbd_inode *ci,
+ struct lease_ctx_info *lctx);
+void destroy_lease_table(struct ksmbd_conn *conn);
+int smb2_check_durable_oplock(struct ksmbd_file *fp,
+ struct lease_ctx_info *lctx, char *name);
+#endif /* __KSMBD_OPLOCK_H */
diff --git a/fs/ksmbd/server.c b/fs/ksmbd/server.c
new file mode 100644
index 000000000000..e6a9f6aa47eb
--- /dev/null
+++ b/fs/ksmbd/server.c
@@ -0,0 +1,633 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include "glob.h"
+#include "oplock.h"
+#include "misc.h"
+#include <linux/sched/signal.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+#include "server.h"
+#include "smb_common.h"
+#include "smbstatus.h"
+#include "connection.h"
+#include "transport_ipc.h"
+#include "mgmt/user_session.h"
+#include "crypto_ctx.h"
+#include "auth.h"
+
+int ksmbd_debug_types;
+
+struct ksmbd_server_config server_conf;
+
+enum SERVER_CTRL_TYPE {
+ SERVER_CTRL_TYPE_INIT,
+ SERVER_CTRL_TYPE_RESET,
+};
+
+struct server_ctrl_struct {
+ int type;
+ struct work_struct ctrl_work;
+};
+
+static DEFINE_MUTEX(ctrl_lock);
+
+static int ___server_conf_set(int idx, char *val)
+{
+ if (idx >= ARRAY_SIZE(server_conf.conf))
+ return -EINVAL;
+
+ if (!val || val[0] == 0x00)
+ return -EINVAL;
+
+ kfree(server_conf.conf[idx]);
+ server_conf.conf[idx] = kstrdup(val, GFP_KERNEL);
+ if (!server_conf.conf[idx])
+ return -ENOMEM;
+ return 0;
+}
+
+int ksmbd_set_netbios_name(char *v)
+{
+ return ___server_conf_set(SERVER_CONF_NETBIOS_NAME, v);
+}
+
+int ksmbd_set_server_string(char *v)
+{
+ return ___server_conf_set(SERVER_CONF_SERVER_STRING, v);
+}
+
+int ksmbd_set_work_group(char *v)
+{
+ return ___server_conf_set(SERVER_CONF_WORK_GROUP, v);
+}
+
+char *ksmbd_netbios_name(void)
+{
+ return server_conf.conf[SERVER_CONF_NETBIOS_NAME];
+}
+
+char *ksmbd_server_string(void)
+{
+ return server_conf.conf[SERVER_CONF_SERVER_STRING];
+}
+
+char *ksmbd_work_group(void)
+{
+ return server_conf.conf[SERVER_CONF_WORK_GROUP];
+}
+
+/**
+ * check_conn_state() - check state of server thread connection
+ * @work: smb work containing server thread information
+ *
+ * Return: 0 on valid connection, otherwise 1 to reconnect
+ */
+static inline int check_conn_state(struct ksmbd_work *work)
+{
+ struct smb_hdr *rsp_hdr;
+
+ if (ksmbd_conn_exiting(work) || ksmbd_conn_need_reconnect(work)) {
+ rsp_hdr = work->response_buf;
+ rsp_hdr->Status.CifsError = STATUS_CONNECTION_DISCONNECTED;
+ return 1;
+ }
+ return 0;
+}
+
+#define SERVER_HANDLER_CONTINUE 0
+#define SERVER_HANDLER_ABORT 1
+
+static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn,
+ u16 *cmd)
+{
+ struct smb_version_cmds *cmds;
+ u16 command;
+ int ret;
+
+ if (check_conn_state(work))
+ return SERVER_HANDLER_CONTINUE;
+
+ if (ksmbd_verify_smb_message(work))
+ return SERVER_HANDLER_ABORT;
+
+ command = conn->ops->get_cmd_val(work);
+ *cmd = command;
+
+andx_again:
+ if (command >= conn->max_cmds) {
+ conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
+ return SERVER_HANDLER_CONTINUE;
+ }
+
+ cmds = &conn->cmds[command];
+ if (!cmds->proc) {
+ ksmbd_debug(SMB, "*** not implemented yet cmd = %x\n", command);
+ conn->ops->set_rsp_status(work, STATUS_NOT_IMPLEMENTED);
+ return SERVER_HANDLER_CONTINUE;
+ }
+
+ if (work->sess && conn->ops->is_sign_req(work, command)) {
+ ret = conn->ops->check_sign_req(work);
+ if (!ret) {
+ conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);
+ return SERVER_HANDLER_CONTINUE;
+ }
+ }
+
+ ret = cmds->proc(work);
+
+ if (ret < 0)
+ ksmbd_debug(CONN, "Failed to process %u [%d]\n", command, ret);
+ /* AndX commands - chained request can return positive values */
+ else if (ret > 0) {
+ command = ret;
+ *cmd = command;
+ goto andx_again;
+ }
+
+ if (work->send_no_response)
+ return SERVER_HANDLER_ABORT;
+ return SERVER_HANDLER_CONTINUE;
+}
+
+static void __handle_ksmbd_work(struct ksmbd_work *work,
+ struct ksmbd_conn *conn)
+{
+ u16 command = 0;
+ int rc;
+
+ if (conn->ops->allocate_rsp_buf(work))
+ return;
+
+ if (conn->ops->is_transform_hdr &&
+ conn->ops->is_transform_hdr(work->request_buf)) {
+ rc = conn->ops->decrypt_req(work);
+ if (rc < 0) {
+ conn->ops->set_rsp_status(work, STATUS_DATA_ERROR);
+ goto send;
+ }
+
+ work->encrypted = true;
+ }
+
+ rc = conn->ops->init_rsp_hdr(work);
+ if (rc) {
+ /* either uid or tid is not correct */
+ conn->ops->set_rsp_status(work, STATUS_INVALID_HANDLE);
+ goto send;
+ }
+
+ if (conn->ops->check_user_session) {
+ rc = conn->ops->check_user_session(work);
+ if (rc < 0) {
+ command = conn->ops->get_cmd_val(work);
+ conn->ops->set_rsp_status(work,
+ STATUS_USER_SESSION_DELETED);
+ goto send;
+ } else if (rc > 0) {
+ rc = conn->ops->get_ksmbd_tcon(work);
+ if (rc < 0) {
+ conn->ops->set_rsp_status(work,
+ STATUS_NETWORK_NAME_DELETED);
+ goto send;
+ }
+ }
+ }
+
+ do {
+ rc = __process_request(work, conn, &command);
+ if (rc == SERVER_HANDLER_ABORT)
+ break;
+
+ /*
+ * Call smb2_set_rsp_credits() function to set number of credits
+ * granted in hdr of smb2 response.
+ */
+ if (conn->ops->set_rsp_credits) {
+ spin_lock(&conn->credits_lock);
+ rc = conn->ops->set_rsp_credits(work);
+ spin_unlock(&conn->credits_lock);
+ if (rc < 0) {
+ conn->ops->set_rsp_status(work,
+ STATUS_INVALID_PARAMETER);
+ goto send;
+ }
+ }
+
+ if (work->sess &&
+ (work->sess->sign || smb3_11_final_sess_setup_resp(work) ||
+ conn->ops->is_sign_req(work, command)))
+ conn->ops->set_sign_rsp(work);
+ } while (is_chained_smb2_message(work));
+
+ if (work->send_no_response)
+ return;
+
+send:
+ smb3_preauth_hash_rsp(work);
+ if (work->sess && work->sess->enc && work->encrypted &&
+ conn->ops->encrypt_resp) {
+ rc = conn->ops->encrypt_resp(work);
+ if (rc < 0) {
+ conn->ops->set_rsp_status(work, STATUS_DATA_ERROR);
+ goto send;
+ }
+ }
+
+ ksmbd_conn_write(work);
+}
+
+/**
+ * handle_ksmbd_work() - process pending smb work requests
+ * @wk: smb work containing request command buffer
+ *
+ * called by kworker threads to processing remaining smb work requests
+ */
+static void handle_ksmbd_work(struct work_struct *wk)
+{
+ struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
+ struct ksmbd_conn *conn = work->conn;
+
+ atomic64_inc(&conn->stats.request_served);
+
+ __handle_ksmbd_work(work, conn);
+
+ ksmbd_conn_try_dequeue_request(work);
+ ksmbd_free_work_struct(work);
+ atomic_dec(&conn->r_count);
+}
+
+/**
+ * queue_ksmbd_work() - queue a smb request to worker thread queue
+ * for proccessing smb command and sending response
+ * @conn: connection instance
+ *
+ * read remaining data from socket create and submit work.
+ */
+static int queue_ksmbd_work(struct ksmbd_conn *conn)
+{
+ struct ksmbd_work *work;
+
+ work = ksmbd_alloc_work_struct();
+ if (!work) {
+ pr_err("allocation for work failed\n");
+ return -ENOMEM;
+ }
+
+ work->conn = conn;
+ work->request_buf = conn->request_buf;
+ conn->request_buf = NULL;
+
+ if (ksmbd_init_smb_server(work)) {
+ ksmbd_free_work_struct(work);
+ return -EINVAL;
+ }
+
+ ksmbd_conn_enqueue_request(work);
+ atomic_inc(&conn->r_count);
+ /* update activity on connection */
+ conn->last_active = jiffies;
+ INIT_WORK(&work->work, handle_ksmbd_work);
+ ksmbd_queue_work(work);
+ return 0;
+}
+
+static int ksmbd_server_process_request(struct ksmbd_conn *conn)
+{
+ return queue_ksmbd_work(conn);
+}
+
+static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn)
+{
+ ksmbd_sessions_deregister(conn);
+ destroy_lease_table(conn);
+ return 0;
+}
+
+static void ksmbd_server_tcp_callbacks_init(void)
+{
+ struct ksmbd_conn_ops ops;
+
+ ops.process_fn = ksmbd_server_process_request;
+ ops.terminate_fn = ksmbd_server_terminate_conn;
+
+ ksmbd_conn_init_server_callbacks(&ops);
+}
+
+static void server_conf_free(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(server_conf.conf); i++) {
+ kfree(server_conf.conf[i]);
+ server_conf.conf[i] = NULL;
+ }
+}
+
+static int server_conf_init(void)
+{
+ WRITE_ONCE(server_conf.state, SERVER_STATE_STARTING_UP);
+ server_conf.enforced_signing = 0;
+ server_conf.min_protocol = ksmbd_min_protocol();
+ server_conf.max_protocol = ksmbd_max_protocol();
+ server_conf.auth_mechs = KSMBD_AUTH_NTLMSSP;
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+ server_conf.auth_mechs |= KSMBD_AUTH_KRB5 |
+ KSMBD_AUTH_MSKRB5;
+#endif
+ return 0;
+}
+
+static void server_ctrl_handle_init(struct server_ctrl_struct *ctrl)
+{
+ int ret;
+
+ ret = ksmbd_conn_transport_init();
+ if (ret) {
+ server_queue_ctrl_reset_work();
+ return;
+ }
+
+ WRITE_ONCE(server_conf.state, SERVER_STATE_RUNNING);
+}
+
+static void server_ctrl_handle_reset(struct server_ctrl_struct *ctrl)
+{
+ ksmbd_ipc_soft_reset();
+ ksmbd_conn_transport_destroy();
+ server_conf_free();
+ server_conf_init();
+ WRITE_ONCE(server_conf.state, SERVER_STATE_STARTING_UP);
+}
+
+static void server_ctrl_handle_work(struct work_struct *work)
+{
+ struct server_ctrl_struct *ctrl;
+
+ ctrl = container_of(work, struct server_ctrl_struct, ctrl_work);
+
+ mutex_lock(&ctrl_lock);
+ switch (ctrl->type) {
+ case SERVER_CTRL_TYPE_INIT:
+ server_ctrl_handle_init(ctrl);
+ break;
+ case SERVER_CTRL_TYPE_RESET:
+ server_ctrl_handle_reset(ctrl);
+ break;
+ default:
+ pr_err("Unknown server work type: %d\n", ctrl->type);
+ }
+ mutex_unlock(&ctrl_lock);
+ kfree(ctrl);
+ module_put(THIS_MODULE);
+}
+
+static int __queue_ctrl_work(int type)
+{
+ struct server_ctrl_struct *ctrl;
+
+ ctrl = kmalloc(sizeof(struct server_ctrl_struct), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ __module_get(THIS_MODULE);
+ ctrl->type = type;
+ INIT_WORK(&ctrl->ctrl_work, server_ctrl_handle_work);
+ queue_work(system_long_wq, &ctrl->ctrl_work);
+ return 0;
+}
+
+int server_queue_ctrl_init_work(void)
+{
+ return __queue_ctrl_work(SERVER_CTRL_TYPE_INIT);
+}
+
+int server_queue_ctrl_reset_work(void)
+{
+ return __queue_ctrl_work(SERVER_CTRL_TYPE_RESET);
+}
+
+static ssize_t stats_show(struct class *class, struct class_attribute *attr,
+ char *buf)
+{
+ /*
+ * Inc this each time you change stats output format,
+ * so user space will know what to do.
+ */
+ static int stats_version = 2;
+ static const char * const state[] = {
+ "startup",
+ "running",
+ "reset",
+ "shutdown"
+ };
+
+ ssize_t sz = scnprintf(buf, PAGE_SIZE, "%d %s %d %lu\n", stats_version,
+ state[server_conf.state], server_conf.tcp_port,
+ server_conf.ipc_last_active / HZ);
+ return sz;
+}
+
+static ssize_t kill_server_store(struct class *class,
+ struct class_attribute *attr, const char *buf,
+ size_t len)
+{
+ if (!sysfs_streq(buf, "hard"))
+ return len;
+
+ pr_info("kill command received\n");
+ mutex_lock(&ctrl_lock);
+ WRITE_ONCE(server_conf.state, SERVER_STATE_RESETTING);
+ __module_get(THIS_MODULE);
+ server_ctrl_handle_reset(NULL);
+ module_put(THIS_MODULE);
+ mutex_unlock(&ctrl_lock);
+ return len;
+}
+
+static const char * const debug_type_strings[] = {"smb", "auth", "vfs",
+ "oplock", "ipc", "conn",
+ "rdma"};
+
+static ssize_t debug_show(struct class *class, struct class_attribute *attr,
+ char *buf)
+{
+ ssize_t sz = 0;
+ int i, pos = 0;
+
+ for (i = 0; i < ARRAY_SIZE(debug_type_strings); i++) {
+ if ((ksmbd_debug_types >> i) & 1) {
+ pos = scnprintf(buf + sz,
+ PAGE_SIZE - sz,
+ "[%s] ",
+ debug_type_strings[i]);
+ } else {
+ pos = scnprintf(buf + sz,
+ PAGE_SIZE - sz,
+ "%s ",
+ debug_type_strings[i]);
+ }
+ sz += pos;
+ }
+ sz += scnprintf(buf + sz, PAGE_SIZE - sz, "\n");
+ return sz;
+}
+
+static ssize_t debug_store(struct class *class, struct class_attribute *attr,
+ const char *buf, size_t len)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(debug_type_strings); i++) {
+ if (sysfs_streq(buf, "all")) {
+ if (ksmbd_debug_types == KSMBD_DEBUG_ALL)
+ ksmbd_debug_types = 0;
+ else
+ ksmbd_debug_types = KSMBD_DEBUG_ALL;
+ break;
+ }
+
+ if (sysfs_streq(buf, debug_type_strings[i])) {
+ if (ksmbd_debug_types & (1 << i))
+ ksmbd_debug_types &= ~(1 << i);
+ else
+ ksmbd_debug_types |= (1 << i);
+ break;
+ }
+ }
+
+ return len;
+}
+
+static CLASS_ATTR_RO(stats);
+static CLASS_ATTR_WO(kill_server);
+static CLASS_ATTR_RW(debug);
+
+static struct attribute *ksmbd_control_class_attrs[] = {
+ &class_attr_stats.attr,
+ &class_attr_kill_server.attr,
+ &class_attr_debug.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ksmbd_control_class);
+
+static struct class ksmbd_control_class = {
+ .name = "ksmbd-control",
+ .owner = THIS_MODULE,
+ .class_groups = ksmbd_control_class_groups,
+};
+
+static int ksmbd_server_shutdown(void)
+{
+ WRITE_ONCE(server_conf.state, SERVER_STATE_SHUTTING_DOWN);
+
+ class_unregister(&ksmbd_control_class);
+ ksmbd_workqueue_destroy();
+ ksmbd_ipc_release();
+ ksmbd_conn_transport_destroy();
+ ksmbd_crypto_destroy();
+ ksmbd_free_global_file_table();
+ destroy_lease_table(NULL);
+ ksmbd_work_pool_destroy();
+ ksmbd_exit_file_cache();
+ server_conf_free();
+ return 0;
+}
+
+static int __init ksmbd_server_init(void)
+{
+ int ret;
+
+ ret = class_register(&ksmbd_control_class);
+ if (ret) {
+ pr_err("Unable to register ksmbd-control class\n");
+ return ret;
+ }
+
+ ksmbd_server_tcp_callbacks_init();
+
+ ret = server_conf_init();
+ if (ret)
+ goto err_unregister;
+
+ ret = ksmbd_work_pool_init();
+ if (ret)
+ goto err_unregister;
+
+ ret = ksmbd_init_file_cache();
+ if (ret)
+ goto err_destroy_work_pools;
+
+ ret = ksmbd_ipc_init();
+ if (ret)
+ goto err_exit_file_cache;
+
+ ret = ksmbd_init_global_file_table();
+ if (ret)
+ goto err_ipc_release;
+
+ ret = ksmbd_inode_hash_init();
+ if (ret)
+ goto err_destroy_file_table;
+
+ ret = ksmbd_crypto_create();
+ if (ret)
+ goto err_release_inode_hash;
+
+ ret = ksmbd_workqueue_init();
+ if (ret)
+ goto err_crypto_destroy;
+ return 0;
+
+err_crypto_destroy:
+ ksmbd_crypto_destroy();
+err_release_inode_hash:
+ ksmbd_release_inode_hash();
+err_destroy_file_table:
+ ksmbd_free_global_file_table();
+err_ipc_release:
+ ksmbd_ipc_release();
+err_exit_file_cache:
+ ksmbd_exit_file_cache();
+err_destroy_work_pools:
+ ksmbd_work_pool_destroy();
+err_unregister:
+ class_unregister(&ksmbd_control_class);
+
+ return ret;
+}
+
+/**
+ * ksmbd_server_exit() - shutdown forker thread and free memory at module exit
+ */
+static void __exit ksmbd_server_exit(void)
+{
+ ksmbd_server_shutdown();
+ ksmbd_release_inode_hash();
+}
+
+MODULE_AUTHOR("Namjae Jeon <linkinjeon@kernel.org>");
+MODULE_VERSION(KSMBD_VERSION);
+MODULE_DESCRIPTION("Linux kernel CIFS/SMB SERVER");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: ecb");
+MODULE_SOFTDEP("pre: hmac");
+MODULE_SOFTDEP("pre: md4");
+MODULE_SOFTDEP("pre: md5");
+MODULE_SOFTDEP("pre: nls");
+MODULE_SOFTDEP("pre: aes");
+MODULE_SOFTDEP("pre: cmac");
+MODULE_SOFTDEP("pre: sha256");
+MODULE_SOFTDEP("pre: sha512");
+MODULE_SOFTDEP("pre: aead2");
+MODULE_SOFTDEP("pre: ccm");
+MODULE_SOFTDEP("pre: gcm");
+module_init(ksmbd_server_init)
+module_exit(ksmbd_server_exit)
diff --git a/fs/ksmbd/server.h b/fs/ksmbd/server.h
new file mode 100644
index 000000000000..ac9d932f8c8a
--- /dev/null
+++ b/fs/ksmbd/server.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef __SERVER_H__
+#define __SERVER_H__
+
+#include "smbacl.h"
+
+/*
+ * Server state type
+ */
+enum {
+ SERVER_STATE_STARTING_UP,
+ SERVER_STATE_RUNNING,
+ SERVER_STATE_RESETTING,
+ SERVER_STATE_SHUTTING_DOWN,
+};
+
+/*
+ * Server global config string index
+ */
+enum {
+ SERVER_CONF_NETBIOS_NAME,
+ SERVER_CONF_SERVER_STRING,
+ SERVER_CONF_WORK_GROUP,
+};
+
+struct ksmbd_server_config {
+ unsigned int flags;
+ unsigned int state;
+ short signing;
+ short enforced_signing;
+ short min_protocol;
+ short max_protocol;
+ unsigned short tcp_port;
+ unsigned short ipc_timeout;
+ unsigned long ipc_last_active;
+ unsigned long deadtime;
+ unsigned int share_fake_fscaps;
+ struct smb_sid domain_sid;
+ unsigned int auth_mechs;
+
+ char *conf[SERVER_CONF_WORK_GROUP + 1];
+};
+
+extern struct ksmbd_server_config server_conf;
+
+int ksmbd_set_netbios_name(char *v);
+int ksmbd_set_server_string(char *v);
+int ksmbd_set_work_group(char *v);
+
+char *ksmbd_netbios_name(void);
+char *ksmbd_server_string(void);
+char *ksmbd_work_group(void);
+
+static inline int ksmbd_server_running(void)
+{
+ return READ_ONCE(server_conf.state) == SERVER_STATE_RUNNING;
+}
+
+static inline int ksmbd_server_configurable(void)
+{
+ return READ_ONCE(server_conf.state) < SERVER_STATE_RESETTING;
+}
+
+int server_queue_ctrl_init_work(void);
+int server_queue_ctrl_reset_work(void);
+#endif /* __SERVER_H__ */
diff --git a/fs/ksmbd/smb2misc.c b/fs/ksmbd/smb2misc.c
new file mode 100644
index 000000000000..9aa46bb3e10d
--- /dev/null
+++ b/fs/ksmbd/smb2misc.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include "glob.h"
+#include "nterr.h"
+#include "smb2pdu.h"
+#include "smb_common.h"
+#include "smbstatus.h"
+#include "mgmt/user_session.h"
+#include "connection.h"
+
+static int check_smb2_hdr(struct smb2_hdr *hdr)
+{
+ /*
+ * Make sure that this really is an SMB, that it is a response.
+ */
+ if (hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR)
+ return 1;
+ return 0;
+}
+
+/*
+ * The following table defines the expected "StructureSize" of SMB2 requests
+ * in order by SMB2 command. This is similar to "wct" in SMB/CIFS requests.
+ *
+ * Note that commands are defined in smb2pdu.h in le16 but the array below is
+ * indexed by command in host byte order
+ */
+static const __le16 smb2_req_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = {
+ /* SMB2_NEGOTIATE */ cpu_to_le16(36),
+ /* SMB2_SESSION_SETUP */ cpu_to_le16(25),
+ /* SMB2_LOGOFF */ cpu_to_le16(4),
+ /* SMB2_TREE_CONNECT */ cpu_to_le16(9),
+ /* SMB2_TREE_DISCONNECT */ cpu_to_le16(4),
+ /* SMB2_CREATE */ cpu_to_le16(57),
+ /* SMB2_CLOSE */ cpu_to_le16(24),
+ /* SMB2_FLUSH */ cpu_to_le16(24),
+ /* SMB2_READ */ cpu_to_le16(49),
+ /* SMB2_WRITE */ cpu_to_le16(49),
+ /* SMB2_LOCK */ cpu_to_le16(48),
+ /* SMB2_IOCTL */ cpu_to_le16(57),
+ /* SMB2_CANCEL */ cpu_to_le16(4),
+ /* SMB2_ECHO */ cpu_to_le16(4),
+ /* SMB2_QUERY_DIRECTORY */ cpu_to_le16(33),
+ /* SMB2_CHANGE_NOTIFY */ cpu_to_le16(32),
+ /* SMB2_QUERY_INFO */ cpu_to_le16(41),
+ /* SMB2_SET_INFO */ cpu_to_le16(33),
+ /* use 44 for lease break */
+ /* SMB2_OPLOCK_BREAK */ cpu_to_le16(36)
+};
+
+/*
+ * The size of the variable area depends on the offset and length fields
+ * located in different fields for various SMB2 requests. SMB2 requests
+ * with no variable length info, show an offset of zero for the offset field.
+ */
+static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = {
+ /* SMB2_NEGOTIATE */ true,
+ /* SMB2_SESSION_SETUP */ true,
+ /* SMB2_LOGOFF */ false,
+ /* SMB2_TREE_CONNECT */ true,
+ /* SMB2_TREE_DISCONNECT */ false,
+ /* SMB2_CREATE */ true,
+ /* SMB2_CLOSE */ false,
+ /* SMB2_FLUSH */ false,
+ /* SMB2_READ */ true,
+ /* SMB2_WRITE */ true,
+ /* SMB2_LOCK */ true,
+ /* SMB2_IOCTL */ true,
+ /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */
+ /* SMB2_ECHO */ false,
+ /* SMB2_QUERY_DIRECTORY */ true,
+ /* SMB2_CHANGE_NOTIFY */ false,
+ /* SMB2_QUERY_INFO */ true,
+ /* SMB2_SET_INFO */ true,
+ /* SMB2_OPLOCK_BREAK */ false
+};
+
+/*
+ * Returns the pointer to the beginning of the data area. Length of the data
+ * area and the offset to it (from the beginning of the smb are also returned.
+ */
+static char *smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *hdr)
+{
+ *off = 0;
+ *len = 0;
+
+ /* error reqeusts do not have data area */
+ if (hdr->Status && hdr->Status != STATUS_MORE_PROCESSING_REQUIRED &&
+ (((struct smb2_err_rsp *)hdr)->StructureSize) == SMB2_ERROR_STRUCTURE_SIZE2_LE)
+ return NULL;
+
+ /*
+ * Following commands have data areas so we have to get the location
+ * of the data buffer offset and data buffer length for the particular
+ * command.
+ */
+ switch (hdr->Command) {
+ case SMB2_SESSION_SETUP:
+ *off = le16_to_cpu(((struct smb2_sess_setup_req *)hdr)->SecurityBufferOffset);
+ *len = le16_to_cpu(((struct smb2_sess_setup_req *)hdr)->SecurityBufferLength);
+ break;
+ case SMB2_TREE_CONNECT:
+ *off = le16_to_cpu(((struct smb2_tree_connect_req *)hdr)->PathOffset);
+ *len = le16_to_cpu(((struct smb2_tree_connect_req *)hdr)->PathLength);
+ break;
+ case SMB2_CREATE:
+ {
+ if (((struct smb2_create_req *)hdr)->CreateContextsLength) {
+ *off = le32_to_cpu(((struct smb2_create_req *)
+ hdr)->CreateContextsOffset);
+ *len = le32_to_cpu(((struct smb2_create_req *)
+ hdr)->CreateContextsLength);
+ break;
+ }
+
+ *off = le16_to_cpu(((struct smb2_create_req *)hdr)->NameOffset);
+ *len = le16_to_cpu(((struct smb2_create_req *)hdr)->NameLength);
+ break;
+ }
+ case SMB2_QUERY_INFO:
+ *off = le16_to_cpu(((struct smb2_query_info_req *)hdr)->InputBufferOffset);
+ *len = le32_to_cpu(((struct smb2_query_info_req *)hdr)->InputBufferLength);
+ break;
+ case SMB2_SET_INFO:
+ *off = le16_to_cpu(((struct smb2_set_info_req *)hdr)->BufferOffset);
+ *len = le32_to_cpu(((struct smb2_set_info_req *)hdr)->BufferLength);
+ break;
+ case SMB2_READ:
+ *off = le16_to_cpu(((struct smb2_read_req *)hdr)->ReadChannelInfoOffset);
+ *len = le16_to_cpu(((struct smb2_read_req *)hdr)->ReadChannelInfoLength);
+ break;
+ case SMB2_WRITE:
+ if (((struct smb2_write_req *)hdr)->DataOffset) {
+ *off = le16_to_cpu(((struct smb2_write_req *)hdr)->DataOffset);
+ *len = le32_to_cpu(((struct smb2_write_req *)hdr)->Length);
+ break;
+ }
+
+ *off = le16_to_cpu(((struct smb2_write_req *)hdr)->WriteChannelInfoOffset);
+ *len = le16_to_cpu(((struct smb2_write_req *)hdr)->WriteChannelInfoLength);
+ break;
+ case SMB2_QUERY_DIRECTORY:
+ *off = le16_to_cpu(((struct smb2_query_directory_req *)hdr)->FileNameOffset);
+ *len = le16_to_cpu(((struct smb2_query_directory_req *)hdr)->FileNameLength);
+ break;
+ case SMB2_LOCK:
+ {
+ int lock_count;
+
+ /*
+ * smb2_lock request size is 48 included single
+ * smb2_lock_element structure size.
+ */
+ lock_count = le16_to_cpu(((struct smb2_lock_req *)hdr)->LockCount) - 1;
+ if (lock_count > 0) {
+ *off = __SMB2_HEADER_STRUCTURE_SIZE + 48;
+ *len = sizeof(struct smb2_lock_element) * lock_count;
+ }
+ break;
+ }
+ case SMB2_IOCTL:
+ *off = le32_to_cpu(((struct smb2_ioctl_req *)hdr)->InputOffset);
+ *len = le32_to_cpu(((struct smb2_ioctl_req *)hdr)->InputCount);
+
+ break;
+ default:
+ ksmbd_debug(SMB, "no length check for command\n");
+ break;
+ }
+
+ /*
+ * Invalid length or offset probably means data area is invalid, but
+ * we have little choice but to ignore the data area in this case.
+ */
+ if (*off > 4096) {
+ ksmbd_debug(SMB, "offset %d too large, data area ignored\n",
+ *off);
+ *len = 0;
+ *off = 0;
+ } else if (*off < 0) {
+ ksmbd_debug(SMB,
+ "negative offset %d to data invalid ignore data area\n",
+ *off);
+ *off = 0;
+ *len = 0;
+ } else if (*len < 0) {
+ ksmbd_debug(SMB,
+ "negative data length %d invalid, data area ignored\n",
+ *len);
+ *len = 0;
+ } else if (*len > 128 * 1024) {
+ ksmbd_debug(SMB, "data area larger than 128K: %d\n", *len);
+ *len = 0;
+ }
+
+ /* return pointer to beginning of data area, ie offset from SMB start */
+ if ((*off != 0) && (*len != 0))
+ return (char *)hdr + *off;
+ else
+ return NULL;
+}
+
+/*
+ * Calculate the size of the SMB message based on the fixed header
+ * portion, the number of word parameters and the data portion of the message.
+ */
+static unsigned int smb2_calc_size(void *buf)
+{
+ struct smb2_pdu *pdu = (struct smb2_pdu *)buf;
+ struct smb2_hdr *hdr = &pdu->hdr;
+ int offset; /* the offset from the beginning of SMB to data area */
+ int data_length; /* the length of the variable length data area */
+ /* Structure Size has already been checked to make sure it is 64 */
+ int len = le16_to_cpu(hdr->StructureSize);
+
+ /*
+ * StructureSize2, ie length of fixed parameter area has already
+ * been checked to make sure it is the correct length.
+ */
+ len += le16_to_cpu(pdu->StructureSize2);
+
+ if (has_smb2_data_area[le16_to_cpu(hdr->Command)] == false)
+ goto calc_size_exit;
+
+ smb2_get_data_area_len(&offset, &data_length, hdr);
+ ksmbd_debug(SMB, "SMB2 data length %d offset %d\n", data_length,
+ offset);
+
+ if (data_length > 0) {
+ /*
+ * Check to make sure that data area begins after fixed area,
+ * Note that last byte of the fixed area is part of data area
+ * for some commands, typically those with odd StructureSize,
+ * so we must add one to the calculation.
+ */
+ if (offset + 1 < len)
+ ksmbd_debug(SMB,
+ "data area offset %d overlaps SMB2 header %d\n",
+ offset + 1, len);
+ else
+ len = offset + data_length;
+ }
+calc_size_exit:
+ ksmbd_debug(SMB, "SMB2 len %d\n", len);
+ return len;
+}
+
+static inline int smb2_query_info_req_len(struct smb2_query_info_req *h)
+{
+ return le32_to_cpu(h->InputBufferLength) +
+ le32_to_cpu(h->OutputBufferLength);
+}
+
+static inline int smb2_set_info_req_len(struct smb2_set_info_req *h)
+{
+ return le32_to_cpu(h->BufferLength);
+}
+
+static inline int smb2_read_req_len(struct smb2_read_req *h)
+{
+ return le32_to_cpu(h->Length);
+}
+
+static inline int smb2_write_req_len(struct smb2_write_req *h)
+{
+ return le32_to_cpu(h->Length);
+}
+
+static inline int smb2_query_dir_req_len(struct smb2_query_directory_req *h)
+{
+ return le32_to_cpu(h->OutputBufferLength);
+}
+
+static inline int smb2_ioctl_req_len(struct smb2_ioctl_req *h)
+{
+ return le32_to_cpu(h->InputCount) +
+ le32_to_cpu(h->OutputCount);
+}
+
+static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h)
+{
+ return le32_to_cpu(h->MaxInputResponse) +
+ le32_to_cpu(h->MaxOutputResponse);
+}
+
+static int smb2_validate_credit_charge(struct smb2_hdr *hdr)
+{
+ int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
+ int credit_charge = le16_to_cpu(hdr->CreditCharge);
+ void *__hdr = hdr;
+
+ switch (hdr->Command) {
+ case SMB2_QUERY_INFO:
+ req_len = smb2_query_info_req_len(__hdr);
+ break;
+ case SMB2_SET_INFO:
+ req_len = smb2_set_info_req_len(__hdr);
+ break;
+ case SMB2_READ:
+ req_len = smb2_read_req_len(__hdr);
+ break;
+ case SMB2_WRITE:
+ req_len = smb2_write_req_len(__hdr);
+ break;
+ case SMB2_QUERY_DIRECTORY:
+ req_len = smb2_query_dir_req_len(__hdr);
+ break;
+ case SMB2_IOCTL:
+ req_len = smb2_ioctl_req_len(__hdr);
+ expect_resp_len = smb2_ioctl_resp_len(__hdr);
+ break;
+ default:
+ return 0;
+ }
+
+ credit_charge = max(1, credit_charge);
+ max_len = max(req_len, expect_resp_len);
+ calc_credit_num = DIV_ROUND_UP(max_len, SMB2_MAX_BUFFER_SIZE);
+
+ if (credit_charge < calc_credit_num) {
+ pr_err("Insufficient credit charge, given: %d, needed: %d\n",
+ credit_charge, calc_credit_num);
+ return 1;
+ }
+
+ return 0;
+}
+
+int ksmbd_smb2_check_message(struct ksmbd_work *work)
+{
+ struct smb2_pdu *pdu = work->request_buf;
+ struct smb2_hdr *hdr = &pdu->hdr;
+ int command;
+ __u32 clc_len; /* calculated length */
+ __u32 len = get_rfc1002_len(pdu);
+
+ if (work->next_smb2_rcv_hdr_off) {
+ pdu = ksmbd_req_buf_next(work);
+ hdr = &pdu->hdr;
+ }
+
+ if (le32_to_cpu(hdr->NextCommand) > 0) {
+ len = le32_to_cpu(hdr->NextCommand);
+ } else if (work->next_smb2_rcv_hdr_off) {
+ len -= work->next_smb2_rcv_hdr_off;
+ len = round_up(len, 8);
+ }
+
+ if (check_smb2_hdr(hdr))
+ return 1;
+
+ if (hdr->StructureSize != SMB2_HEADER_STRUCTURE_SIZE) {
+ ksmbd_debug(SMB, "Illegal structure size %u\n",
+ le16_to_cpu(hdr->StructureSize));
+ return 1;
+ }
+
+ command = le16_to_cpu(hdr->Command);
+ if (command >= NUMBER_OF_SMB2_COMMANDS) {
+ ksmbd_debug(SMB, "Illegal SMB2 command %d\n", command);
+ return 1;
+ }
+
+ if (smb2_req_struct_sizes[command] != pdu->StructureSize2) {
+ if (command != SMB2_OPLOCK_BREAK_HE &&
+ (hdr->Status == 0 || pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE)) {
+ /* error packets have 9 byte structure size */
+ ksmbd_debug(SMB,
+ "Illegal request size %u for command %d\n",
+ le16_to_cpu(pdu->StructureSize2), command);
+ return 1;
+ } else if (command == SMB2_OPLOCK_BREAK_HE &&
+ hdr->Status == 0 &&
+ le16_to_cpu(pdu->StructureSize2) != OP_BREAK_STRUCT_SIZE_20 &&
+ le16_to_cpu(pdu->StructureSize2) != OP_BREAK_STRUCT_SIZE_21) {
+ /* special case for SMB2.1 lease break message */
+ ksmbd_debug(SMB,
+ "Illegal request size %d for oplock break\n",
+ le16_to_cpu(pdu->StructureSize2));
+ return 1;
+ }
+ }
+
+ if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
+ smb2_validate_credit_charge(hdr)) {
+ work->conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
+ return 1;
+ }
+
+ clc_len = smb2_calc_size(hdr);
+ if (len != clc_len) {
+ /* server can return one byte more due to implied bcc[0] */
+ if (clc_len == len + 1)
+ return 0;
+
+ /*
+ * Some windows servers (win2016) will pad also the final
+ * PDU in a compound to 8 bytes.
+ */
+ if (ALIGN(clc_len, 8) == len)
+ return 0;
+
+ /*
+ * windows client also pad up to 8 bytes when compounding.
+ * If pad is longer than eight bytes, log the server behavior
+ * (once), since may indicate a problem but allow it and
+ * continue since the frame is parseable.
+ */
+ if (clc_len < len) {
+ ksmbd_debug(SMB,
+ "cli req padded more than expected. Length %d not %d for cmd:%d mid:%llu\n",
+ len, clc_len, command,
+ le64_to_cpu(hdr->MessageId));
+ return 0;
+ }
+
+ if (command == SMB2_LOCK_HE && len == 88)
+ return 0;
+
+ ksmbd_debug(SMB,
+ "cli req too short, len %d not %d. cmd:%d mid:%llu\n",
+ len, clc_len, command,
+ le64_to_cpu(hdr->MessageId));
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int smb2_negotiate_request(struct ksmbd_work *work)
+{
+ return ksmbd_smb_negotiate_common(work, SMB2_NEGOTIATE_HE);
+}
diff --git a/fs/ksmbd/smb2ops.c b/fs/ksmbd/smb2ops.c
new file mode 100644
index 000000000000..197473871aa4
--- /dev/null
+++ b/fs/ksmbd/smb2ops.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/slab.h>
+#include "glob.h"
+#include "smb2pdu.h"
+
+#include "auth.h"
+#include "connection.h"
+#include "smb_common.h"
+#include "server.h"
+
+static struct smb_version_values smb21_server_values = {
+ .version_string = SMB21_VERSION_STRING,
+ .protocol_id = SMB21_PROT_ID,
+ .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB21_DEFAULT_IOSIZE,
+ .max_write_size = SMB21_DEFAULT_IOSIZE,
+ .max_trans_size = SMB21_DEFAULT_IOSIZE,
+ .large_lock_type = 0,
+ .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE,
+ .shared_lock_type = SMB2_LOCKFLAG_SHARED,
+ .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+ .header_size = sizeof(struct smb2_hdr),
+ .max_header_size = MAX_SMB2_HDR_SIZE,
+ .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+ .lock_cmd = SMB2_LOCK,
+ .cap_unix = 0,
+ .cap_nt_find = SMB2_NT_FIND,
+ .cap_large_files = SMB2_LARGE_FILES,
+ .create_lease_size = sizeof(struct create_lease),
+ .create_durable_size = sizeof(struct create_durable_rsp),
+ .create_mxac_size = sizeof(struct create_mxac_rsp),
+ .create_disk_id_size = sizeof(struct create_disk_id_rsp),
+ .create_posix_size = sizeof(struct create_posix_rsp),
+};
+
+static struct smb_version_values smb30_server_values = {
+ .version_string = SMB30_VERSION_STRING,
+ .protocol_id = SMB30_PROT_ID,
+ .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+ .large_lock_type = 0,
+ .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE,
+ .shared_lock_type = SMB2_LOCKFLAG_SHARED,
+ .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+ .header_size = sizeof(struct smb2_hdr),
+ .max_header_size = MAX_SMB2_HDR_SIZE,
+ .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+ .lock_cmd = SMB2_LOCK,
+ .cap_unix = 0,
+ .cap_nt_find = SMB2_NT_FIND,
+ .cap_large_files = SMB2_LARGE_FILES,
+ .create_lease_size = sizeof(struct create_lease_v2),
+ .create_durable_size = sizeof(struct create_durable_rsp),
+ .create_durable_v2_size = sizeof(struct create_durable_v2_rsp),
+ .create_mxac_size = sizeof(struct create_mxac_rsp),
+ .create_disk_id_size = sizeof(struct create_disk_id_rsp),
+ .create_posix_size = sizeof(struct create_posix_rsp),
+};
+
+static struct smb_version_values smb302_server_values = {
+ .version_string = SMB302_VERSION_STRING,
+ .protocol_id = SMB302_PROT_ID,
+ .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+ .large_lock_type = 0,
+ .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE,
+ .shared_lock_type = SMB2_LOCKFLAG_SHARED,
+ .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+ .header_size = sizeof(struct smb2_hdr),
+ .max_header_size = MAX_SMB2_HDR_SIZE,
+ .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+ .lock_cmd = SMB2_LOCK,
+ .cap_unix = 0,
+ .cap_nt_find = SMB2_NT_FIND,
+ .cap_large_files = SMB2_LARGE_FILES,
+ .create_lease_size = sizeof(struct create_lease_v2),
+ .create_durable_size = sizeof(struct create_durable_rsp),
+ .create_durable_v2_size = sizeof(struct create_durable_v2_rsp),
+ .create_mxac_size = sizeof(struct create_mxac_rsp),
+ .create_disk_id_size = sizeof(struct create_disk_id_rsp),
+ .create_posix_size = sizeof(struct create_posix_rsp),
+};
+
+static struct smb_version_values smb311_server_values = {
+ .version_string = SMB311_VERSION_STRING,
+ .protocol_id = SMB311_PROT_ID,
+ .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+ .large_lock_type = 0,
+ .exclusive_lock_type = SMB2_LOCKFLAG_EXCLUSIVE,
+ .shared_lock_type = SMB2_LOCKFLAG_SHARED,
+ .unlock_lock_type = SMB2_LOCKFLAG_UNLOCK,
+ .header_size = sizeof(struct smb2_hdr),
+ .max_header_size = MAX_SMB2_HDR_SIZE,
+ .read_rsp_size = sizeof(struct smb2_read_rsp) - 1,
+ .lock_cmd = SMB2_LOCK,
+ .cap_unix = 0,
+ .cap_nt_find = SMB2_NT_FIND,
+ .cap_large_files = SMB2_LARGE_FILES,
+ .create_lease_size = sizeof(struct create_lease_v2),
+ .create_durable_size = sizeof(struct create_durable_rsp),
+ .create_durable_v2_size = sizeof(struct create_durable_v2_rsp),
+ .create_mxac_size = sizeof(struct create_mxac_rsp),
+ .create_disk_id_size = sizeof(struct create_disk_id_rsp),
+ .create_posix_size = sizeof(struct create_posix_rsp),
+};
+
+static struct smb_version_ops smb2_0_server_ops = {
+ .get_cmd_val = get_smb2_cmd_val,
+ .init_rsp_hdr = init_smb2_rsp_hdr,
+ .set_rsp_status = set_smb2_rsp_status,
+ .allocate_rsp_buf = smb2_allocate_rsp_buf,
+ .set_rsp_credits = smb2_set_rsp_credits,
+ .check_user_session = smb2_check_user_session,
+ .get_ksmbd_tcon = smb2_get_ksmbd_tcon,
+ .is_sign_req = smb2_is_sign_req,
+ .check_sign_req = smb2_check_sign_req,
+ .set_sign_rsp = smb2_set_sign_rsp
+};
+
+static struct smb_version_ops smb3_0_server_ops = {
+ .get_cmd_val = get_smb2_cmd_val,
+ .init_rsp_hdr = init_smb2_rsp_hdr,
+ .set_rsp_status = set_smb2_rsp_status,
+ .allocate_rsp_buf = smb2_allocate_rsp_buf,
+ .set_rsp_credits = smb2_set_rsp_credits,
+ .check_user_session = smb2_check_user_session,
+ .get_ksmbd_tcon = smb2_get_ksmbd_tcon,
+ .is_sign_req = smb2_is_sign_req,
+ .check_sign_req = smb3_check_sign_req,
+ .set_sign_rsp = smb3_set_sign_rsp,
+ .generate_signingkey = ksmbd_gen_smb30_signingkey,
+ .generate_encryptionkey = ksmbd_gen_smb30_encryptionkey,
+ .is_transform_hdr = smb3_is_transform_hdr,
+ .decrypt_req = smb3_decrypt_req,
+ .encrypt_resp = smb3_encrypt_resp
+};
+
+static struct smb_version_ops smb3_11_server_ops = {
+ .get_cmd_val = get_smb2_cmd_val,
+ .init_rsp_hdr = init_smb2_rsp_hdr,
+ .set_rsp_status = set_smb2_rsp_status,
+ .allocate_rsp_buf = smb2_allocate_rsp_buf,
+ .set_rsp_credits = smb2_set_rsp_credits,
+ .check_user_session = smb2_check_user_session,
+ .get_ksmbd_tcon = smb2_get_ksmbd_tcon,
+ .is_sign_req = smb2_is_sign_req,
+ .check_sign_req = smb3_check_sign_req,
+ .set_sign_rsp = smb3_set_sign_rsp,
+ .generate_signingkey = ksmbd_gen_smb311_signingkey,
+ .generate_encryptionkey = ksmbd_gen_smb311_encryptionkey,
+ .is_transform_hdr = smb3_is_transform_hdr,
+ .decrypt_req = smb3_decrypt_req,
+ .encrypt_resp = smb3_encrypt_resp
+};
+
+static struct smb_version_cmds smb2_0_server_cmds[NUMBER_OF_SMB2_COMMANDS] = {
+ [SMB2_NEGOTIATE_HE] = { .proc = smb2_negotiate_request, },
+ [SMB2_SESSION_SETUP_HE] = { .proc = smb2_sess_setup, },
+ [SMB2_TREE_CONNECT_HE] = { .proc = smb2_tree_connect,},
+ [SMB2_TREE_DISCONNECT_HE] = { .proc = smb2_tree_disconnect,},
+ [SMB2_LOGOFF_HE] = { .proc = smb2_session_logoff,},
+ [SMB2_CREATE_HE] = { .proc = smb2_open},
+ [SMB2_QUERY_INFO_HE] = { .proc = smb2_query_info},
+ [SMB2_QUERY_DIRECTORY_HE] = { .proc = smb2_query_dir},
+ [SMB2_CLOSE_HE] = { .proc = smb2_close},
+ [SMB2_ECHO_HE] = { .proc = smb2_echo},
+ [SMB2_SET_INFO_HE] = { .proc = smb2_set_info},
+ [SMB2_READ_HE] = { .proc = smb2_read},
+ [SMB2_WRITE_HE] = { .proc = smb2_write},
+ [SMB2_FLUSH_HE] = { .proc = smb2_flush},
+ [SMB2_CANCEL_HE] = { .proc = smb2_cancel},
+ [SMB2_LOCK_HE] = { .proc = smb2_lock},
+ [SMB2_IOCTL_HE] = { .proc = smb2_ioctl},
+ [SMB2_OPLOCK_BREAK_HE] = { .proc = smb2_oplock_break},
+ [SMB2_CHANGE_NOTIFY_HE] = { .proc = smb2_notify},
+};
+
+int init_smb2_0_server(struct ksmbd_conn *conn)
+{
+ return -EOPNOTSUPP;
+}
+
+/**
+ * init_smb2_1_server() - initialize a smb server connection with smb2.1
+ * command dispatcher
+ * @conn: connection instance
+ */
+void init_smb2_1_server(struct ksmbd_conn *conn)
+{
+ conn->vals = &smb21_server_values;
+ conn->ops = &smb2_0_server_ops;
+ conn->cmds = smb2_0_server_cmds;
+ conn->max_cmds = ARRAY_SIZE(smb2_0_server_cmds);
+ conn->max_credits = SMB2_MAX_CREDITS;
+ conn->signing_algorithm = SIGNING_ALG_HMAC_SHA256;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
+}
+
+/**
+ * init_smb3_0_server() - initialize a smb server connection with smb3.0
+ * command dispatcher
+ * @conn: connection instance
+ */
+void init_smb3_0_server(struct ksmbd_conn *conn)
+{
+ conn->vals = &smb30_server_values;
+ conn->ops = &smb3_0_server_ops;
+ conn->cmds = smb2_0_server_cmds;
+ conn->max_cmds = ARRAY_SIZE(smb2_0_server_cmds);
+ conn->max_credits = SMB2_MAX_CREDITS;
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION &&
+ conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+}
+
+/**
+ * init_smb3_02_server() - initialize a smb server connection with smb3.02
+ * command dispatcher
+ * @conn: connection instance
+ */
+void init_smb3_02_server(struct ksmbd_conn *conn)
+{
+ conn->vals = &smb302_server_values;
+ conn->ops = &smb3_0_server_ops;
+ conn->cmds = smb2_0_server_cmds;
+ conn->max_cmds = ARRAY_SIZE(smb2_0_server_cmds);
+ conn->max_credits = SMB2_MAX_CREDITS;
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION &&
+ conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+}
+
+/**
+ * init_smb3_11_server() - initialize a smb server connection with smb3.11
+ * command dispatcher
+ * @conn: connection instance
+ */
+int init_smb3_11_server(struct ksmbd_conn *conn)
+{
+ conn->vals = &smb311_server_values;
+ conn->ops = &smb3_11_server_ops;
+ conn->cmds = smb2_0_server_cmds;
+ conn->max_cmds = ARRAY_SIZE(smb2_0_server_cmds);
+ conn->max_credits = SMB2_MAX_CREDITS;
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
+
+ if (conn->cipher_type)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+ conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+
+ INIT_LIST_HEAD(&conn->preauth_sess_table);
+ return 0;
+}
+
+void init_smb2_max_read_size(unsigned int sz)
+{
+ smb21_server_values.max_read_size = sz;
+ smb30_server_values.max_read_size = sz;
+ smb302_server_values.max_read_size = sz;
+ smb311_server_values.max_read_size = sz;
+}
+
+void init_smb2_max_write_size(unsigned int sz)
+{
+ smb21_server_values.max_write_size = sz;
+ smb30_server_values.max_write_size = sz;
+ smb302_server_values.max_write_size = sz;
+ smb311_server_values.max_write_size = sz;
+}
+
+void init_smb2_max_trans_size(unsigned int sz)
+{
+ smb21_server_values.max_trans_size = sz;
+ smb30_server_values.max_trans_size = sz;
+ smb302_server_values.max_trans_size = sz;
+ smb311_server_values.max_trans_size = sz;
+}
diff --git a/fs/ksmbd/smb2pdu.c b/fs/ksmbd/smb2pdu.c
new file mode 100644
index 000000000000..d329ea49fa14
--- /dev/null
+++ b/fs/ksmbd/smb2pdu.c
@@ -0,0 +1,8373 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
+#include <linux/statfs.h>
+#include <linux/ethtool.h>
+#include <linux/falloc.h>
+
+#include "glob.h"
+#include "smb2pdu.h"
+#include "smbfsctl.h"
+#include "oplock.h"
+#include "smbacl.h"
+
+#include "auth.h"
+#include "asn1.h"
+#include "connection.h"
+#include "transport_ipc.h"
+#include "transport_rdma.h"
+#include "vfs.h"
+#include "vfs_cache.h"
+#include "misc.h"
+
+#include "server.h"
+#include "smb_common.h"
+#include "smbstatus.h"
+#include "ksmbd_work.h"
+#include "mgmt/user_config.h"
+#include "mgmt/share_config.h"
+#include "mgmt/tree_connect.h"
+#include "mgmt/user_session.h"
+#include "mgmt/ksmbd_ida.h"
+#include "ndr.h"
+
+static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
+{
+ if (work->next_smb2_rcv_hdr_off) {
+ *req = ksmbd_req_buf_next(work);
+ *rsp = ksmbd_resp_buf_next(work);
+ } else {
+ *req = work->request_buf;
+ *rsp = work->response_buf;
+ }
+}
+
+#define WORK_BUFFERS(w, rq, rs) __wbuf((w), (void **)&(rq), (void **)&(rs))
+
+/**
+ * check_session_id() - check for valid session id in smb header
+ * @conn: connection instance
+ * @id: session id from smb header
+ *
+ * Return: 1 if valid session id, otherwise 0
+ */
+static inline bool check_session_id(struct ksmbd_conn *conn, u64 id)
+{
+ struct ksmbd_session *sess;
+
+ if (id == 0 || id == -1)
+ return false;
+
+ sess = ksmbd_session_lookup_all(conn, id);
+ if (sess)
+ return true;
+ pr_err("Invalid user session id: %llu\n", id);
+ return false;
+}
+
+struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn)
+{
+ struct channel *chann;
+
+ list_for_each_entry(chann, &sess->ksmbd_chann_list, chann_list) {
+ if (chann->conn == conn)
+ return chann;
+ }
+
+ return NULL;
+}
+
+/**
+ * smb2_get_ksmbd_tcon() - get tree connection information using a tree id.
+ * @work: smb work
+ *
+ * Return: 0 if there is a tree connection matched or these are
+ * skipable commands, otherwise error
+ */
+int smb2_get_ksmbd_tcon(struct ksmbd_work *work)
+{
+ struct smb2_hdr *req_hdr = work->request_buf;
+ int tree_id;
+
+ work->tcon = NULL;
+ if (work->conn->ops->get_cmd_val(work) == SMB2_TREE_CONNECT_HE ||
+ work->conn->ops->get_cmd_val(work) == SMB2_CANCEL_HE ||
+ work->conn->ops->get_cmd_val(work) == SMB2_LOGOFF_HE) {
+ ksmbd_debug(SMB, "skip to check tree connect request\n");
+ return 0;
+ }
+
+ if (xa_empty(&work->sess->tree_conns)) {
+ ksmbd_debug(SMB, "NO tree connected\n");
+ return -ENOENT;
+ }
+
+ tree_id = le32_to_cpu(req_hdr->Id.SyncId.TreeId);
+ work->tcon = ksmbd_tree_conn_lookup(work->sess, tree_id);
+ if (!work->tcon) {
+ pr_err("Invalid tid %d\n", tree_id);
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+/**
+ * smb2_set_err_rsp() - set error response code on smb response
+ * @work: smb work containing response buffer
+ */
+void smb2_set_err_rsp(struct ksmbd_work *work)
+{
+ struct smb2_err_rsp *err_rsp;
+
+ if (work->next_smb2_rcv_hdr_off)
+ err_rsp = ksmbd_resp_buf_next(work);
+ else
+ err_rsp = work->response_buf;
+
+ if (err_rsp->hdr.Status != STATUS_STOPPED_ON_SYMLINK) {
+ err_rsp->StructureSize = SMB2_ERROR_STRUCTURE_SIZE2_LE;
+ err_rsp->ErrorContextCount = 0;
+ err_rsp->Reserved = 0;
+ err_rsp->ByteCount = 0;
+ err_rsp->ErrorData[0] = 0;
+ inc_rfc1001_len(work->response_buf, SMB2_ERROR_STRUCTURE_SIZE2);
+ }
+}
+
+/**
+ * is_smb2_neg_cmd() - is it smb2 negotiation command
+ * @work: smb work containing smb header
+ *
+ * Return: true if smb2 negotiation command, otherwise false
+ */
+bool is_smb2_neg_cmd(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr = work->request_buf;
+
+ /* is it SMB2 header ? */
+ if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
+ return false;
+
+ /* make sure it is request not response message */
+ if (hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR)
+ return false;
+
+ if (hdr->Command != SMB2_NEGOTIATE)
+ return false;
+
+ return true;
+}
+
+/**
+ * is_smb2_rsp() - is it smb2 response
+ * @work: smb work containing smb response buffer
+ *
+ * Return: true if smb2 response, otherwise false
+ */
+bool is_smb2_rsp(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr = work->response_buf;
+
+ /* is it SMB2 header ? */
+ if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
+ return false;
+
+ /* make sure it is response not request message */
+ if (!(hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR))
+ return false;
+
+ return true;
+}
+
+/**
+ * get_smb2_cmd_val() - get smb command code from smb header
+ * @work: smb work containing smb request buffer
+ *
+ * Return: smb2 request command value
+ */
+u16 get_smb2_cmd_val(struct ksmbd_work *work)
+{
+ struct smb2_hdr *rcv_hdr;
+
+ if (work->next_smb2_rcv_hdr_off)
+ rcv_hdr = ksmbd_req_buf_next(work);
+ else
+ rcv_hdr = work->request_buf;
+ return le16_to_cpu(rcv_hdr->Command);
+}
+
+/**
+ * set_smb2_rsp_status() - set error response code on smb2 header
+ * @work: smb work containing response buffer
+ * @err: error response code
+ */
+void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err)
+{
+ struct smb2_hdr *rsp_hdr;
+
+ if (work->next_smb2_rcv_hdr_off)
+ rsp_hdr = ksmbd_resp_buf_next(work);
+ else
+ rsp_hdr = work->response_buf;
+ rsp_hdr->Status = err;
+ smb2_set_err_rsp(work);
+}
+
+/**
+ * init_smb2_neg_rsp() - initialize smb2 response for negotiate command
+ * @work: smb work containing smb request buffer
+ *
+ * smb2 negotiate response is sent in reply of smb1 negotiate command for
+ * dialect auto-negotiation.
+ */
+int init_smb2_neg_rsp(struct ksmbd_work *work)
+{
+ struct smb2_hdr *rsp_hdr;
+ struct smb2_negotiate_rsp *rsp;
+ struct ksmbd_conn *conn = work->conn;
+
+ if (conn->need_neg == false)
+ return -EINVAL;
+ if (!(conn->dialect >= SMB20_PROT_ID &&
+ conn->dialect <= SMB311_PROT_ID))
+ return -EINVAL;
+
+ rsp_hdr = work->response_buf;
+
+ memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
+
+ rsp_hdr->smb2_buf_length =
+ cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
+
+ rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
+ rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
+ rsp_hdr->CreditRequest = cpu_to_le16(2);
+ rsp_hdr->Command = SMB2_NEGOTIATE;
+ rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ rsp_hdr->NextCommand = 0;
+ rsp_hdr->MessageId = 0;
+ rsp_hdr->Id.SyncId.ProcessId = 0;
+ rsp_hdr->Id.SyncId.TreeId = 0;
+ rsp_hdr->SessionId = 0;
+ memset(rsp_hdr->Signature, 0, 16);
+
+ rsp = work->response_buf;
+
+ WARN_ON(ksmbd_conn_good(work));
+
+ rsp->StructureSize = cpu_to_le16(65);
+ ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
+ rsp->DialectRevision = cpu_to_le16(conn->dialect);
+ /* Not setting conn guid rsp->ServerGUID, as it
+ * not used by client for identifying connection
+ */
+ rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+ /* Default Max Message Size till SMB2.0, 64K*/
+ rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
+ rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
+ rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
+
+ rsp->SystemTime = cpu_to_le64(ksmbd_systime());
+ rsp->ServerStartTime = 0;
+
+ rsp->SecurityBufferOffset = cpu_to_le16(128);
+ rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
+ ksmbd_copy_gss_neg_header(((char *)(&rsp->hdr) +
+ sizeof(rsp->hdr.smb2_buf_length)) +
+ le16_to_cpu(rsp->SecurityBufferOffset));
+ inc_rfc1001_len(rsp, sizeof(struct smb2_negotiate_rsp) -
+ sizeof(struct smb2_hdr) - sizeof(rsp->Buffer) +
+ AUTH_GSS_LENGTH);
+ rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
+ if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY)
+ rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
+ conn->use_spnego = true;
+
+ ksmbd_conn_set_need_negotiate(work);
+ return 0;
+}
+
+static int smb2_consume_credit_charge(struct ksmbd_work *work,
+ unsigned short credit_charge)
+{
+ struct ksmbd_conn *conn = work->conn;
+ unsigned int rsp_credits = 1;
+
+ if (!conn->total_credits)
+ return 0;
+
+ if (credit_charge > 0)
+ rsp_credits = credit_charge;
+
+ conn->total_credits -= rsp_credits;
+ return rsp_credits;
+}
+
+/**
+ * smb2_set_rsp_credits() - set number of credits in response buffer
+ * @work: smb work containing smb response buffer
+ */
+int smb2_set_rsp_credits(struct ksmbd_work *work)
+{
+ struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
+ struct smb2_hdr *hdr = ksmbd_resp_buf_next(work);
+ struct ksmbd_conn *conn = work->conn;
+ unsigned short credits_requested = le16_to_cpu(req_hdr->CreditRequest);
+ unsigned short credit_charge = 1, credits_granted = 0;
+ unsigned short aux_max, aux_credits, min_credits;
+ int rsp_credit_charge;
+
+ if (hdr->Command == SMB2_CANCEL)
+ goto out;
+
+ /* get default minimum credits by shifting maximum credits by 4 */
+ min_credits = conn->max_credits >> 4;
+
+ if (conn->total_credits >= conn->max_credits) {
+ pr_err("Total credits overflow: %d\n", conn->total_credits);
+ conn->total_credits = min_credits;
+ }
+
+ rsp_credit_charge =
+ smb2_consume_credit_charge(work, le16_to_cpu(req_hdr->CreditCharge));
+ if (rsp_credit_charge < 0)
+ return -EINVAL;
+
+ hdr->CreditCharge = cpu_to_le16(rsp_credit_charge);
+
+ if (credits_requested > 0) {
+ aux_credits = credits_requested - 1;
+ aux_max = 32;
+ if (hdr->Command == SMB2_NEGOTIATE)
+ aux_max = 0;
+ aux_credits = (aux_credits < aux_max) ? aux_credits : aux_max;
+ credits_granted = aux_credits + credit_charge;
+
+ /* if credits granted per client is getting bigger than default
+ * minimum credits then we should wrap it up within the limits.
+ */
+ if ((conn->total_credits + credits_granted) > min_credits)
+ credits_granted = min_credits - conn->total_credits;
+ /*
+ * TODO: Need to adjuct CreditRequest value according to
+ * current cpu load
+ */
+ } else if (conn->total_credits == 0) {
+ credits_granted = 1;
+ }
+
+ conn->total_credits += credits_granted;
+ work->credits_granted += credits_granted;
+
+ if (!req_hdr->NextCommand) {
+ /* Update CreditRequest in last request */
+ hdr->CreditRequest = cpu_to_le16(work->credits_granted);
+ }
+out:
+ ksmbd_debug(SMB,
+ "credits: requested[%d] granted[%d] total_granted[%d]\n",
+ credits_requested, credits_granted,
+ conn->total_credits);
+ return 0;
+}
+
+/**
+ * init_chained_smb2_rsp() - initialize smb2 chained response
+ * @work: smb work containing smb response buffer
+ */
+static void init_chained_smb2_rsp(struct ksmbd_work *work)
+{
+ struct smb2_hdr *req = ksmbd_req_buf_next(work);
+ struct smb2_hdr *rsp = ksmbd_resp_buf_next(work);
+ struct smb2_hdr *rsp_hdr;
+ struct smb2_hdr *rcv_hdr;
+ int next_hdr_offset = 0;
+ int len, new_len;
+
+ /* Len of this response = updated RFC len - offset of previous cmd
+ * in the compound rsp
+ */
+
+ /* Storing the current local FID which may be needed by subsequent
+ * command in the compound request
+ */
+ if (req->Command == SMB2_CREATE && rsp->Status == STATUS_SUCCESS) {
+ work->compound_fid =
+ le64_to_cpu(((struct smb2_create_rsp *)rsp)->
+ VolatileFileId);
+ work->compound_pfid =
+ le64_to_cpu(((struct smb2_create_rsp *)rsp)->
+ PersistentFileId);
+ work->compound_sid = le64_to_cpu(rsp->SessionId);
+ }
+
+ len = get_rfc1002_len(work->response_buf) - work->next_smb2_rsp_hdr_off;
+ next_hdr_offset = le32_to_cpu(req->NextCommand);
+
+ new_len = ALIGN(len, 8);
+ inc_rfc1001_len(work->response_buf, ((sizeof(struct smb2_hdr) - 4)
+ + new_len - len));
+ rsp->NextCommand = cpu_to_le32(new_len);
+
+ work->next_smb2_rcv_hdr_off += next_hdr_offset;
+ work->next_smb2_rsp_hdr_off += new_len;
+ ksmbd_debug(SMB,
+ "Compound req new_len = %d rcv off = %d rsp off = %d\n",
+ new_len, work->next_smb2_rcv_hdr_off,
+ work->next_smb2_rsp_hdr_off);
+
+ rsp_hdr = ksmbd_resp_buf_next(work);
+ rcv_hdr = ksmbd_req_buf_next(work);
+
+ if (!(rcv_hdr->Flags & SMB2_FLAGS_RELATED_OPERATIONS)) {
+ ksmbd_debug(SMB, "related flag should be set\n");
+ work->compound_fid = KSMBD_NO_FID;
+ work->compound_pfid = KSMBD_NO_FID;
+ }
+ memset((char *)rsp_hdr + 4, 0, sizeof(struct smb2_hdr) + 2);
+ rsp_hdr->ProtocolId = rcv_hdr->ProtocolId;
+ rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
+ rsp_hdr->Command = rcv_hdr->Command;
+
+ /*
+ * Message is response. We don't grant oplock yet.
+ */
+ rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
+ SMB2_FLAGS_RELATED_OPERATIONS);
+ rsp_hdr->NextCommand = 0;
+ rsp_hdr->MessageId = rcv_hdr->MessageId;
+ rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
+ rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
+ rsp_hdr->SessionId = rcv_hdr->SessionId;
+ memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
+}
+
+/**
+ * is_chained_smb2_message() - check for chained command
+ * @work: smb work containing smb request buffer
+ *
+ * Return: true if chained request, otherwise false
+ */
+bool is_chained_smb2_message(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr = work->request_buf;
+ unsigned int len;
+
+ if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
+ return false;
+
+ hdr = ksmbd_req_buf_next(work);
+ if (le32_to_cpu(hdr->NextCommand) > 0) {
+ ksmbd_debug(SMB, "got SMB2 chained command\n");
+ init_chained_smb2_rsp(work);
+ return true;
+ } else if (work->next_smb2_rcv_hdr_off) {
+ /*
+ * This is last request in chained command,
+ * align response to 8 byte
+ */
+ len = ALIGN(get_rfc1002_len(work->response_buf), 8);
+ len = len - get_rfc1002_len(work->response_buf);
+ if (len) {
+ ksmbd_debug(SMB, "padding len %u\n", len);
+ inc_rfc1001_len(work->response_buf, len);
+ if (work->aux_payload_sz)
+ work->aux_payload_sz += len;
+ }
+ }
+ return false;
+}
+
+/**
+ * init_smb2_rsp_hdr() - initialize smb2 response
+ * @work: smb work containing smb request buffer
+ *
+ * Return: 0
+ */
+int init_smb2_rsp_hdr(struct ksmbd_work *work)
+{
+ struct smb2_hdr *rsp_hdr = work->response_buf;
+ struct smb2_hdr *rcv_hdr = work->request_buf;
+ struct ksmbd_conn *conn = work->conn;
+
+ memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
+ rsp_hdr->smb2_buf_length =
+ cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
+ rsp_hdr->ProtocolId = rcv_hdr->ProtocolId;
+ rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
+ rsp_hdr->Command = rcv_hdr->Command;
+
+ /*
+ * Message is response. We don't grant oplock yet.
+ */
+ rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+ rsp_hdr->NextCommand = 0;
+ rsp_hdr->MessageId = rcv_hdr->MessageId;
+ rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
+ rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
+ rsp_hdr->SessionId = rcv_hdr->SessionId;
+ memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
+
+ work->syncronous = true;
+ if (work->async_id) {
+ ksmbd_release_id(&conn->async_ida, work->async_id);
+ work->async_id = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * smb2_allocate_rsp_buf() - allocate smb2 response buffer
+ * @work: smb work containing smb request buffer
+ *
+ * Return: 0 on success, otherwise -ENOMEM
+ */
+int smb2_allocate_rsp_buf(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr = work->request_buf;
+ size_t small_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
+ size_t large_sz = work->conn->vals->max_trans_size + MAX_SMB2_HDR_SIZE;
+ size_t sz = small_sz;
+ int cmd = le16_to_cpu(hdr->Command);
+
+ if (cmd == SMB2_IOCTL_HE || cmd == SMB2_QUERY_DIRECTORY_HE)
+ sz = large_sz;
+
+ if (cmd == SMB2_QUERY_INFO_HE) {
+ struct smb2_query_info_req *req;
+
+ req = work->request_buf;
+ if (req->InfoType == SMB2_O_INFO_FILE &&
+ (req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
+ req->FileInfoClass == FILE_ALL_INFORMATION))
+ sz = large_sz;
+ }
+
+ /* allocate large response buf for chained commands */
+ if (le32_to_cpu(hdr->NextCommand) > 0)
+ sz = large_sz;
+
+ work->response_buf = kvmalloc(sz, GFP_KERNEL | __GFP_ZERO);
+ if (!work->response_buf)
+ return -ENOMEM;
+
+ work->response_sz = sz;
+ return 0;
+}
+
+/**
+ * smb2_check_user_session() - check for valid session for a user
+ * @work: smb work containing smb request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_check_user_session(struct ksmbd_work *work)
+{
+ struct smb2_hdr *req_hdr = work->request_buf;
+ struct ksmbd_conn *conn = work->conn;
+ unsigned int cmd = conn->ops->get_cmd_val(work);
+ unsigned long long sess_id;
+
+ work->sess = NULL;
+ /*
+ * SMB2_ECHO, SMB2_NEGOTIATE, SMB2_SESSION_SETUP command do not
+ * require a session id, so no need to validate user session's for
+ * these commands.
+ */
+ if (cmd == SMB2_ECHO_HE || cmd == SMB2_NEGOTIATE_HE ||
+ cmd == SMB2_SESSION_SETUP_HE)
+ return 0;
+
+ if (!ksmbd_conn_good(work))
+ return -EINVAL;
+
+ sess_id = le64_to_cpu(req_hdr->SessionId);
+ /* Check for validity of user session */
+ work->sess = ksmbd_session_lookup_all(conn, sess_id);
+ if (work->sess)
+ return 1;
+ ksmbd_debug(SMB, "Invalid user session, Uid %llu\n", sess_id);
+ return -EINVAL;
+}
+
+static void destroy_previous_session(struct ksmbd_user *user, u64 id)
+{
+ struct ksmbd_session *prev_sess = ksmbd_session_lookup_slowpath(id);
+ struct ksmbd_user *prev_user;
+
+ if (!prev_sess)
+ return;
+
+ prev_user = prev_sess->user;
+
+ if (!prev_user ||
+ strcmp(user->name, prev_user->name) ||
+ user->passkey_sz != prev_user->passkey_sz ||
+ memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) {
+ put_session(prev_sess);
+ return;
+ }
+
+ put_session(prev_sess);
+ ksmbd_session_destroy(prev_sess);
+}
+
+/**
+ * smb2_get_name() - get filename string from on the wire smb format
+ * @share: ksmbd_share_config pointer
+ * @src: source buffer
+ * @maxlen: maxlen of source string
+ * @nls_table: nls_table pointer
+ *
+ * Return: matching converted filename on success, otherwise error ptr
+ */
+static char *
+smb2_get_name(struct ksmbd_share_config *share, const char *src,
+ const int maxlen, struct nls_table *local_nls)
+{
+ char *name, *unixname;
+
+ name = smb_strndup_from_utf16(src, maxlen, 1, local_nls);
+ if (IS_ERR(name)) {
+ pr_err("failed to get name %ld\n", PTR_ERR(name));
+ return name;
+ }
+
+ /* change it to absolute unix name */
+ ksmbd_conv_path_to_unix(name);
+ ksmbd_strip_last_slash(name);
+
+ unixname = convert_to_unix_name(share, name);
+ kfree(name);
+ if (!unixname) {
+ pr_err("can not convert absolute name\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ksmbd_debug(SMB, "absolute name = %s\n", unixname);
+ return unixname;
+}
+
+int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
+{
+ struct smb2_hdr *rsp_hdr;
+ struct ksmbd_conn *conn = work->conn;
+ int id;
+
+ rsp_hdr = work->response_buf;
+ rsp_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+
+ id = ksmbd_acquire_async_msg_id(&conn->async_ida);
+ if (id < 0) {
+ pr_err("Failed to alloc async message id\n");
+ return id;
+ }
+ work->syncronous = false;
+ work->async_id = id;
+ rsp_hdr->Id.AsyncId = cpu_to_le64(id);
+
+ ksmbd_debug(SMB,
+ "Send interim Response to inform async request id : %d\n",
+ work->async_id);
+
+ work->cancel_fn = fn;
+ work->cancel_argv = arg;
+
+ if (list_empty(&work->async_request_entry)) {
+ spin_lock(&conn->request_lock);
+ list_add_tail(&work->async_request_entry, &conn->async_requests);
+ spin_unlock(&conn->request_lock);
+ }
+
+ return 0;
+}
+
+void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
+{
+ struct smb2_hdr *rsp_hdr;
+
+ rsp_hdr = work->response_buf;
+ smb2_set_err_rsp(work);
+ rsp_hdr->Status = status;
+
+ work->multiRsp = 1;
+ ksmbd_conn_write(work);
+ rsp_hdr->Status = 0;
+ work->multiRsp = 0;
+}
+
+static __le32 smb2_get_reparse_tag_special_file(umode_t mode)
+{
+ if (S_ISDIR(mode) || S_ISREG(mode))
+ return 0;
+
+ if (S_ISLNK(mode))
+ return IO_REPARSE_TAG_LX_SYMLINK_LE;
+ else if (S_ISFIFO(mode))
+ return IO_REPARSE_TAG_LX_FIFO_LE;
+ else if (S_ISSOCK(mode))
+ return IO_REPARSE_TAG_AF_UNIX_LE;
+ else if (S_ISCHR(mode))
+ return IO_REPARSE_TAG_LX_CHR_LE;
+ else if (S_ISBLK(mode))
+ return IO_REPARSE_TAG_LX_BLK_LE;
+
+ return 0;
+}
+
+/**
+ * smb2_get_dos_mode() - get file mode in dos format from unix mode
+ * @stat: kstat containing file mode
+ * @attribute: attribute flags
+ *
+ * Return: converted dos mode
+ */
+static int smb2_get_dos_mode(struct kstat *stat, int attribute)
+{
+ int attr = 0;
+
+ if (S_ISDIR(stat->mode)) {
+ attr = ATTR_DIRECTORY |
+ (attribute & (ATTR_HIDDEN | ATTR_SYSTEM));
+ } else {
+ attr = (attribute & 0x00005137) | ATTR_ARCHIVE;
+ attr &= ~(ATTR_DIRECTORY);
+ if (S_ISREG(stat->mode) && (server_conf.share_fake_fscaps &
+ FILE_SUPPORTS_SPARSE_FILES))
+ attr |= ATTR_SPARSE;
+
+ if (smb2_get_reparse_tag_special_file(stat->mode))
+ attr |= ATTR_REPARSE;
+ }
+
+ return attr;
+}
+
+static void build_preauth_ctxt(struct smb2_preauth_neg_context *pneg_ctxt,
+ __le16 hash_id)
+{
+ pneg_ctxt->ContextType = SMB2_PREAUTH_INTEGRITY_CAPABILITIES;
+ pneg_ctxt->DataLength = cpu_to_le16(38);
+ pneg_ctxt->HashAlgorithmCount = cpu_to_le16(1);
+ pneg_ctxt->Reserved = cpu_to_le32(0);
+ pneg_ctxt->SaltLength = cpu_to_le16(SMB311_SALT_SIZE);
+ get_random_bytes(pneg_ctxt->Salt, SMB311_SALT_SIZE);
+ pneg_ctxt->HashAlgorithms = hash_id;
+}
+
+static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
+ __le16 cipher_type)
+{
+ pneg_ctxt->ContextType = SMB2_ENCRYPTION_CAPABILITIES;
+ pneg_ctxt->DataLength = cpu_to_le16(4);
+ pneg_ctxt->Reserved = cpu_to_le32(0);
+ pneg_ctxt->CipherCount = cpu_to_le16(1);
+ pneg_ctxt->Ciphers[0] = cipher_type;
+}
+
+static void build_compression_ctxt(struct smb2_compression_ctx *pneg_ctxt,
+ __le16 comp_algo)
+{
+ pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
+ pneg_ctxt->DataLength =
+ cpu_to_le16(sizeof(struct smb2_compression_ctx)
+ - sizeof(struct smb2_neg_context));
+ pneg_ctxt->Reserved = cpu_to_le32(0);
+ pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(1);
+ pneg_ctxt->Reserved1 = cpu_to_le32(0);
+ pneg_ctxt->CompressionAlgorithms[0] = comp_algo;
+}
+
+static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
+ __le16 sign_algo)
+{
+ pneg_ctxt->ContextType = SMB2_SIGNING_CAPABILITIES;
+ pneg_ctxt->DataLength =
+ cpu_to_le16((sizeof(struct smb2_signing_capabilities) + 2)
+ - sizeof(struct smb2_neg_context));
+ pneg_ctxt->Reserved = cpu_to_le32(0);
+ pneg_ctxt->SigningAlgorithmCount = cpu_to_le16(1);
+ pneg_ctxt->SigningAlgorithms[0] = sign_algo;
+}
+
+static void build_posix_ctxt(struct smb2_posix_neg_context *pneg_ctxt)
+{
+ pneg_ctxt->ContextType = SMB2_POSIX_EXTENSIONS_AVAILABLE;
+ pneg_ctxt->DataLength = cpu_to_le16(POSIX_CTXT_DATA_LEN);
+ /* SMB2_CREATE_TAG_POSIX is "0x93AD25509CB411E7B42383DE968BCD7C" */
+ pneg_ctxt->Name[0] = 0x93;
+ pneg_ctxt->Name[1] = 0xAD;
+ pneg_ctxt->Name[2] = 0x25;
+ pneg_ctxt->Name[3] = 0x50;
+ pneg_ctxt->Name[4] = 0x9C;
+ pneg_ctxt->Name[5] = 0xB4;
+ pneg_ctxt->Name[6] = 0x11;
+ pneg_ctxt->Name[7] = 0xE7;
+ pneg_ctxt->Name[8] = 0xB4;
+ pneg_ctxt->Name[9] = 0x23;
+ pneg_ctxt->Name[10] = 0x83;
+ pneg_ctxt->Name[11] = 0xDE;
+ pneg_ctxt->Name[12] = 0x96;
+ pneg_ctxt->Name[13] = 0x8B;
+ pneg_ctxt->Name[14] = 0xCD;
+ pneg_ctxt->Name[15] = 0x7C;
+}
+
+static void assemble_neg_contexts(struct ksmbd_conn *conn,
+ struct smb2_negotiate_rsp *rsp)
+{
+ /* +4 is to account for the RFC1001 len field */
+ char *pneg_ctxt = (char *)rsp +
+ le32_to_cpu(rsp->NegotiateContextOffset) + 4;
+ int neg_ctxt_cnt = 1;
+ int ctxt_size;
+
+ ksmbd_debug(SMB,
+ "assemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n");
+ build_preauth_ctxt((struct smb2_preauth_neg_context *)pneg_ctxt,
+ conn->preauth_info->Preauth_HashId);
+ rsp->NegotiateContextCount = cpu_to_le16(neg_ctxt_cnt);
+ inc_rfc1001_len(rsp, AUTH_GSS_PADDING);
+ ctxt_size = sizeof(struct smb2_preauth_neg_context);
+ /* Round to 8 byte boundary */
+ pneg_ctxt += round_up(sizeof(struct smb2_preauth_neg_context), 8);
+
+ if (conn->cipher_type) {
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_ENCRYPTION_CAPABILITIES context\n");
+ build_encrypt_ctxt((struct smb2_encryption_neg_context *)pneg_ctxt,
+ conn->cipher_type);
+ rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
+ ctxt_size += sizeof(struct smb2_encryption_neg_context) + 2;
+ /* Round to 8 byte boundary */
+ pneg_ctxt +=
+ round_up(sizeof(struct smb2_encryption_neg_context) + 2,
+ 8);
+ }
+
+ if (conn->compress_algorithm) {
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_COMPRESSION_CAPABILITIES context\n");
+ /* Temporarily set to SMB3_COMPRESS_NONE */
+ build_compression_ctxt((struct smb2_compression_ctx *)pneg_ctxt,
+ conn->compress_algorithm);
+ rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
+ ctxt_size += sizeof(struct smb2_compression_ctx) + 2;
+ /* Round to 8 byte boundary */
+ pneg_ctxt += round_up(sizeof(struct smb2_compression_ctx) + 2,
+ 8);
+ }
+
+ if (conn->posix_ext_supported) {
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
+ build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt);
+ rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
+ ctxt_size += sizeof(struct smb2_posix_neg_context);
+ /* Round to 8 byte boundary */
+ pneg_ctxt += round_up(sizeof(struct smb2_posix_neg_context), 8);
+ }
+
+ if (conn->signing_negotiated) {
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_SIGNING_CAPABILITIES context\n");
+ build_sign_cap_ctxt((struct smb2_signing_capabilities *)pneg_ctxt,
+ conn->signing_algorithm);
+ rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
+ ctxt_size += sizeof(struct smb2_signing_capabilities) + 2;
+ }
+
+ inc_rfc1001_len(rsp, ctxt_size);
+}
+
+static __le32 decode_preauth_ctxt(struct ksmbd_conn *conn,
+ struct smb2_preauth_neg_context *pneg_ctxt)
+{
+ __le32 err = STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP;
+
+ if (pneg_ctxt->HashAlgorithms == SMB2_PREAUTH_INTEGRITY_SHA512) {
+ conn->preauth_info->Preauth_HashId =
+ SMB2_PREAUTH_INTEGRITY_SHA512;
+ err = STATUS_SUCCESS;
+ }
+
+ return err;
+}
+
+static void decode_encrypt_ctxt(struct ksmbd_conn *conn,
+ struct smb2_encryption_neg_context *pneg_ctxt,
+ int len_of_ctxts)
+{
+ int cph_cnt = le16_to_cpu(pneg_ctxt->CipherCount);
+ int i, cphs_size = cph_cnt * sizeof(__le16);
+
+ conn->cipher_type = 0;
+
+ if (sizeof(struct smb2_encryption_neg_context) + cphs_size >
+ len_of_ctxts) {
+ pr_err("Invalid cipher count(%d)\n", cph_cnt);
+ return;
+ }
+
+ if (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION))
+ return;
+
+ for (i = 0; i < cph_cnt; i++) {
+ if (pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_GCM ||
+ pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_CCM ||
+ pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_CCM ||
+ pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_GCM) {
+ ksmbd_debug(SMB, "Cipher ID = 0x%x\n",
+ pneg_ctxt->Ciphers[i]);
+ conn->cipher_type = pneg_ctxt->Ciphers[i];
+ break;
+ }
+ }
+}
+
+static void decode_compress_ctxt(struct ksmbd_conn *conn,
+ struct smb2_compression_ctx *pneg_ctxt)
+{
+ conn->compress_algorithm = SMB3_COMPRESS_NONE;
+}
+
+static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
+ struct smb2_signing_capabilities *pneg_ctxt,
+ int len_of_ctxts)
+{
+ int sign_algo_cnt = le16_to_cpu(pneg_ctxt->SigningAlgorithmCount);
+ int i, sign_alos_size = sign_algo_cnt * sizeof(__le16);
+
+ conn->signing_negotiated = false;
+
+ if (sizeof(struct smb2_signing_capabilities) + sign_alos_size >
+ len_of_ctxts) {
+ pr_err("Invalid signing algorithm count(%d)\n", sign_algo_cnt);
+ return;
+ }
+
+ for (i = 0; i < sign_algo_cnt; i++) {
+ if (pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_HMAC_SHA256 ||
+ pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_AES_CMAC) {
+ ksmbd_debug(SMB, "Signing Algorithm ID = 0x%x\n",
+ pneg_ctxt->SigningAlgorithms[i]);
+ conn->signing_negotiated = true;
+ conn->signing_algorithm =
+ pneg_ctxt->SigningAlgorithms[i];
+ break;
+ }
+ }
+}
+
+static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
+ struct smb2_negotiate_req *req)
+{
+ /* +4 is to account for the RFC1001 len field */
+ struct smb2_neg_context *pctx = (struct smb2_neg_context *)((char *)req + 4);
+ int i = 0, len_of_ctxts;
+ int offset = le32_to_cpu(req->NegotiateContextOffset);
+ int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
+ int len_of_smb = be32_to_cpu(req->hdr.smb2_buf_length);
+ __le32 status = STATUS_INVALID_PARAMETER;
+
+ ksmbd_debug(SMB, "decoding %d negotiate contexts\n", neg_ctxt_cnt);
+ if (len_of_smb <= offset) {
+ ksmbd_debug(SMB, "Invalid response: negotiate context offset\n");
+ return status;
+ }
+
+ len_of_ctxts = len_of_smb - offset;
+
+ while (i++ < neg_ctxt_cnt) {
+ int clen;
+
+ /* check that offset is not beyond end of SMB */
+ if (len_of_ctxts == 0)
+ break;
+
+ if (len_of_ctxts < sizeof(struct smb2_neg_context))
+ break;
+
+ pctx = (struct smb2_neg_context *)((char *)pctx + offset);
+ clen = le16_to_cpu(pctx->DataLength);
+ if (clen + sizeof(struct smb2_neg_context) > len_of_ctxts)
+ break;
+
+ if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n");
+ if (conn->preauth_info->Preauth_HashId)
+ break;
+
+ status = decode_preauth_ctxt(conn,
+ (struct smb2_preauth_neg_context *)pctx);
+ if (status != STATUS_SUCCESS)
+ break;
+ } else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_ENCRYPTION_CAPABILITIES context\n");
+ if (conn->cipher_type)
+ break;
+
+ decode_encrypt_ctxt(conn,
+ (struct smb2_encryption_neg_context *)pctx,
+ len_of_ctxts);
+ } else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_COMPRESSION_CAPABILITIES context\n");
+ if (conn->compress_algorithm)
+ break;
+
+ decode_compress_ctxt(conn,
+ (struct smb2_compression_ctx *)pctx);
+ } else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n");
+ } else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
+ conn->posix_ext_supported = true;
+ } else if (pctx->ContextType == SMB2_SIGNING_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_SIGNING_CAPABILITIES context\n");
+ decode_sign_cap_ctxt(conn,
+ (struct smb2_signing_capabilities *)pctx,
+ len_of_ctxts);
+ }
+
+ /* offsets must be 8 byte aligned */
+ clen = (clen + 7) & ~0x7;
+ offset = clen + sizeof(struct smb2_neg_context);
+ len_of_ctxts -= clen + sizeof(struct smb2_neg_context);
+ }
+ return status;
+}
+
+/**
+ * smb2_handle_negotiate() - handler for smb2 negotiate command
+ * @work: smb work containing smb request buffer
+ *
+ * Return: 0
+ */
+int smb2_handle_negotiate(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_negotiate_req *req = work->request_buf;
+ struct smb2_negotiate_rsp *rsp = work->response_buf;
+ int rc = 0;
+ __le32 status;
+
+ ksmbd_debug(SMB, "Received negotiate request\n");
+ conn->need_neg = false;
+ if (ksmbd_conn_good(work)) {
+ pr_err("conn->tcp_status is already in CifsGood State\n");
+ work->send_no_response = 1;
+ return rc;
+ }
+
+ if (req->DialectCount == 0) {
+ pr_err("malformed packet\n");
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ rc = -EINVAL;
+ goto err_out;
+ }
+
+ conn->cli_cap = le32_to_cpu(req->Capabilities);
+ switch (conn->dialect) {
+ case SMB311_PROT_ID:
+ conn->preauth_info =
+ kzalloc(sizeof(struct preauth_integrity_info),
+ GFP_KERNEL);
+ if (!conn->preauth_info) {
+ rc = -ENOMEM;
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ status = deassemble_neg_contexts(conn, req);
+ if (status != STATUS_SUCCESS) {
+ pr_err("deassemble_neg_contexts error(0x%x)\n",
+ status);
+ rsp->hdr.Status = status;
+ rc = -EINVAL;
+ goto err_out;
+ }
+
+ rc = init_smb3_11_server(conn);
+ if (rc < 0) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ ksmbd_gen_preauth_integrity_hash(conn,
+ work->request_buf,
+ conn->preauth_info->Preauth_HashValue);
+ rsp->NegotiateContextOffset =
+ cpu_to_le32(OFFSET_OF_NEG_CONTEXT);
+ assemble_neg_contexts(conn, rsp);
+ break;
+ case SMB302_PROT_ID:
+ init_smb3_02_server(conn);
+ break;
+ case SMB30_PROT_ID:
+ init_smb3_0_server(conn);
+ break;
+ case SMB21_PROT_ID:
+ init_smb2_1_server(conn);
+ break;
+ case SMB20_PROT_ID:
+ rc = init_smb2_0_server(conn);
+ if (rc) {
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ goto err_out;
+ }
+ break;
+ case SMB2X_PROT_ID:
+ case BAD_PROT_ID:
+ default:
+ ksmbd_debug(SMB, "Server dialect :0x%x not supported\n",
+ conn->dialect);
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ rc = -EINVAL;
+ goto err_out;
+ }
+ rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+
+ /* For stats */
+ conn->connection_type = conn->dialect;
+
+ rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
+ rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
+ rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
+
+ if (conn->dialect > SMB20_PROT_ID) {
+ memcpy(conn->ClientGUID, req->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE);
+ conn->cli_sec_mode = le16_to_cpu(req->SecurityMode);
+ }
+
+ rsp->StructureSize = cpu_to_le16(65);
+ rsp->DialectRevision = cpu_to_le16(conn->dialect);
+ /* Not setting conn guid rsp->ServerGUID, as it
+ * not used by client for identifying server
+ */
+ memset(rsp->ServerGUID, 0, SMB2_CLIENT_GUID_SIZE);
+
+ rsp->SystemTime = cpu_to_le64(ksmbd_systime());
+ rsp->ServerStartTime = 0;
+ ksmbd_debug(SMB, "negotiate context offset %d, count %d\n",
+ le32_to_cpu(rsp->NegotiateContextOffset),
+ le16_to_cpu(rsp->NegotiateContextCount));
+
+ rsp->SecurityBufferOffset = cpu_to_le16(128);
+ rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
+ ksmbd_copy_gss_neg_header(((char *)(&rsp->hdr) +
+ sizeof(rsp->hdr.smb2_buf_length)) +
+ le16_to_cpu(rsp->SecurityBufferOffset));
+ inc_rfc1001_len(rsp, sizeof(struct smb2_negotiate_rsp) -
+ sizeof(struct smb2_hdr) - sizeof(rsp->Buffer) +
+ AUTH_GSS_LENGTH);
+ rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
+ conn->use_spnego = true;
+
+ if ((server_conf.signing == KSMBD_CONFIG_OPT_AUTO ||
+ server_conf.signing == KSMBD_CONFIG_OPT_DISABLED) &&
+ req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED_LE)
+ conn->sign = true;
+ else if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY) {
+ server_conf.enforced_signing = true;
+ rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
+ conn->sign = true;
+ }
+
+ conn->srv_sec_mode = le16_to_cpu(rsp->SecurityMode);
+ ksmbd_conn_set_need_negotiate(work);
+
+err_out:
+ if (rc < 0)
+ smb2_set_err_rsp(work);
+
+ return rc;
+}
+
+static int alloc_preauth_hash(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn)
+{
+ if (sess->Preauth_HashValue)
+ return 0;
+
+ sess->Preauth_HashValue = kmemdup(conn->preauth_info->Preauth_HashValue,
+ PREAUTH_HASHVALUE_SIZE, GFP_KERNEL);
+ if (!sess->Preauth_HashValue)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int generate_preauth_hash(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ u8 *preauth_hash;
+
+ if (conn->dialect != SMB311_PROT_ID)
+ return 0;
+
+ if (conn->binding) {
+ struct preauth_session *preauth_sess;
+
+ preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
+ if (!preauth_sess) {
+ preauth_sess = ksmbd_preauth_session_alloc(conn, sess->id);
+ if (!preauth_sess)
+ return -ENOMEM;
+ }
+
+ preauth_hash = preauth_sess->Preauth_HashValue;
+ } else {
+ if (!sess->Preauth_HashValue)
+ if (alloc_preauth_hash(sess, conn))
+ return -ENOMEM;
+ preauth_hash = sess->Preauth_HashValue;
+ }
+
+ ksmbd_gen_preauth_integrity_hash(conn, work->request_buf, preauth_hash);
+ return 0;
+}
+
+static int decode_negotiation_token(struct ksmbd_work *work,
+ struct negotiate_message *negblob)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_sess_setup_req *req;
+ int sz;
+
+ if (!conn->use_spnego)
+ return -EINVAL;
+
+ req = work->request_buf;
+ sz = le16_to_cpu(req->SecurityBufferLength);
+
+ if (ksmbd_decode_negTokenInit((char *)negblob, sz, conn)) {
+ if (ksmbd_decode_negTokenTarg((char *)negblob, sz, conn)) {
+ conn->auth_mechs |= KSMBD_AUTH_NTLMSSP;
+ conn->preferred_auth_mech = KSMBD_AUTH_NTLMSSP;
+ conn->use_spnego = false;
+ }
+ }
+ return 0;
+}
+
+static int ntlm_negotiate(struct ksmbd_work *work,
+ struct negotiate_message *negblob)
+{
+ struct smb2_sess_setup_req *req = work->request_buf;
+ struct smb2_sess_setup_rsp *rsp = work->response_buf;
+ struct challenge_message *chgblob;
+ unsigned char *spnego_blob = NULL;
+ u16 spnego_blob_len;
+ char *neg_blob;
+ int sz, rc;
+
+ ksmbd_debug(SMB, "negotiate phase\n");
+ sz = le16_to_cpu(req->SecurityBufferLength);
+ rc = ksmbd_decode_ntlmssp_neg_blob(negblob, sz, work->sess);
+ if (rc)
+ return rc;
+
+ sz = le16_to_cpu(rsp->SecurityBufferOffset);
+ chgblob =
+ (struct challenge_message *)((char *)&rsp->hdr.ProtocolId + sz);
+ memset(chgblob, 0, sizeof(struct challenge_message));
+
+ if (!work->conn->use_spnego) {
+ sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->sess);
+ if (sz < 0)
+ return -ENOMEM;
+
+ rsp->SecurityBufferLength = cpu_to_le16(sz);
+ return 0;
+ }
+
+ sz = sizeof(struct challenge_message);
+ sz += (strlen(ksmbd_netbios_name()) * 2 + 1 + 4) * 6;
+
+ neg_blob = kzalloc(sz, GFP_KERNEL);
+ if (!neg_blob)
+ return -ENOMEM;
+
+ chgblob = (struct challenge_message *)neg_blob;
+ sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->sess);
+ if (sz < 0) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ rc = build_spnego_ntlmssp_neg_blob(&spnego_blob, &spnego_blob_len,
+ neg_blob, sz);
+ if (rc) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ sz = le16_to_cpu(rsp->SecurityBufferOffset);
+ memcpy((char *)&rsp->hdr.ProtocolId + sz, spnego_blob, spnego_blob_len);
+ rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
+
+out:
+ kfree(spnego_blob);
+ kfree(neg_blob);
+ return rc;
+}
+
+static struct authenticate_message *user_authblob(struct ksmbd_conn *conn,
+ struct smb2_sess_setup_req *req)
+{
+ int sz;
+
+ if (conn->use_spnego && conn->mechToken)
+ return (struct authenticate_message *)conn->mechToken;
+
+ sz = le16_to_cpu(req->SecurityBufferOffset);
+ return (struct authenticate_message *)((char *)&req->hdr.ProtocolId
+ + sz);
+}
+
+static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
+ struct smb2_sess_setup_req *req)
+{
+ struct authenticate_message *authblob;
+ struct ksmbd_user *user;
+ char *name;
+ int sz;
+
+ authblob = user_authblob(conn, req);
+ sz = le32_to_cpu(authblob->UserName.BufferOffset);
+ name = smb_strndup_from_utf16((const char *)authblob + sz,
+ le16_to_cpu(authblob->UserName.Length),
+ true,
+ conn->local_nls);
+ if (IS_ERR(name)) {
+ pr_err("cannot allocate memory\n");
+ return NULL;
+ }
+
+ ksmbd_debug(SMB, "session setup request for user %s\n", name);
+ user = ksmbd_login_user(name);
+ kfree(name);
+ return user;
+}
+
+static int ntlm_authenticate(struct ksmbd_work *work)
+{
+ struct smb2_sess_setup_req *req = work->request_buf;
+ struct smb2_sess_setup_rsp *rsp = work->response_buf;
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ struct channel *chann = NULL;
+ struct ksmbd_user *user;
+ u64 prev_id;
+ int sz, rc;
+
+ ksmbd_debug(SMB, "authenticate phase\n");
+ if (conn->use_spnego) {
+ unsigned char *spnego_blob;
+ u16 spnego_blob_len;
+
+ rc = build_spnego_ntlmssp_auth_blob(&spnego_blob,
+ &spnego_blob_len,
+ 0);
+ if (rc)
+ return -ENOMEM;
+
+ sz = le16_to_cpu(rsp->SecurityBufferOffset);
+ memcpy((char *)&rsp->hdr.ProtocolId + sz, spnego_blob, spnego_blob_len);
+ rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
+ kfree(spnego_blob);
+ inc_rfc1001_len(rsp, spnego_blob_len - 1);
+ }
+
+ user = session_user(conn, req);
+ if (!user) {
+ ksmbd_debug(SMB, "Unknown user name or an error\n");
+ return -EPERM;
+ }
+
+ /* Check for previous session */
+ prev_id = le64_to_cpu(req->PreviousSessionId);
+ if (prev_id && prev_id != sess->id)
+ destroy_previous_session(user, prev_id);
+
+ if (sess->state == SMB2_SESSION_VALID) {
+ /*
+ * Reuse session if anonymous try to connect
+ * on reauthetication.
+ */
+ if (ksmbd_anonymous_user(user)) {
+ ksmbd_free_user(user);
+ return 0;
+ }
+ ksmbd_free_user(sess->user);
+ }
+
+ sess->user = user;
+ if (user_guest(sess->user)) {
+ if (conn->sign) {
+ ksmbd_debug(SMB, "Guest login not allowed when signing enabled\n");
+ return -EPERM;
+ }
+
+ rsp->SessionFlags = SMB2_SESSION_FLAG_IS_GUEST_LE;
+ } else {
+ struct authenticate_message *authblob;
+
+ authblob = user_authblob(conn, req);
+ sz = le16_to_cpu(req->SecurityBufferLength);
+ rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, sess);
+ if (rc) {
+ set_user_flag(sess->user, KSMBD_USER_FLAG_BAD_PASSWORD);
+ ksmbd_debug(SMB, "authentication failed\n");
+ return -EPERM;
+ }
+
+ /*
+ * If session state is SMB2_SESSION_VALID, We can assume
+ * that it is reauthentication. And the user/password
+ * has been verified, so return it here.
+ */
+ if (sess->state == SMB2_SESSION_VALID) {
+ if (conn->binding)
+ goto binding_session;
+ return 0;
+ }
+
+ if ((conn->sign || server_conf.enforced_signing) ||
+ (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
+ sess->sign = true;
+
+ if (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION &&
+ conn->ops->generate_encryptionkey &&
+ !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+ rc = conn->ops->generate_encryptionkey(sess);
+ if (rc) {
+ ksmbd_debug(SMB,
+ "SMB3 encryption key generation failed\n");
+ return -EINVAL;
+ }
+ sess->enc = true;
+ rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
+ /*
+ * signing is disable if encryption is enable
+ * on this session
+ */
+ sess->sign = false;
+ }
+ }
+
+binding_session:
+ if (conn->dialect >= SMB30_PROT_ID) {
+ chann = lookup_chann_list(sess, conn);
+ if (!chann) {
+ chann = kmalloc(sizeof(struct channel), GFP_KERNEL);
+ if (!chann)
+ return -ENOMEM;
+
+ chann->conn = conn;
+ INIT_LIST_HEAD(&chann->chann_list);
+ list_add(&chann->chann_list, &sess->ksmbd_chann_list);
+ }
+ }
+
+ if (conn->ops->generate_signingkey) {
+ rc = conn->ops->generate_signingkey(sess, conn);
+ if (rc) {
+ ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (conn->dialect > SMB20_PROT_ID) {
+ if (!ksmbd_conn_lookup_dialect(conn)) {
+ pr_err("fail to verify the dialect\n");
+ return -ENOENT;
+ }
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+static int krb5_authenticate(struct ksmbd_work *work)
+{
+ struct smb2_sess_setup_req *req = work->request_buf;
+ struct smb2_sess_setup_rsp *rsp = work->response_buf;
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ char *in_blob, *out_blob;
+ struct channel *chann = NULL;
+ u64 prev_sess_id;
+ int in_len, out_len;
+ int retval;
+
+ in_blob = (char *)&req->hdr.ProtocolId +
+ le16_to_cpu(req->SecurityBufferOffset);
+ in_len = le16_to_cpu(req->SecurityBufferLength);
+ out_blob = (char *)&rsp->hdr.ProtocolId +
+ le16_to_cpu(rsp->SecurityBufferOffset);
+ out_len = work->response_sz -
+ offsetof(struct smb2_hdr, smb2_buf_length) -
+ le16_to_cpu(rsp->SecurityBufferOffset);
+
+ /* Check previous session */
+ prev_sess_id = le64_to_cpu(req->PreviousSessionId);
+ if (prev_sess_id && prev_sess_id != sess->id)
+ destroy_previous_session(sess->user, prev_sess_id);
+
+ if (sess->state == SMB2_SESSION_VALID)
+ ksmbd_free_user(sess->user);
+
+ retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
+ out_blob, &out_len);
+ if (retval) {
+ ksmbd_debug(SMB, "krb5 authentication failed\n");
+ return -EINVAL;
+ }
+ rsp->SecurityBufferLength = cpu_to_le16(out_len);
+ inc_rfc1001_len(rsp, out_len - 1);
+
+ if ((conn->sign || server_conf.enforced_signing) ||
+ (req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
+ sess->sign = true;
+
+ if ((conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) &&
+ conn->ops->generate_encryptionkey) {
+ retval = conn->ops->generate_encryptionkey(sess);
+ if (retval) {
+ ksmbd_debug(SMB,
+ "SMB3 encryption key generation failed\n");
+ return -EINVAL;
+ }
+ sess->enc = true;
+ rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
+ sess->sign = false;
+ }
+
+ if (conn->dialect >= SMB30_PROT_ID) {
+ chann = lookup_chann_list(sess, conn);
+ if (!chann) {
+ chann = kmalloc(sizeof(struct channel), GFP_KERNEL);
+ if (!chann)
+ return -ENOMEM;
+
+ chann->conn = conn;
+ INIT_LIST_HEAD(&chann->chann_list);
+ list_add(&chann->chann_list, &sess->ksmbd_chann_list);
+ }
+ }
+
+ if (conn->ops->generate_signingkey) {
+ retval = conn->ops->generate_signingkey(sess, conn);
+ if (retval) {
+ ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (conn->dialect > SMB20_PROT_ID) {
+ if (!ksmbd_conn_lookup_dialect(conn)) {
+ pr_err("fail to verify the dialect\n");
+ return -ENOENT;
+ }
+ }
+ return 0;
+}
+#else
+static int krb5_authenticate(struct ksmbd_work *work)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+int smb2_sess_setup(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_sess_setup_req *req = work->request_buf;
+ struct smb2_sess_setup_rsp *rsp = work->response_buf;
+ struct ksmbd_session *sess;
+ struct negotiate_message *negblob;
+ int rc = 0;
+
+ ksmbd_debug(SMB, "Received request for session setup\n");
+
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->SessionFlags = 0;
+ rsp->SecurityBufferOffset = cpu_to_le16(72);
+ rsp->SecurityBufferLength = 0;
+ inc_rfc1001_len(rsp, 9);
+
+ if (!req->hdr.SessionId) {
+ sess = ksmbd_smb2_session_create();
+ if (!sess) {
+ rc = -ENOMEM;
+ goto out_err;
+ }
+ rsp->hdr.SessionId = cpu_to_le64(sess->id);
+ ksmbd_session_register(conn, sess);
+ } else if (conn->dialect >= SMB30_PROT_ID &&
+ (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
+ req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) {
+ u64 sess_id = le64_to_cpu(req->hdr.SessionId);
+
+ sess = ksmbd_session_lookup_slowpath(sess_id);
+ if (!sess) {
+ rc = -ENOENT;
+ goto out_err;
+ }
+
+ if (conn->dialect != sess->conn->dialect) {
+ rc = -EINVAL;
+ goto out_err;
+ }
+
+ if (!(req->hdr.Flags & SMB2_FLAGS_SIGNED)) {
+ rc = -EINVAL;
+ goto out_err;
+ }
+
+ if (strncmp(conn->ClientGUID, sess->conn->ClientGUID,
+ SMB2_CLIENT_GUID_SIZE)) {
+ rc = -ENOENT;
+ goto out_err;
+ }
+
+ if (sess->state == SMB2_SESSION_IN_PROGRESS) {
+ rc = -EACCES;
+ goto out_err;
+ }
+
+ if (sess->state == SMB2_SESSION_EXPIRED) {
+ rc = -EFAULT;
+ goto out_err;
+ }
+
+ if (ksmbd_session_lookup(conn, sess_id)) {
+ rc = -EACCES;
+ goto out_err;
+ }
+
+ conn->binding = true;
+ } else if ((conn->dialect < SMB30_PROT_ID ||
+ server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
+ (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+ sess = NULL;
+ rc = -EACCES;
+ goto out_err;
+ } else {
+ sess = ksmbd_session_lookup(conn,
+ le64_to_cpu(req->hdr.SessionId));
+ if (!sess) {
+ rc = -ENOENT;
+ goto out_err;
+ }
+ }
+ work->sess = sess;
+
+ if (sess->state == SMB2_SESSION_EXPIRED)
+ sess->state = SMB2_SESSION_IN_PROGRESS;
+
+ negblob = (struct negotiate_message *)((char *)&req->hdr.ProtocolId +
+ le16_to_cpu(req->SecurityBufferOffset));
+
+ if (decode_negotiation_token(work, negblob) == 0) {
+ if (conn->mechToken)
+ negblob = (struct negotiate_message *)conn->mechToken;
+ }
+
+ if (server_conf.auth_mechs & conn->auth_mechs) {
+ rc = generate_preauth_hash(work);
+ if (rc)
+ goto out_err;
+
+ if (conn->preferred_auth_mech &
+ (KSMBD_AUTH_KRB5 | KSMBD_AUTH_MSKRB5)) {
+ rc = krb5_authenticate(work);
+ if (rc) {
+ rc = -EINVAL;
+ goto out_err;
+ }
+
+ ksmbd_conn_set_good(work);
+ sess->state = SMB2_SESSION_VALID;
+ kfree(sess->Preauth_HashValue);
+ sess->Preauth_HashValue = NULL;
+ } else if (conn->preferred_auth_mech == KSMBD_AUTH_NTLMSSP) {
+ if (negblob->MessageType == NtLmNegotiate) {
+ rc = ntlm_negotiate(work, negblob);
+ if (rc)
+ goto out_err;
+ rsp->hdr.Status =
+ STATUS_MORE_PROCESSING_REQUIRED;
+ /*
+ * Note: here total size -1 is done as an
+ * adjustment for 0 size blob
+ */
+ inc_rfc1001_len(rsp, le16_to_cpu(rsp->SecurityBufferLength) - 1);
+
+ } else if (negblob->MessageType == NtLmAuthenticate) {
+ rc = ntlm_authenticate(work);
+ if (rc)
+ goto out_err;
+
+ ksmbd_conn_set_good(work);
+ sess->state = SMB2_SESSION_VALID;
+ if (conn->binding) {
+ struct preauth_session *preauth_sess;
+
+ preauth_sess =
+ ksmbd_preauth_session_lookup(conn, sess->id);
+ if (preauth_sess) {
+ list_del(&preauth_sess->preauth_entry);
+ kfree(preauth_sess);
+ }
+ }
+ kfree(sess->Preauth_HashValue);
+ sess->Preauth_HashValue = NULL;
+ }
+ } else {
+ /* TODO: need one more negotiation */
+ pr_err("Not support the preferred authentication\n");
+ rc = -EINVAL;
+ }
+ } else {
+ pr_err("Not support authentication\n");
+ rc = -EINVAL;
+ }
+
+out_err:
+ if (rc == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (rc == -ENOENT)
+ rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
+ else if (rc == -EACCES)
+ rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
+ else if (rc == -EFAULT)
+ rsp->hdr.Status = STATUS_NETWORK_SESSION_EXPIRED;
+ else if (rc == -ENOMEM)
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ else if (rc)
+ rsp->hdr.Status = STATUS_LOGON_FAILURE;
+
+ if (conn->use_spnego && conn->mechToken) {
+ kfree(conn->mechToken);
+ conn->mechToken = NULL;
+ }
+
+ if (rc < 0 && sess) {
+ ksmbd_session_destroy(sess);
+ work->sess = NULL;
+ }
+
+ return rc;
+}
+
+/**
+ * smb2_tree_connect() - handler for smb2 tree connect command
+ * @work: smb work containing smb request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_tree_connect(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_tree_connect_req *req = work->request_buf;
+ struct smb2_tree_connect_rsp *rsp = work->response_buf;
+ struct ksmbd_session *sess = work->sess;
+ char *treename = NULL, *name = NULL;
+ struct ksmbd_tree_conn_status status;
+ struct ksmbd_share_config *share;
+ int rc = -EINVAL;
+
+ treename = smb_strndup_from_utf16(req->Buffer,
+ le16_to_cpu(req->PathLength), true,
+ conn->local_nls);
+ if (IS_ERR(treename)) {
+ pr_err("treename is NULL\n");
+ status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
+ goto out_err1;
+ }
+
+ name = ksmbd_extract_sharename(treename);
+ if (IS_ERR(name)) {
+ status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
+ goto out_err1;
+ }
+
+ ksmbd_debug(SMB, "tree connect request for tree %s treename %s\n",
+ name, treename);
+
+ status = ksmbd_tree_conn_connect(sess, name);
+ if (status.ret == KSMBD_TREE_CONN_STATUS_OK)
+ rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
+ else
+ goto out_err1;
+
+ share = status.tree_conn->share_conf;
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
+ ksmbd_debug(SMB, "IPC share path request\n");
+ rsp->ShareType = SMB2_SHARE_TYPE_PIPE;
+ rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
+ FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE |
+ FILE_DELETE_LE | FILE_READ_CONTROL_LE |
+ FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
+ FILE_SYNCHRONIZE_LE;
+ } else {
+ rsp->ShareType = SMB2_SHARE_TYPE_DISK;
+ rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
+ FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE;
+ if (test_tree_conn_flag(status.tree_conn,
+ KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ rsp->MaximalAccess |= FILE_WRITE_DATA_LE |
+ FILE_APPEND_DATA_LE | FILE_WRITE_EA_LE |
+ FILE_DELETE_LE | FILE_WRITE_ATTRIBUTES_LE |
+ FILE_DELETE_CHILD_LE | FILE_READ_CONTROL_LE |
+ FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
+ FILE_SYNCHRONIZE_LE;
+ }
+ }
+
+ status.tree_conn->maximal_access = le32_to_cpu(rsp->MaximalAccess);
+ if (conn->posix_ext_supported)
+ status.tree_conn->posix_extensions = true;
+
+out_err1:
+ rsp->StructureSize = cpu_to_le16(16);
+ rsp->Capabilities = 0;
+ rsp->Reserved = 0;
+ /* default manual caching */
+ rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING;
+ inc_rfc1001_len(rsp, 16);
+
+ if (!IS_ERR(treename))
+ kfree(treename);
+ if (!IS_ERR(name))
+ kfree(name);
+
+ switch (status.ret) {
+ case KSMBD_TREE_CONN_STATUS_OK:
+ rsp->hdr.Status = STATUS_SUCCESS;
+ rc = 0;
+ break;
+ case KSMBD_TREE_CONN_STATUS_NO_SHARE:
+ rsp->hdr.Status = STATUS_BAD_NETWORK_PATH;
+ break;
+ case -ENOMEM:
+ case KSMBD_TREE_CONN_STATUS_NOMEM:
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ break;
+ case KSMBD_TREE_CONN_STATUS_ERROR:
+ case KSMBD_TREE_CONN_STATUS_TOO_MANY_CONNS:
+ case KSMBD_TREE_CONN_STATUS_TOO_MANY_SESSIONS:
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ break;
+ case -EINVAL:
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ break;
+ default:
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ }
+
+ return rc;
+}
+
+/**
+ * smb2_create_open_flags() - convert smb open flags to unix open flags
+ * @file_present: is file already present
+ * @access: file access flags
+ * @disposition: file disposition flags
+ * @may_flags: set with MAY_ flags
+ *
+ * Return: file open flags
+ */
+static int smb2_create_open_flags(bool file_present, __le32 access,
+ __le32 disposition,
+ int *may_flags)
+{
+ int oflags = O_NONBLOCK | O_LARGEFILE;
+
+ if (access & FILE_READ_DESIRED_ACCESS_LE &&
+ access & FILE_WRITE_DESIRE_ACCESS_LE) {
+ oflags |= O_RDWR;
+ *may_flags = MAY_OPEN | MAY_READ | MAY_WRITE;
+ } else if (access & FILE_WRITE_DESIRE_ACCESS_LE) {
+ oflags |= O_WRONLY;
+ *may_flags = MAY_OPEN | MAY_WRITE;
+ } else {
+ oflags |= O_RDONLY;
+ *may_flags = MAY_OPEN | MAY_READ;
+ }
+
+ if (access == FILE_READ_ATTRIBUTES_LE)
+ oflags |= O_PATH;
+
+ if (file_present) {
+ switch (disposition & FILE_CREATE_MASK_LE) {
+ case FILE_OPEN_LE:
+ case FILE_CREATE_LE:
+ break;
+ case FILE_SUPERSEDE_LE:
+ case FILE_OVERWRITE_LE:
+ case FILE_OVERWRITE_IF_LE:
+ oflags |= O_TRUNC;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (disposition & FILE_CREATE_MASK_LE) {
+ case FILE_SUPERSEDE_LE:
+ case FILE_CREATE_LE:
+ case FILE_OPEN_IF_LE:
+ case FILE_OVERWRITE_IF_LE:
+ oflags |= O_CREAT;
+ break;
+ case FILE_OPEN_LE:
+ case FILE_OVERWRITE_LE:
+ oflags &= ~O_CREAT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return oflags;
+}
+
+/**
+ * smb2_tree_disconnect() - handler for smb tree connect request
+ * @work: smb work containing request buffer
+ *
+ * Return: 0
+ */
+int smb2_tree_disconnect(struct ksmbd_work *work)
+{
+ struct smb2_tree_disconnect_rsp *rsp = work->response_buf;
+ struct ksmbd_session *sess = work->sess;
+ struct ksmbd_tree_connect *tcon = work->tcon;
+
+ rsp->StructureSize = cpu_to_le16(4);
+ inc_rfc1001_len(rsp, 4);
+
+ ksmbd_debug(SMB, "request\n");
+
+ if (!tcon) {
+ struct smb2_tree_disconnect_req *req = work->request_buf;
+
+ ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
+ rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ ksmbd_close_tree_conn_fds(work);
+ ksmbd_tree_conn_disconnect(sess, tcon);
+ return 0;
+}
+
+/**
+ * smb2_session_logoff() - handler for session log off request
+ * @work: smb work containing request buffer
+ *
+ * Return: 0
+ */
+int smb2_session_logoff(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_logoff_rsp *rsp = work->response_buf;
+ struct ksmbd_session *sess = work->sess;
+
+ rsp->StructureSize = cpu_to_le16(4);
+ inc_rfc1001_len(rsp, 4);
+
+ ksmbd_debug(SMB, "request\n");
+
+ /* Got a valid session, set connection state */
+ WARN_ON(sess->conn != conn);
+
+ /* setting CifsExiting here may race with start_tcp_sess */
+ ksmbd_conn_set_need_reconnect(work);
+ ksmbd_close_session_fds(work);
+ ksmbd_conn_wait_idle(conn);
+
+ if (ksmbd_tree_conn_session_logoff(sess)) {
+ struct smb2_logoff_req *req = work->request_buf;
+
+ ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
+ rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ ksmbd_destroy_file_table(&sess->file_table);
+ sess->state = SMB2_SESSION_EXPIRED;
+
+ ksmbd_free_user(sess->user);
+ sess->user = NULL;
+
+ /* let start_tcp_sess free connection info now */
+ ksmbd_conn_set_need_negotiate(work);
+ return 0;
+}
+
+/**
+ * create_smb2_pipe() - create IPC pipe
+ * @work: smb work containing request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+static noinline int create_smb2_pipe(struct ksmbd_work *work)
+{
+ struct smb2_create_rsp *rsp = work->response_buf;
+ struct smb2_create_req *req = work->request_buf;
+ int id;
+ int err;
+ char *name;
+
+ name = smb_strndup_from_utf16(req->Buffer, le16_to_cpu(req->NameLength),
+ 1, work->conn->local_nls);
+ if (IS_ERR(name)) {
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ err = PTR_ERR(name);
+ goto out;
+ }
+
+ id = ksmbd_session_rpc_open(work->sess, name);
+ if (id < 0) {
+ pr_err("Unable to open RPC pipe: %d\n", id);
+ err = id;
+ goto out;
+ }
+
+ rsp->hdr.Status = STATUS_SUCCESS;
+ rsp->StructureSize = cpu_to_le16(89);
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ rsp->Reserved = 0;
+ rsp->CreateAction = cpu_to_le32(FILE_OPENED);
+
+ rsp->CreationTime = cpu_to_le64(0);
+ rsp->LastAccessTime = cpu_to_le64(0);
+ rsp->ChangeTime = cpu_to_le64(0);
+ rsp->AllocationSize = cpu_to_le64(0);
+ rsp->EndofFile = cpu_to_le64(0);
+ rsp->FileAttributes = ATTR_NORMAL_LE;
+ rsp->Reserved2 = 0;
+ rsp->VolatileFileId = cpu_to_le64(id);
+ rsp->PersistentFileId = 0;
+ rsp->CreateContextsOffset = 0;
+ rsp->CreateContextsLength = 0;
+
+ inc_rfc1001_len(rsp, 88); /* StructureSize - 1*/
+ kfree(name);
+ return 0;
+
+out:
+ switch (err) {
+ case -EINVAL:
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ break;
+ case -ENOSPC:
+ case -ENOMEM:
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ break;
+ }
+
+ if (!IS_ERR(name))
+ kfree(name);
+
+ smb2_set_err_rsp(work);
+ return err;
+}
+
+/**
+ * smb2_set_ea() - handler for setting extended attributes using set
+ * info command
+ * @eabuf: set info command buffer
+ * @path: dentry path for get ea
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int smb2_set_ea(struct smb2_ea_info *eabuf, struct path *path)
+{
+ struct user_namespace *user_ns = mnt_user_ns(path->mnt);
+ char *attr_name = NULL, *value;
+ int rc = 0;
+ int next = 0;
+
+ attr_name = kmalloc(XATTR_NAME_MAX + 1, GFP_KERNEL);
+ if (!attr_name)
+ return -ENOMEM;
+
+ do {
+ if (!eabuf->EaNameLength)
+ goto next;
+
+ ksmbd_debug(SMB,
+ "name : <%s>, name_len : %u, value_len : %u, next : %u\n",
+ eabuf->name, eabuf->EaNameLength,
+ le16_to_cpu(eabuf->EaValueLength),
+ le32_to_cpu(eabuf->NextEntryOffset));
+
+ if (eabuf->EaNameLength >
+ (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) {
+ rc = -EINVAL;
+ break;
+ }
+
+ memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
+ memcpy(&attr_name[XATTR_USER_PREFIX_LEN], eabuf->name,
+ eabuf->EaNameLength);
+ attr_name[XATTR_USER_PREFIX_LEN + eabuf->EaNameLength] = '\0';
+ value = (char *)&eabuf->name + eabuf->EaNameLength + 1;
+
+ if (!eabuf->EaValueLength) {
+ rc = ksmbd_vfs_casexattr_len(user_ns,
+ path->dentry,
+ attr_name,
+ XATTR_USER_PREFIX_LEN +
+ eabuf->EaNameLength);
+
+ /* delete the EA only when it exits */
+ if (rc > 0) {
+ rc = ksmbd_vfs_remove_xattr(user_ns,
+ path->dentry,
+ attr_name);
+
+ if (rc < 0) {
+ ksmbd_debug(SMB,
+ "remove xattr failed(%d)\n",
+ rc);
+ break;
+ }
+ }
+
+ /* if the EA doesn't exist, just do nothing. */
+ rc = 0;
+ } else {
+ rc = ksmbd_vfs_setxattr(user_ns,
+ path->dentry, attr_name, value,
+ le16_to_cpu(eabuf->EaValueLength), 0);
+ if (rc < 0) {
+ ksmbd_debug(SMB,
+ "ksmbd_vfs_setxattr is failed(%d)\n",
+ rc);
+ break;
+ }
+ }
+
+next:
+ next = le32_to_cpu(eabuf->NextEntryOffset);
+ eabuf = (struct smb2_ea_info *)((char *)eabuf + next);
+ } while (next != 0);
+
+ kfree(attr_name);
+ return rc;
+}
+
+static noinline int smb2_set_stream_name_xattr(struct path *path,
+ struct ksmbd_file *fp,
+ char *stream_name, int s_type)
+{
+ struct user_namespace *user_ns = mnt_user_ns(path->mnt);
+ size_t xattr_stream_size;
+ char *xattr_stream_name;
+ int rc;
+
+ rc = ksmbd_vfs_xattr_stream_name(stream_name,
+ &xattr_stream_name,
+ &xattr_stream_size,
+ s_type);
+ if (rc)
+ return rc;
+
+ fp->stream.name = xattr_stream_name;
+ fp->stream.size = xattr_stream_size;
+
+ /* Check if there is stream prefix in xattr space */
+ rc = ksmbd_vfs_casexattr_len(user_ns,
+ path->dentry,
+ xattr_stream_name,
+ xattr_stream_size);
+ if (rc >= 0)
+ return 0;
+
+ if (fp->cdoption == FILE_OPEN_LE) {
+ ksmbd_debug(SMB, "XATTR stream name lookup failed: %d\n", rc);
+ return -EBADF;
+ }
+
+ rc = ksmbd_vfs_setxattr(user_ns, path->dentry,
+ xattr_stream_name, NULL, 0, 0);
+ if (rc < 0)
+ pr_err("Failed to store XATTR stream name :%d\n", rc);
+ return 0;
+}
+
+static int smb2_remove_smb_xattrs(struct path *path)
+{
+ struct user_namespace *user_ns = mnt_user_ns(path->mnt);
+ char *name, *xattr_list = NULL;
+ ssize_t xattr_list_len;
+ int err = 0;
+
+ xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+ if (xattr_list_len < 0) {
+ goto out;
+ } else if (!xattr_list_len) {
+ ksmbd_debug(SMB, "empty xattr in the file\n");
+ goto out;
+ }
+
+ for (name = xattr_list; name - xattr_list < xattr_list_len;
+ name += strlen(name) + 1) {
+ ksmbd_debug(SMB, "%s, len %zd\n", name, strlen(name));
+
+ if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
+ strncmp(&name[XATTR_USER_PREFIX_LEN], DOS_ATTRIBUTE_PREFIX,
+ DOS_ATTRIBUTE_PREFIX_LEN) &&
+ strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX, STREAM_PREFIX_LEN))
+ continue;
+
+ err = ksmbd_vfs_remove_xattr(user_ns, path->dentry, name);
+ if (err)
+ ksmbd_debug(SMB, "remove xattr failed : %s\n", name);
+ }
+out:
+ kvfree(xattr_list);
+ return err;
+}
+
+static int smb2_create_truncate(struct path *path)
+{
+ int rc = vfs_truncate(path, 0);
+
+ if (rc) {
+ pr_err("vfs_truncate failed, rc %d\n", rc);
+ return rc;
+ }
+
+ rc = smb2_remove_smb_xattrs(path);
+ if (rc == -EOPNOTSUPP)
+ rc = 0;
+ if (rc)
+ ksmbd_debug(SMB,
+ "ksmbd_truncate_stream_name_xattr failed, rc %d\n",
+ rc);
+ return rc;
+}
+
+static void smb2_new_xattrs(struct ksmbd_tree_connect *tcon, struct path *path,
+ struct ksmbd_file *fp)
+{
+ struct xattr_dos_attrib da = {0};
+ int rc;
+
+ if (!test_share_config_flag(tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS))
+ return;
+
+ da.version = 4;
+ da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+ da.itime = da.create_time = fp->create_time;
+ da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
+ XATTR_DOSINFO_ITIME;
+
+ rc = ksmbd_vfs_set_dos_attrib_xattr(mnt_user_ns(path->mnt),
+ path->dentry, &da);
+ if (rc)
+ ksmbd_debug(SMB, "failed to store file attribute into xattr\n");
+}
+
+static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
+ struct path *path, struct ksmbd_file *fp)
+{
+ struct xattr_dos_attrib da;
+ int rc;
+
+ fp->f_ci->m_fattr &= ~(ATTR_HIDDEN_LE | ATTR_SYSTEM_LE);
+
+ /* get FileAttributes from XATTR_NAME_DOS_ATTRIBUTE */
+ if (!test_share_config_flag(tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS))
+ return;
+
+ rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_user_ns(path->mnt),
+ path->dentry, &da);
+ if (rc > 0) {
+ fp->f_ci->m_fattr = cpu_to_le32(da.attr);
+ fp->create_time = da.create_time;
+ fp->itime = da.itime;
+ }
+}
+
+static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
+ int open_flags, umode_t posix_mode, bool is_dir)
+{
+ struct ksmbd_tree_connect *tcon = work->tcon;
+ struct ksmbd_share_config *share = tcon->share_conf;
+ umode_t mode;
+ int rc;
+
+ if (!(open_flags & O_CREAT))
+ return -EBADF;
+
+ ksmbd_debug(SMB, "file does not exist, so creating\n");
+ if (is_dir == true) {
+ ksmbd_debug(SMB, "creating directory\n");
+
+ mode = share_config_directory_mode(share, posix_mode);
+ rc = ksmbd_vfs_mkdir(work, name, mode);
+ if (rc)
+ return rc;
+ } else {
+ ksmbd_debug(SMB, "creating regular file\n");
+
+ mode = share_config_create_mode(share, posix_mode);
+ rc = ksmbd_vfs_create(work, name, mode);
+ if (rc)
+ return rc;
+ }
+
+ rc = ksmbd_vfs_kern_path(name, 0, path, 0);
+ if (rc) {
+ pr_err("cannot get linux path (%s), err = %d\n",
+ name, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int smb2_create_sd_buffer(struct ksmbd_work *work,
+ struct smb2_create_req *req,
+ struct path *path)
+{
+ struct create_context *context;
+ struct create_sd_buf_req *sd_buf;
+
+ if (!req->CreateContextsOffset)
+ return -ENOENT;
+
+ /* Parse SD BUFFER create contexts */
+ context = smb2_find_context_vals(req, SMB2_CREATE_SD_BUFFER);
+ if (!context)
+ return -ENOENT;
+ else if (IS_ERR(context))
+ return PTR_ERR(context);
+
+ ksmbd_debug(SMB,
+ "Set ACLs using SMB2_CREATE_SD_BUFFER context\n");
+ sd_buf = (struct create_sd_buf_req *)context;
+ return set_info_sec(work->conn, work->tcon, path, &sd_buf->ntsd,
+ le32_to_cpu(sd_buf->ccontext.DataLength), true);
+}
+
+static void ksmbd_acls_fattr(struct smb_fattr *fattr, struct inode *inode)
+{
+ fattr->cf_uid = inode->i_uid;
+ fattr->cf_gid = inode->i_gid;
+ fattr->cf_mode = inode->i_mode;
+ fattr->cf_acls = NULL;
+ fattr->cf_dacls = NULL;
+
+ if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
+ fattr->cf_acls = get_acl(inode, ACL_TYPE_ACCESS);
+ if (S_ISDIR(inode->i_mode))
+ fattr->cf_dacls = get_acl(inode, ACL_TYPE_DEFAULT);
+ }
+}
+
+/**
+ * smb2_open() - handler for smb file open request
+ * @work: smb work containing request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_open(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ struct ksmbd_tree_connect *tcon = work->tcon;
+ struct smb2_create_req *req;
+ struct smb2_create_rsp *rsp, *rsp_org;
+ struct path path;
+ struct ksmbd_share_config *share = tcon->share_conf;
+ struct ksmbd_file *fp = NULL;
+ struct file *filp = NULL;
+ struct user_namespace *user_ns = NULL;
+ struct kstat stat;
+ struct create_context *context;
+ struct lease_ctx_info *lc = NULL;
+ struct create_ea_buf_req *ea_buf = NULL;
+ struct oplock_info *opinfo;
+ __le32 *next_ptr = NULL;
+ int req_op_level = 0, open_flags = 0, may_flags = 0, file_info = 0;
+ int rc = 0, len = 0;
+ int contxt_cnt = 0, query_disk_id = 0;
+ int maximal_access_ctxt = 0, posix_ctxt = 0;
+ int s_type = 0;
+ int next_off = 0;
+ char *name = NULL;
+ char *stream_name = NULL;
+ bool file_present = false, created = false, already_permitted = false;
+ int share_ret, need_truncate = 0;
+ u64 time;
+ umode_t posix_mode = 0;
+ __le32 daccess, maximal_access = 0;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ if (req->hdr.NextCommand && !work->next_smb2_rcv_hdr_off &&
+ (req->hdr.Flags & SMB2_FLAGS_RELATED_OPERATIONS)) {
+ ksmbd_debug(SMB, "invalid flag in chained command\n");
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ smb2_set_err_rsp(work);
+ return -EINVAL;
+ }
+
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
+ ksmbd_debug(SMB, "IPC pipe create request\n");
+ return create_smb2_pipe(work);
+ }
+
+ if (req->NameLength) {
+ if ((req->CreateOptions & FILE_DIRECTORY_FILE_LE) &&
+ *(char *)req->Buffer == '\\') {
+ pr_err("not allow directory name included leading slash\n");
+ rc = -EINVAL;
+ goto err_out1;
+ }
+
+ name = smb2_get_name(share,
+ req->Buffer,
+ le16_to_cpu(req->NameLength),
+ work->conn->local_nls);
+ if (IS_ERR(name)) {
+ rc = PTR_ERR(name);
+ if (rc != -ENOMEM)
+ rc = -ENOENT;
+ name = NULL;
+ goto err_out1;
+ }
+
+ ksmbd_debug(SMB, "converted name = %s\n", name);
+ if (strchr(name, ':')) {
+ if (!test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_STREAMS)) {
+ rc = -EBADF;
+ goto err_out1;
+ }
+ rc = parse_stream_name(name, &stream_name, &s_type);
+ if (rc < 0)
+ goto err_out1;
+ }
+
+ rc = ksmbd_validate_filename(name);
+ if (rc < 0)
+ goto err_out1;
+
+ if (ksmbd_share_veto_filename(share, name)) {
+ rc = -ENOENT;
+ ksmbd_debug(SMB, "Reject open(), vetoed file: %s\n",
+ name);
+ goto err_out1;
+ }
+ } else {
+ len = strlen(share->path);
+ ksmbd_debug(SMB, "share path len %d\n", len);
+ name = kmalloc(len + 1, GFP_KERNEL);
+ if (!name) {
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ rc = -ENOMEM;
+ goto err_out1;
+ }
+
+ memcpy(name, share->path, len);
+ *(name + len) = '\0';
+ }
+
+ req_op_level = req->RequestedOplockLevel;
+ if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
+ lc = parse_lease_state(req);
+
+ if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE_LE)) {
+ pr_err("Invalid impersonationlevel : 0x%x\n",
+ le32_to_cpu(req->ImpersonationLevel));
+ rc = -EIO;
+ rsp->hdr.Status = STATUS_BAD_IMPERSONATION_LEVEL;
+ goto err_out1;
+ }
+
+ if (req->CreateOptions && !(req->CreateOptions & CREATE_OPTIONS_MASK)) {
+ pr_err("Invalid create options : 0x%x\n",
+ le32_to_cpu(req->CreateOptions));
+ rc = -EINVAL;
+ goto err_out1;
+ } else {
+ if (req->CreateOptions & FILE_SEQUENTIAL_ONLY_LE &&
+ req->CreateOptions & FILE_RANDOM_ACCESS_LE)
+ req->CreateOptions = ~(FILE_SEQUENTIAL_ONLY_LE);
+
+ if (req->CreateOptions &
+ (FILE_OPEN_BY_FILE_ID_LE | CREATE_TREE_CONNECTION |
+ FILE_RESERVE_OPFILTER_LE)) {
+ rc = -EOPNOTSUPP;
+ goto err_out1;
+ }
+
+ if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
+ if (req->CreateOptions & FILE_NON_DIRECTORY_FILE_LE) {
+ rc = -EINVAL;
+ goto err_out1;
+ } else if (req->CreateOptions & FILE_NO_COMPRESSION_LE) {
+ req->CreateOptions = ~(FILE_NO_COMPRESSION_LE);
+ }
+ }
+ }
+
+ if (le32_to_cpu(req->CreateDisposition) >
+ le32_to_cpu(FILE_OVERWRITE_IF_LE)) {
+ pr_err("Invalid create disposition : 0x%x\n",
+ le32_to_cpu(req->CreateDisposition));
+ rc = -EINVAL;
+ goto err_out1;
+ }
+
+ if (!(req->DesiredAccess & DESIRED_ACCESS_MASK)) {
+ pr_err("Invalid desired access : 0x%x\n",
+ le32_to_cpu(req->DesiredAccess));
+ rc = -EACCES;
+ goto err_out1;
+ }
+
+ if (req->FileAttributes && !(req->FileAttributes & ATTR_MASK_LE)) {
+ pr_err("Invalid file attribute : 0x%x\n",
+ le32_to_cpu(req->FileAttributes));
+ rc = -EINVAL;
+ goto err_out1;
+ }
+
+ if (req->CreateContextsOffset) {
+ /* Parse non-durable handle create contexts */
+ context = smb2_find_context_vals(req, SMB2_CREATE_EA_BUFFER);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ ea_buf = (struct create_ea_buf_req *)context;
+ if (req->CreateOptions & FILE_NO_EA_KNOWLEDGE_LE) {
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ rc = -EACCES;
+ goto err_out1;
+ }
+ }
+
+ context = smb2_find_context_vals(req,
+ SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ ksmbd_debug(SMB,
+ "get query maximal access context\n");
+ maximal_access_ctxt = 1;
+ }
+
+ context = smb2_find_context_vals(req,
+ SMB2_CREATE_TIMEWARP_REQUEST);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ ksmbd_debug(SMB, "get timewarp context\n");
+ rc = -EBADF;
+ goto err_out1;
+ }
+
+ if (tcon->posix_extensions) {
+ context = smb2_find_context_vals(req,
+ SMB2_CREATE_TAG_POSIX);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ struct create_posix *posix =
+ (struct create_posix *)context;
+ ksmbd_debug(SMB, "get posix context\n");
+
+ posix_mode = le32_to_cpu(posix->Mode);
+ posix_ctxt = 1;
+ }
+ }
+ }
+
+ if (ksmbd_override_fsids(work)) {
+ rc = -ENOMEM;
+ goto err_out1;
+ }
+
+ if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
+ /*
+ * On delete request, instead of following up, need to
+ * look the current entity
+ */
+ rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
+ if (!rc) {
+ /*
+ * If file exists with under flags, return access
+ * denied error.
+ */
+ if (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
+ req->CreateDisposition == FILE_OPEN_IF_LE) {
+ rc = -EACCES;
+ path_put(&path);
+ goto err_out;
+ }
+
+ if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ rc = -EACCES;
+ path_put(&path);
+ goto err_out;
+ }
+ }
+ } else {
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS)) {
+ /*
+ * Use LOOKUP_FOLLOW to follow the path of
+ * symlink in path buildup
+ */
+ rc = ksmbd_vfs_kern_path(name, LOOKUP_FOLLOW, &path, 1);
+ if (rc) { /* Case for broken link ?*/
+ rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
+ }
+ } else {
+ rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
+ if (!rc && d_is_symlink(path.dentry)) {
+ rc = -EACCES;
+ path_put(&path);
+ goto err_out;
+ }
+ }
+ }
+
+ if (rc) {
+ if (rc == -EACCES) {
+ ksmbd_debug(SMB,
+ "User does not have right permission\n");
+ goto err_out;
+ }
+ ksmbd_debug(SMB, "can not get linux path for %s, rc = %d\n",
+ name, rc);
+ rc = 0;
+ } else {
+ file_present = true;
+ user_ns = mnt_user_ns(path.mnt);
+ generic_fillattr(user_ns, d_inode(path.dentry), &stat);
+ }
+ if (stream_name) {
+ if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
+ if (s_type == DATA_STREAM) {
+ rc = -EIO;
+ rsp->hdr.Status = STATUS_NOT_A_DIRECTORY;
+ }
+ } else {
+ if (S_ISDIR(stat.mode) && s_type == DATA_STREAM) {
+ rc = -EIO;
+ rsp->hdr.Status = STATUS_FILE_IS_A_DIRECTORY;
+ }
+ }
+
+ if (req->CreateOptions & FILE_DIRECTORY_FILE_LE &&
+ req->FileAttributes & ATTR_NORMAL_LE) {
+ rsp->hdr.Status = STATUS_NOT_A_DIRECTORY;
+ rc = -EIO;
+ }
+
+ if (rc < 0)
+ goto err_out;
+ }
+
+ if (file_present && req->CreateOptions & FILE_NON_DIRECTORY_FILE_LE &&
+ S_ISDIR(stat.mode) && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
+ ksmbd_debug(SMB, "open() argument is a directory: %s, %x\n",
+ name, req->CreateOptions);
+ rsp->hdr.Status = STATUS_FILE_IS_A_DIRECTORY;
+ rc = -EIO;
+ goto err_out;
+ }
+
+ if (file_present && (req->CreateOptions & FILE_DIRECTORY_FILE_LE) &&
+ !(req->CreateDisposition == FILE_CREATE_LE) &&
+ !S_ISDIR(stat.mode)) {
+ rsp->hdr.Status = STATUS_NOT_A_DIRECTORY;
+ rc = -EIO;
+ goto err_out;
+ }
+
+ if (!stream_name && file_present &&
+ req->CreateDisposition == FILE_CREATE_LE) {
+ rc = -EEXIST;
+ goto err_out;
+ }
+
+ daccess = smb_map_generic_desired_access(req->DesiredAccess);
+
+ if (file_present && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
+ rc = smb_check_perm_dacl(conn, &path, &daccess,
+ sess->user->uid);
+ if (rc)
+ goto err_out;
+ }
+
+ if (daccess & FILE_MAXIMAL_ACCESS_LE) {
+ if (!file_present) {
+ daccess = cpu_to_le32(GENERIC_ALL_FLAGS);
+ } else {
+ rc = ksmbd_vfs_query_maximal_access(user_ns,
+ path.dentry,
+ &daccess);
+ if (rc)
+ goto err_out;
+ already_permitted = true;
+ }
+ maximal_access = daccess;
+ }
+
+ open_flags = smb2_create_open_flags(file_present, daccess,
+ req->CreateDisposition,
+ &may_flags);
+
+ if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ if (open_flags & O_CREAT) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ rc = -EACCES;
+ goto err_out;
+ }
+ }
+
+ /*create file if not present */
+ if (!file_present) {
+ rc = smb2_creat(work, &path, name, open_flags, posix_mode,
+ req->CreateOptions & FILE_DIRECTORY_FILE_LE);
+ if (rc) {
+ if (rc == -ENOENT) {
+ rc = -EIO;
+ rsp->hdr.Status = STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ goto err_out;
+ }
+
+ created = true;
+ user_ns = mnt_user_ns(path.mnt);
+ if (ea_buf) {
+ rc = smb2_set_ea(&ea_buf->ea, &path);
+ if (rc == -EOPNOTSUPP)
+ rc = 0;
+ else if (rc)
+ goto err_out;
+ }
+ } else if (!already_permitted) {
+ /* FILE_READ_ATTRIBUTE is allowed without inode_permission,
+ * because execute(search) permission on a parent directory,
+ * is already granted.
+ */
+ if (daccess & ~(FILE_READ_ATTRIBUTES_LE | FILE_READ_CONTROL_LE)) {
+ rc = inode_permission(user_ns,
+ d_inode(path.dentry),
+ may_flags);
+ if (rc)
+ goto err_out;
+
+ if ((daccess & FILE_DELETE_LE) ||
+ (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
+ rc = ksmbd_vfs_may_delete(user_ns,
+ path.dentry);
+ if (rc)
+ goto err_out;
+ }
+ }
+ }
+
+ rc = ksmbd_query_inode_status(d_inode(path.dentry->d_parent));
+ if (rc == KSMBD_INODE_STATUS_PENDING_DELETE) {
+ rc = -EBUSY;
+ goto err_out;
+ }
+
+ rc = 0;
+ filp = dentry_open(&path, open_flags, current_cred());
+ if (IS_ERR(filp)) {
+ rc = PTR_ERR(filp);
+ pr_err("dentry open for dir failed, rc %d\n", rc);
+ goto err_out;
+ }
+
+ if (file_present) {
+ if (!(open_flags & O_TRUNC))
+ file_info = FILE_OPENED;
+ else
+ file_info = FILE_OVERWRITTEN;
+
+ if ((req->CreateDisposition & FILE_CREATE_MASK_LE) ==
+ FILE_SUPERSEDE_LE)
+ file_info = FILE_SUPERSEDED;
+ } else if (open_flags & O_CREAT) {
+ file_info = FILE_CREATED;
+ }
+
+ ksmbd_vfs_set_fadvise(filp, req->CreateOptions);
+
+ /* Obtain Volatile-ID */
+ fp = ksmbd_open_fd(work, filp);
+ if (IS_ERR(fp)) {
+ fput(filp);
+ rc = PTR_ERR(fp);
+ fp = NULL;
+ goto err_out;
+ }
+
+ /* Get Persistent-ID */
+ ksmbd_open_durable_fd(fp);
+ if (!has_file_id(fp->persistent_id)) {
+ rc = -ENOMEM;
+ goto err_out;
+ }
+
+ fp->filename = name;
+ fp->cdoption = req->CreateDisposition;
+ fp->daccess = daccess;
+ fp->saccess = req->ShareAccess;
+ fp->coption = req->CreateOptions;
+
+ /* Set default windows and posix acls if creating new file */
+ if (created) {
+ int posix_acl_rc;
+ struct inode *inode = d_inode(path.dentry);
+
+ posix_acl_rc = ksmbd_vfs_inherit_posix_acl(user_ns,
+ inode,
+ d_inode(path.dentry->d_parent));
+ if (posix_acl_rc)
+ ksmbd_debug(SMB, "inherit posix acl failed : %d\n", posix_acl_rc);
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ACL_XATTR)) {
+ rc = smb_inherit_dacl(conn, &path, sess->user->uid,
+ sess->user->gid);
+ }
+
+ if (rc) {
+ rc = smb2_create_sd_buffer(work, req, &path);
+ if (rc) {
+ if (posix_acl_rc)
+ ksmbd_vfs_set_init_posix_acl(user_ns,
+ inode);
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ACL_XATTR)) {
+ struct smb_fattr fattr;
+ struct smb_ntsd *pntsd;
+ int pntsd_size, ace_num = 0;
+
+ ksmbd_acls_fattr(&fattr, inode);
+ if (fattr.cf_acls)
+ ace_num = fattr.cf_acls->a_count;
+ if (fattr.cf_dacls)
+ ace_num += fattr.cf_dacls->a_count;
+
+ pntsd = kmalloc(sizeof(struct smb_ntsd) +
+ sizeof(struct smb_sid) * 3 +
+ sizeof(struct smb_acl) +
+ sizeof(struct smb_ace) * ace_num * 2,
+ GFP_KERNEL);
+ if (!pntsd)
+ goto err_out;
+
+ rc = build_sec_desc(user_ns,
+ pntsd, NULL,
+ OWNER_SECINFO |
+ GROUP_SECINFO |
+ DACL_SECINFO,
+ &pntsd_size, &fattr);
+ posix_acl_release(fattr.cf_acls);
+ posix_acl_release(fattr.cf_dacls);
+
+ rc = ksmbd_vfs_set_sd_xattr(conn,
+ user_ns,
+ path.dentry,
+ pntsd,
+ pntsd_size);
+ kfree(pntsd);
+ if (rc)
+ pr_err("failed to store ntacl in xattr : %d\n",
+ rc);
+ }
+ }
+ }
+ rc = 0;
+ }
+
+ if (stream_name) {
+ rc = smb2_set_stream_name_xattr(&path,
+ fp,
+ stream_name,
+ s_type);
+ if (rc)
+ goto err_out;
+ file_info = FILE_CREATED;
+ }
+
+ fp->attrib_only = !(req->DesiredAccess & ~(FILE_READ_ATTRIBUTES_LE |
+ FILE_WRITE_ATTRIBUTES_LE | FILE_SYNCHRONIZE_LE));
+ if (!S_ISDIR(file_inode(filp)->i_mode) && open_flags & O_TRUNC &&
+ !fp->attrib_only && !stream_name) {
+ smb_break_all_oplock(work, fp);
+ need_truncate = 1;
+ }
+
+ /* fp should be searchable through ksmbd_inode.m_fp_list
+ * after daccess, saccess, attrib_only, and stream are
+ * initialized.
+ */
+ write_lock(&fp->f_ci->m_lock);
+ list_add(&fp->node, &fp->f_ci->m_fp_list);
+ write_unlock(&fp->f_ci->m_lock);
+
+ rc = ksmbd_vfs_getattr(&path, &stat);
+ if (rc) {
+ generic_fillattr(user_ns, d_inode(path.dentry), &stat);
+ rc = 0;
+ }
+
+ /* Check delete pending among previous fp before oplock break */
+ if (ksmbd_inode_pending_delete(fp)) {
+ rc = -EBUSY;
+ goto err_out;
+ }
+
+ share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
+ if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) ||
+ (req_op_level == SMB2_OPLOCK_LEVEL_LEASE &&
+ !(conn->vals->capabilities & SMB2_GLOBAL_CAP_LEASING))) {
+ if (share_ret < 0 && !S_ISDIR(file_inode(fp->filp)->i_mode)) {
+ rc = share_ret;
+ goto err_out;
+ }
+ } else {
+ if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
+ req_op_level = smb2_map_lease_to_oplock(lc->req_state);
+ ksmbd_debug(SMB,
+ "lease req for(%s) req oplock state 0x%x, lease state 0x%x\n",
+ name, req_op_level, lc->req_state);
+ rc = find_same_lease_key(sess, fp->f_ci, lc);
+ if (rc)
+ goto err_out;
+ } else if (open_flags == O_RDONLY &&
+ (req_op_level == SMB2_OPLOCK_LEVEL_BATCH ||
+ req_op_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE))
+ req_op_level = SMB2_OPLOCK_LEVEL_II;
+
+ rc = smb_grant_oplock(work, req_op_level,
+ fp->persistent_id, fp,
+ le32_to_cpu(req->hdr.Id.SyncId.TreeId),
+ lc, share_ret);
+ if (rc < 0)
+ goto err_out;
+ }
+
+ if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)
+ ksmbd_fd_set_delete_on_close(fp, file_info);
+
+ if (need_truncate) {
+ rc = smb2_create_truncate(&path);
+ if (rc)
+ goto err_out;
+ }
+
+ if (req->CreateContextsOffset) {
+ struct create_alloc_size_req *az_req;
+
+ az_req = (struct create_alloc_size_req *)smb2_find_context_vals(req,
+ SMB2_CREATE_ALLOCATION_SIZE);
+ if (IS_ERR(az_req)) {
+ rc = PTR_ERR(az_req);
+ goto err_out;
+ } else if (az_req) {
+ loff_t alloc_size = le64_to_cpu(az_req->AllocationSize);
+ int err;
+
+ ksmbd_debug(SMB,
+ "request smb2 create allocate size : %llu\n",
+ alloc_size);
+ smb_break_all_levII_oplock(work, fp, 1);
+ err = vfs_fallocate(fp->filp, FALLOC_FL_KEEP_SIZE, 0,
+ alloc_size);
+ if (err < 0)
+ ksmbd_debug(SMB,
+ "vfs_fallocate is failed : %d\n",
+ err);
+ }
+
+ context = smb2_find_context_vals(req, SMB2_CREATE_QUERY_ON_DISK_ID);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out;
+ } else if (context) {
+ ksmbd_debug(SMB, "get query on disk id context\n");
+ query_disk_id = 1;
+ }
+ }
+
+ if (stat.result_mask & STATX_BTIME)
+ fp->create_time = ksmbd_UnixTimeToNT(stat.btime);
+ else
+ fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
+ if (req->FileAttributes || fp->f_ci->m_fattr == 0)
+ fp->f_ci->m_fattr =
+ cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));
+
+ if (!created)
+ smb2_update_xattrs(tcon, &path, fp);
+ else
+ smb2_new_xattrs(tcon, &path, fp);
+
+ memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+
+ generic_fillattr(user_ns, file_inode(fp->filp),
+ &stat);
+
+ rsp->StructureSize = cpu_to_le16(89);
+ rcu_read_lock();
+ opinfo = rcu_dereference(fp->f_opinfo);
+ rsp->OplockLevel = opinfo != NULL ? opinfo->level : 0;
+ rcu_read_unlock();
+ rsp->Reserved = 0;
+ rsp->CreateAction = cpu_to_le32(file_info);
+ rsp->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(stat.atime);
+ rsp->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.mtime);
+ rsp->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.ctime);
+ rsp->ChangeTime = cpu_to_le64(time);
+ rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
+ cpu_to_le64(stat.blocks << 9);
+ rsp->EndofFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ rsp->FileAttributes = fp->f_ci->m_fattr;
+
+ rsp->Reserved2 = 0;
+
+ rsp->PersistentFileId = cpu_to_le64(fp->persistent_id);
+ rsp->VolatileFileId = cpu_to_le64(fp->volatile_id);
+
+ rsp->CreateContextsOffset = 0;
+ rsp->CreateContextsLength = 0;
+ inc_rfc1001_len(rsp_org, 88); /* StructureSize - 1*/
+
+ /* If lease is request send lease context response */
+ if (opinfo && opinfo->is_lease) {
+ struct create_context *lease_ccontext;
+
+ ksmbd_debug(SMB, "lease granted on(%s) lease state 0x%x\n",
+ name, opinfo->o_lease->state);
+ rsp->OplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
+
+ lease_ccontext = (struct create_context *)rsp->Buffer;
+ contxt_cnt++;
+ create_lease_buf(rsp->Buffer, opinfo->o_lease);
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_lease_size);
+ inc_rfc1001_len(rsp_org, conn->vals->create_lease_size);
+ next_ptr = &lease_ccontext->Next;
+ next_off = conn->vals->create_lease_size;
+ }
+
+ if (maximal_access_ctxt) {
+ struct create_context *mxac_ccontext;
+
+ if (maximal_access == 0)
+ ksmbd_vfs_query_maximal_access(user_ns,
+ path.dentry,
+ &maximal_access);
+ mxac_ccontext = (struct create_context *)(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength));
+ contxt_cnt++;
+ create_mxac_rsp_buf(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength),
+ le32_to_cpu(maximal_access));
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_mxac_size);
+ inc_rfc1001_len(rsp_org, conn->vals->create_mxac_size);
+ if (next_ptr)
+ *next_ptr = cpu_to_le32(next_off);
+ next_ptr = &mxac_ccontext->Next;
+ next_off = conn->vals->create_mxac_size;
+ }
+
+ if (query_disk_id) {
+ struct create_context *disk_id_ccontext;
+
+ disk_id_ccontext = (struct create_context *)(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength));
+ contxt_cnt++;
+ create_disk_id_rsp_buf(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength),
+ stat.ino, tcon->id);
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_disk_id_size);
+ inc_rfc1001_len(rsp_org, conn->vals->create_disk_id_size);
+ if (next_ptr)
+ *next_ptr = cpu_to_le32(next_off);
+ next_ptr = &disk_id_ccontext->Next;
+ next_off = conn->vals->create_disk_id_size;
+ }
+
+ if (posix_ctxt) {
+ contxt_cnt++;
+ create_posix_rsp_buf(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength),
+ fp);
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_posix_size);
+ inc_rfc1001_len(rsp_org, conn->vals->create_posix_size);
+ if (next_ptr)
+ *next_ptr = cpu_to_le32(next_off);
+ }
+
+ if (contxt_cnt > 0) {
+ rsp->CreateContextsOffset =
+ cpu_to_le32(offsetof(struct smb2_create_rsp, Buffer)
+ - 4);
+ }
+
+err_out:
+ if (file_present || created)
+ path_put(&path);
+ ksmbd_revert_fsids(work);
+err_out1:
+ if (rc) {
+ if (rc == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (rc == -EOPNOTSUPP)
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ else if (rc == -EACCES || rc == -ESTALE)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (rc == -ENOENT)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
+ else if (rc == -EPERM)
+ rsp->hdr.Status = STATUS_SHARING_VIOLATION;
+ else if (rc == -EBUSY)
+ rsp->hdr.Status = STATUS_DELETE_PENDING;
+ else if (rc == -EBADF)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ else if (rc == -ENOEXEC)
+ rsp->hdr.Status = STATUS_DUPLICATE_OBJECTID;
+ else if (rc == -ENXIO)
+ rsp->hdr.Status = STATUS_NO_SUCH_DEVICE;
+ else if (rc == -EEXIST)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
+ else if (rc == -EMFILE)
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ if (!rsp->hdr.Status)
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+
+ if (!fp || !fp->filename)
+ kfree(name);
+ if (fp)
+ ksmbd_fd_put(work, fp);
+ smb2_set_err_rsp(work);
+ ksmbd_debug(SMB, "Error response: %x\n", rsp->hdr.Status);
+ }
+
+ kfree(lc);
+
+ return 0;
+}
+
+static int readdir_info_level_struct_sz(int info_level)
+{
+ switch (info_level) {
+ case FILE_FULL_DIRECTORY_INFORMATION:
+ return sizeof(struct file_full_directory_info);
+ case FILE_BOTH_DIRECTORY_INFORMATION:
+ return sizeof(struct file_both_directory_info);
+ case FILE_DIRECTORY_INFORMATION:
+ return sizeof(struct file_directory_info);
+ case FILE_NAMES_INFORMATION:
+ return sizeof(struct file_names_info);
+ case FILEID_FULL_DIRECTORY_INFORMATION:
+ return sizeof(struct file_id_full_dir_info);
+ case FILEID_BOTH_DIRECTORY_INFORMATION:
+ return sizeof(struct file_id_both_directory_info);
+ case SMB_FIND_FILE_POSIX_INFO:
+ return sizeof(struct smb2_posix_info);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int dentry_name(struct ksmbd_dir_info *d_info, int info_level)
+{
+ switch (info_level) {
+ case FILE_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_full_directory_info *ffdinfo;
+
+ ffdinfo = (struct file_full_directory_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(ffdinfo->NextEntryOffset);
+ d_info->name = ffdinfo->FileName;
+ d_info->name_len = le32_to_cpu(ffdinfo->FileNameLength);
+ return 0;
+ }
+ case FILE_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_both_directory_info *fbdinfo;
+
+ fbdinfo = (struct file_both_directory_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(fbdinfo->NextEntryOffset);
+ d_info->name = fbdinfo->FileName;
+ d_info->name_len = le32_to_cpu(fbdinfo->FileNameLength);
+ return 0;
+ }
+ case FILE_DIRECTORY_INFORMATION:
+ {
+ struct file_directory_info *fdinfo;
+
+ fdinfo = (struct file_directory_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(fdinfo->NextEntryOffset);
+ d_info->name = fdinfo->FileName;
+ d_info->name_len = le32_to_cpu(fdinfo->FileNameLength);
+ return 0;
+ }
+ case FILE_NAMES_INFORMATION:
+ {
+ struct file_names_info *fninfo;
+
+ fninfo = (struct file_names_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(fninfo->NextEntryOffset);
+ d_info->name = fninfo->FileName;
+ d_info->name_len = le32_to_cpu(fninfo->FileNameLength);
+ return 0;
+ }
+ case FILEID_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_id_full_dir_info *dinfo;
+
+ dinfo = (struct file_id_full_dir_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(dinfo->NextEntryOffset);
+ d_info->name = dinfo->FileName;
+ d_info->name_len = le32_to_cpu(dinfo->FileNameLength);
+ return 0;
+ }
+ case FILEID_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_id_both_directory_info *fibdinfo;
+
+ fibdinfo = (struct file_id_both_directory_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(fibdinfo->NextEntryOffset);
+ d_info->name = fibdinfo->FileName;
+ d_info->name_len = le32_to_cpu(fibdinfo->FileNameLength);
+ return 0;
+ }
+ case SMB_FIND_FILE_POSIX_INFO:
+ {
+ struct smb2_posix_info *posix_info;
+
+ posix_info = (struct smb2_posix_info *)d_info->rptr;
+ d_info->rptr += le32_to_cpu(posix_info->NextEntryOffset);
+ d_info->name = posix_info->name;
+ d_info->name_len = le32_to_cpu(posix_info->name_len);
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * smb2_populate_readdir_entry() - encode directory entry in smb2 response
+ * buffer
+ * @conn: connection instance
+ * @info_level: smb information level
+ * @d_info: structure included variables for query dir
+ * @user_ns: user namespace
+ * @ksmbd_kstat: ksmbd wrapper of dirent stat information
+ *
+ * if directory has many entries, find first can't read it fully.
+ * find next might be called multiple times to read remaining dir entries
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
+ struct ksmbd_dir_info *d_info,
+ struct user_namespace *user_ns,
+ struct ksmbd_kstat *ksmbd_kstat)
+{
+ int next_entry_offset = 0;
+ char *conv_name;
+ int conv_len;
+ void *kstat;
+ int struct_sz, rc = 0;
+
+ conv_name = ksmbd_convert_dir_info_name(d_info,
+ conn->local_nls,
+ &conv_len);
+ if (!conv_name)
+ return -ENOMEM;
+
+ /* Somehow the name has only terminating NULL bytes */
+ if (conv_len < 0) {
+ rc = -EINVAL;
+ goto free_conv_name;
+ }
+
+ struct_sz = readdir_info_level_struct_sz(info_level);
+ next_entry_offset = ALIGN(struct_sz - 1 + conv_len,
+ KSMBD_DIR_INFO_ALIGNMENT);
+
+ if (next_entry_offset > d_info->out_buf_len) {
+ d_info->out_buf_len = 0;
+ rc = -ENOSPC;
+ goto free_conv_name;
+ }
+
+ kstat = d_info->wptr;
+ if (info_level != FILE_NAMES_INFORMATION)
+ kstat = ksmbd_vfs_init_kstat(&d_info->wptr, ksmbd_kstat);
+
+ switch (info_level) {
+ case FILE_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_full_directory_info *ffdinfo;
+
+ ffdinfo = (struct file_full_directory_info *)kstat;
+ ffdinfo->FileNameLength = cpu_to_le32(conv_len);
+ ffdinfo->EaSize =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+ if (ffdinfo->EaSize)
+ ffdinfo->ExtFileAttributes = ATTR_REPARSE_POINT_LE;
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ ffdinfo->ExtFileAttributes |= ATTR_HIDDEN_LE;
+ memcpy(ffdinfo->FileName, conv_name, conv_len);
+ ffdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_both_directory_info *fbdinfo;
+
+ fbdinfo = (struct file_both_directory_info *)kstat;
+ fbdinfo->FileNameLength = cpu_to_le32(conv_len);
+ fbdinfo->EaSize =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+ if (fbdinfo->EaSize)
+ fbdinfo->ExtFileAttributes = ATTR_REPARSE_POINT_LE;
+ fbdinfo->ShortNameLength = 0;
+ fbdinfo->Reserved = 0;
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ fbdinfo->ExtFileAttributes |= ATTR_HIDDEN_LE;
+ memcpy(fbdinfo->FileName, conv_name, conv_len);
+ fbdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_DIRECTORY_INFORMATION:
+ {
+ struct file_directory_info *fdinfo;
+
+ fdinfo = (struct file_directory_info *)kstat;
+ fdinfo->FileNameLength = cpu_to_le32(conv_len);
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ fdinfo->ExtFileAttributes |= ATTR_HIDDEN_LE;
+ memcpy(fdinfo->FileName, conv_name, conv_len);
+ fdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_NAMES_INFORMATION:
+ {
+ struct file_names_info *fninfo;
+
+ fninfo = (struct file_names_info *)kstat;
+ fninfo->FileNameLength = cpu_to_le32(conv_len);
+ memcpy(fninfo->FileName, conv_name, conv_len);
+ fninfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILEID_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_id_full_dir_info *dinfo;
+
+ dinfo = (struct file_id_full_dir_info *)kstat;
+ dinfo->FileNameLength = cpu_to_le32(conv_len);
+ dinfo->EaSize =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+ if (dinfo->EaSize)
+ dinfo->ExtFileAttributes = ATTR_REPARSE_POINT_LE;
+ dinfo->Reserved = 0;
+ dinfo->UniqueId = cpu_to_le64(ksmbd_kstat->kstat->ino);
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ dinfo->ExtFileAttributes |= ATTR_HIDDEN_LE;
+ memcpy(dinfo->FileName, conv_name, conv_len);
+ dinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILEID_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_id_both_directory_info *fibdinfo;
+
+ fibdinfo = (struct file_id_both_directory_info *)kstat;
+ fibdinfo->FileNameLength = cpu_to_le32(conv_len);
+ fibdinfo->EaSize =
+ smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+ if (fibdinfo->EaSize)
+ fibdinfo->ExtFileAttributes = ATTR_REPARSE_POINT_LE;
+ fibdinfo->UniqueId = cpu_to_le64(ksmbd_kstat->kstat->ino);
+ fibdinfo->ShortNameLength = 0;
+ fibdinfo->Reserved = 0;
+ fibdinfo->Reserved2 = cpu_to_le16(0);
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ fibdinfo->ExtFileAttributes |= ATTR_HIDDEN_LE;
+ memcpy(fibdinfo->FileName, conv_name, conv_len);
+ fibdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case SMB_FIND_FILE_POSIX_INFO:
+ {
+ struct smb2_posix_info *posix_info;
+ u64 time;
+
+ posix_info = (struct smb2_posix_info *)kstat;
+ posix_info->Ignored = 0;
+ posix_info->CreationTime = cpu_to_le64(ksmbd_kstat->create_time);
+ time = ksmbd_UnixTimeToNT(ksmbd_kstat->kstat->ctime);
+ posix_info->ChangeTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(ksmbd_kstat->kstat->atime);
+ posix_info->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(ksmbd_kstat->kstat->mtime);
+ posix_info->LastWriteTime = cpu_to_le64(time);
+ posix_info->EndOfFile = cpu_to_le64(ksmbd_kstat->kstat->size);
+ posix_info->AllocationSize = cpu_to_le64(ksmbd_kstat->kstat->blocks << 9);
+ posix_info->DeviceId = cpu_to_le32(ksmbd_kstat->kstat->rdev);
+ posix_info->HardLinks = cpu_to_le32(ksmbd_kstat->kstat->nlink);
+ posix_info->Mode = cpu_to_le32(ksmbd_kstat->kstat->mode);
+ posix_info->Inode = cpu_to_le64(ksmbd_kstat->kstat->ino);
+ posix_info->DosAttributes =
+ S_ISDIR(ksmbd_kstat->kstat->mode) ? ATTR_DIRECTORY_LE : ATTR_ARCHIVE_LE;
+ if (d_info->hide_dot_file && d_info->name[0] == '.')
+ posix_info->DosAttributes |= ATTR_HIDDEN_LE;
+ id_to_sid(from_kuid(user_ns, ksmbd_kstat->kstat->uid),
+ SIDNFS_USER, (struct smb_sid *)&posix_info->SidBuffer[0]);
+ id_to_sid(from_kgid(user_ns, ksmbd_kstat->kstat->gid),
+ SIDNFS_GROUP, (struct smb_sid *)&posix_info->SidBuffer[20]);
+ memcpy(posix_info->name, conv_name, conv_len);
+ posix_info->name_len = cpu_to_le32(conv_len);
+ posix_info->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+
+ } /* switch (info_level) */
+
+ d_info->last_entry_offset = d_info->data_count;
+ d_info->data_count += next_entry_offset;
+ d_info->out_buf_len -= next_entry_offset;
+ d_info->wptr += next_entry_offset;
+
+ ksmbd_debug(SMB,
+ "info_level : %d, buf_len :%d, next_offset : %d, data_count : %d\n",
+ info_level, d_info->out_buf_len,
+ next_entry_offset, d_info->data_count);
+
+free_conv_name:
+ kfree(conv_name);
+ return rc;
+}
+
+struct smb2_query_dir_private {
+ struct ksmbd_work *work;
+ char *search_pattern;
+ struct ksmbd_file *dir_fp;
+
+ struct ksmbd_dir_info *d_info;
+ int info_level;
+};
+
+static void lock_dir(struct ksmbd_file *dir_fp)
+{
+ struct dentry *dir = dir_fp->filp->f_path.dentry;
+
+ inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
+}
+
+static void unlock_dir(struct ksmbd_file *dir_fp)
+{
+ struct dentry *dir = dir_fp->filp->f_path.dentry;
+
+ inode_unlock(d_inode(dir));
+}
+
+static int process_query_dir_entries(struct smb2_query_dir_private *priv)
+{
+ struct user_namespace *user_ns = file_mnt_user_ns(priv->dir_fp->filp);
+ struct kstat kstat;
+ struct ksmbd_kstat ksmbd_kstat;
+ int rc;
+ int i;
+
+ for (i = 0; i < priv->d_info->num_entry; i++) {
+ struct dentry *dent;
+
+ if (dentry_name(priv->d_info, priv->info_level))
+ return -EINVAL;
+
+ lock_dir(priv->dir_fp);
+ dent = lookup_one_len(priv->d_info->name,
+ priv->dir_fp->filp->f_path.dentry,
+ priv->d_info->name_len);
+ unlock_dir(priv->dir_fp);
+
+ if (IS_ERR(dent)) {
+ ksmbd_debug(SMB, "Cannot lookup `%s' [%ld]\n",
+ priv->d_info->name,
+ PTR_ERR(dent));
+ continue;
+ }
+ if (unlikely(d_is_negative(dent))) {
+ dput(dent);
+ ksmbd_debug(SMB, "Negative dentry `%s'\n",
+ priv->d_info->name);
+ continue;
+ }
+
+ ksmbd_kstat.kstat = &kstat;
+ if (priv->info_level != FILE_NAMES_INFORMATION)
+ ksmbd_vfs_fill_dentry_attrs(priv->work,
+ user_ns,
+ dent,
+ &ksmbd_kstat);
+
+ rc = smb2_populate_readdir_entry(priv->work->conn,
+ priv->info_level,
+ priv->d_info,
+ user_ns,
+ &ksmbd_kstat);
+ dput(dent);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int reserve_populate_dentry(struct ksmbd_dir_info *d_info,
+ int info_level)
+{
+ int struct_sz;
+ int conv_len;
+ int next_entry_offset;
+
+ struct_sz = readdir_info_level_struct_sz(info_level);
+ if (struct_sz == -EOPNOTSUPP)
+ return -EOPNOTSUPP;
+
+ conv_len = (d_info->name_len + 1) * 2;
+ next_entry_offset = ALIGN(struct_sz - 1 + conv_len,
+ KSMBD_DIR_INFO_ALIGNMENT);
+
+ if (next_entry_offset > d_info->out_buf_len) {
+ d_info->out_buf_len = 0;
+ return -ENOSPC;
+ }
+
+ switch (info_level) {
+ case FILE_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_full_directory_info *ffdinfo;
+
+ ffdinfo = (struct file_full_directory_info *)d_info->wptr;
+ memcpy(ffdinfo->FileName, d_info->name, d_info->name_len);
+ ffdinfo->FileName[d_info->name_len] = 0x00;
+ ffdinfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ ffdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_both_directory_info *fbdinfo;
+
+ fbdinfo = (struct file_both_directory_info *)d_info->wptr;
+ memcpy(fbdinfo->FileName, d_info->name, d_info->name_len);
+ fbdinfo->FileName[d_info->name_len] = 0x00;
+ fbdinfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ fbdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_DIRECTORY_INFORMATION:
+ {
+ struct file_directory_info *fdinfo;
+
+ fdinfo = (struct file_directory_info *)d_info->wptr;
+ memcpy(fdinfo->FileName, d_info->name, d_info->name_len);
+ fdinfo->FileName[d_info->name_len] = 0x00;
+ fdinfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ fdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILE_NAMES_INFORMATION:
+ {
+ struct file_names_info *fninfo;
+
+ fninfo = (struct file_names_info *)d_info->wptr;
+ memcpy(fninfo->FileName, d_info->name, d_info->name_len);
+ fninfo->FileName[d_info->name_len] = 0x00;
+ fninfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ fninfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILEID_FULL_DIRECTORY_INFORMATION:
+ {
+ struct file_id_full_dir_info *dinfo;
+
+ dinfo = (struct file_id_full_dir_info *)d_info->wptr;
+ memcpy(dinfo->FileName, d_info->name, d_info->name_len);
+ dinfo->FileName[d_info->name_len] = 0x00;
+ dinfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ dinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case FILEID_BOTH_DIRECTORY_INFORMATION:
+ {
+ struct file_id_both_directory_info *fibdinfo;
+
+ fibdinfo = (struct file_id_both_directory_info *)d_info->wptr;
+ memcpy(fibdinfo->FileName, d_info->name, d_info->name_len);
+ fibdinfo->FileName[d_info->name_len] = 0x00;
+ fibdinfo->FileNameLength = cpu_to_le32(d_info->name_len);
+ fibdinfo->NextEntryOffset = cpu_to_le32(next_entry_offset);
+ break;
+ }
+ case SMB_FIND_FILE_POSIX_INFO:
+ {
+ struct smb2_posix_info *posix_info;
+
+ posix_info = (struct smb2_posix_info *)d_info->wptr;
+ memcpy(posix_info->name, d_info->name, d_info->name_len);
+ posix_info->name[d_info->name_len] = 0x00;
+ posix_info->name_len = cpu_to_le32(d_info->name_len);
+ posix_info->NextEntryOffset =
+ cpu_to_le32(next_entry_offset);
+ break;
+ }
+ } /* switch (info_level) */
+
+ d_info->num_entry++;
+ d_info->out_buf_len -= next_entry_offset;
+ d_info->wptr += next_entry_offset;
+ return 0;
+}
+
+static int __query_dir(struct dir_context *ctx, const char *name, int namlen,
+ loff_t offset, u64 ino, unsigned int d_type)
+{
+ struct ksmbd_readdir_data *buf;
+ struct smb2_query_dir_private *priv;
+ struct ksmbd_dir_info *d_info;
+ int rc;
+
+ buf = container_of(ctx, struct ksmbd_readdir_data, ctx);
+ priv = buf->private;
+ d_info = priv->d_info;
+
+ /* dot and dotdot entries are already reserved */
+ if (!strcmp(".", name) || !strcmp("..", name))
+ return 0;
+ if (ksmbd_share_veto_filename(priv->work->tcon->share_conf, name))
+ return 0;
+ if (!match_pattern(name, namlen, priv->search_pattern))
+ return 0;
+
+ d_info->name = name;
+ d_info->name_len = namlen;
+ rc = reserve_populate_dentry(d_info, priv->info_level);
+ if (rc)
+ return rc;
+ if (d_info->flags & SMB2_RETURN_SINGLE_ENTRY) {
+ d_info->out_buf_len = 0;
+ return 0;
+ }
+ return 0;
+}
+
+static void restart_ctx(struct dir_context *ctx)
+{
+ ctx->pos = 0;
+}
+
+static int verify_info_level(int info_level)
+{
+ switch (info_level) {
+ case FILE_FULL_DIRECTORY_INFORMATION:
+ case FILE_BOTH_DIRECTORY_INFORMATION:
+ case FILE_DIRECTORY_INFORMATION:
+ case FILE_NAMES_INFORMATION:
+ case FILEID_FULL_DIRECTORY_INFORMATION:
+ case FILEID_BOTH_DIRECTORY_INFORMATION:
+ case SMB_FIND_FILE_POSIX_INFO:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+int smb2_query_dir(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_query_directory_req *req;
+ struct smb2_query_directory_rsp *rsp, *rsp_org;
+ struct ksmbd_share_config *share = work->tcon->share_conf;
+ struct ksmbd_file *dir_fp = NULL;
+ struct ksmbd_dir_info d_info;
+ int rc = 0;
+ char *srch_ptr = NULL;
+ unsigned char srch_flag;
+ int buffer_sz;
+ struct smb2_query_dir_private query_dir_private = {NULL, };
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ if (ksmbd_override_fsids(work)) {
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ smb2_set_err_rsp(work);
+ return -ENOMEM;
+ }
+
+ rc = verify_info_level(req->FileInformationClass);
+ if (rc) {
+ rc = -EFAULT;
+ goto err_out2;
+ }
+
+ dir_fp = ksmbd_lookup_fd_slow(work,
+ le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (!dir_fp) {
+ rc = -EBADF;
+ goto err_out2;
+ }
+
+ if (!(dir_fp->daccess & FILE_LIST_DIRECTORY_LE) ||
+ inode_permission(file_mnt_user_ns(dir_fp->filp),
+ file_inode(dir_fp->filp),
+ MAY_READ | MAY_EXEC)) {
+ pr_err("no right to enumerate directory (%pd)\n",
+ dir_fp->filp->f_path.dentry);
+ rc = -EACCES;
+ goto err_out2;
+ }
+
+ if (!S_ISDIR(file_inode(dir_fp->filp)->i_mode)) {
+ pr_err("can't do query dir for a file\n");
+ rc = -EINVAL;
+ goto err_out2;
+ }
+
+ srch_flag = req->Flags;
+ srch_ptr = smb_strndup_from_utf16(req->Buffer,
+ le16_to_cpu(req->FileNameLength), 1,
+ conn->local_nls);
+ if (IS_ERR(srch_ptr)) {
+ ksmbd_debug(SMB, "Search Pattern not found\n");
+ rc = -EINVAL;
+ goto err_out2;
+ } else {
+ ksmbd_debug(SMB, "Search pattern is %s\n", srch_ptr);
+ }
+
+ ksmbd_debug(SMB, "Directory name is %s\n", dir_fp->filename);
+
+ if (srch_flag & SMB2_REOPEN || srch_flag & SMB2_RESTART_SCANS) {
+ ksmbd_debug(SMB, "Restart directory scan\n");
+ generic_file_llseek(dir_fp->filp, 0, SEEK_SET);
+ restart_ctx(&dir_fp->readdir_data.ctx);
+ }
+
+ memset(&d_info, 0, sizeof(struct ksmbd_dir_info));
+ d_info.wptr = (char *)rsp->Buffer;
+ d_info.rptr = (char *)rsp->Buffer;
+ d_info.out_buf_len = (work->response_sz - (get_rfc1002_len(rsp_org) + 4));
+ d_info.out_buf_len = min_t(int, d_info.out_buf_len, le32_to_cpu(req->OutputBufferLength)) -
+ sizeof(struct smb2_query_directory_rsp);
+ d_info.flags = srch_flag;
+
+ /*
+ * reserve dot and dotdot entries in head of buffer
+ * in first response
+ */
+ rc = ksmbd_populate_dot_dotdot_entries(work, req->FileInformationClass,
+ dir_fp, &d_info, srch_ptr,
+ smb2_populate_readdir_entry);
+ if (rc == -ENOSPC)
+ rc = 0;
+ else if (rc)
+ goto err_out;
+
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_HIDE_DOT_FILES))
+ d_info.hide_dot_file = true;
+
+ buffer_sz = d_info.out_buf_len;
+ d_info.rptr = d_info.wptr;
+ query_dir_private.work = work;
+ query_dir_private.search_pattern = srch_ptr;
+ query_dir_private.dir_fp = dir_fp;
+ query_dir_private.d_info = &d_info;
+ query_dir_private.info_level = req->FileInformationClass;
+ dir_fp->readdir_data.private = &query_dir_private;
+ set_ctx_actor(&dir_fp->readdir_data.ctx, __query_dir);
+
+ rc = iterate_dir(dir_fp->filp, &dir_fp->readdir_data.ctx);
+ if (rc == 0)
+ restart_ctx(&dir_fp->readdir_data.ctx);
+ if (rc == -ENOSPC)
+ rc = 0;
+ if (rc)
+ goto err_out;
+
+ d_info.wptr = d_info.rptr;
+ d_info.out_buf_len = buffer_sz;
+ rc = process_query_dir_entries(&query_dir_private);
+ if (rc)
+ goto err_out;
+
+ if (!d_info.data_count && d_info.out_buf_len >= 0) {
+ if (srch_flag & SMB2_RETURN_SINGLE_ENTRY && !is_asterisk(srch_ptr)) {
+ rsp->hdr.Status = STATUS_NO_SUCH_FILE;
+ } else {
+ dir_fp->dot_dotdot[0] = dir_fp->dot_dotdot[1] = 0;
+ rsp->hdr.Status = STATUS_NO_MORE_FILES;
+ }
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->OutputBufferOffset = cpu_to_le16(0);
+ rsp->OutputBufferLength = cpu_to_le32(0);
+ rsp->Buffer[0] = 0;
+ inc_rfc1001_len(rsp_org, 9);
+ } else {
+ ((struct file_directory_info *)
+ ((char *)rsp->Buffer + d_info.last_entry_offset))
+ ->NextEntryOffset = 0;
+
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->OutputBufferOffset = cpu_to_le16(72);
+ rsp->OutputBufferLength = cpu_to_le32(d_info.data_count);
+ inc_rfc1001_len(rsp_org, 8 + d_info.data_count);
+ }
+
+ kfree(srch_ptr);
+ ksmbd_fd_put(work, dir_fp);
+ ksmbd_revert_fsids(work);
+ return 0;
+
+err_out:
+ pr_err("error while processing smb2 query dir rc = %d\n", rc);
+ kfree(srch_ptr);
+
+err_out2:
+ if (rc == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (rc == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (rc == -ENOENT)
+ rsp->hdr.Status = STATUS_NO_SUCH_FILE;
+ else if (rc == -EBADF)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ else if (rc == -ENOMEM)
+ rsp->hdr.Status = STATUS_NO_MEMORY;
+ else if (rc == -EFAULT)
+ rsp->hdr.Status = STATUS_INVALID_INFO_CLASS;
+ if (!rsp->hdr.Status)
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, dir_fp);
+ ksmbd_revert_fsids(work);
+ return 0;
+}
+
+/**
+ * buffer_check_err() - helper function to check buffer errors
+ * @reqOutputBufferLength: max buffer length expected in command response
+ * @rsp: query info response buffer contains output buffer length
+ * @infoclass_size: query info class response buffer size
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int buffer_check_err(int reqOutputBufferLength,
+ struct smb2_query_info_rsp *rsp, int infoclass_size)
+{
+ if (reqOutputBufferLength < le32_to_cpu(rsp->OutputBufferLength)) {
+ if (reqOutputBufferLength < infoclass_size) {
+ pr_err("Invalid Buffer Size Requested\n");
+ rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH;
+ rsp->hdr.smb2_buf_length = cpu_to_be32(sizeof(struct smb2_hdr) - 4);
+ return -EINVAL;
+ }
+
+ ksmbd_debug(SMB, "Buffer Overflow\n");
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ rsp->hdr.smb2_buf_length = cpu_to_be32(sizeof(struct smb2_hdr) - 4 +
+ reqOutputBufferLength);
+ rsp->OutputBufferLength = cpu_to_le32(reqOutputBufferLength);
+ }
+ return 0;
+}
+
+static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp)
+{
+ struct smb2_file_standard_info *sinfo;
+
+ sinfo = (struct smb2_file_standard_info *)rsp->Buffer;
+
+ sinfo->AllocationSize = cpu_to_le64(4096);
+ sinfo->EndOfFile = cpu_to_le64(0);
+ sinfo->NumberOfLinks = cpu_to_le32(1);
+ sinfo->DeletePending = 1;
+ sinfo->Directory = 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_standard_info));
+ inc_rfc1001_len(rsp, sizeof(struct smb2_file_standard_info));
+}
+
+static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num)
+{
+ struct smb2_file_internal_info *file_info;
+
+ file_info = (struct smb2_file_internal_info *)rsp->Buffer;
+
+ /* any unique number */
+ file_info->IndexNumber = cpu_to_le64(num | (1ULL << 63));
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_internal_info));
+ inc_rfc1001_len(rsp, sizeof(struct smb2_file_internal_info));
+}
+
+static int smb2_get_info_file_pipe(struct ksmbd_session *sess,
+ struct smb2_query_info_req *req,
+ struct smb2_query_info_rsp *rsp)
+{
+ u64 id;
+ int rc;
+
+ /*
+ * Windows can sometime send query file info request on
+ * pipe without opening it, checking error condition here
+ */
+ id = le64_to_cpu(req->VolatileFileId);
+ if (!ksmbd_session_rpc_method(sess, id))
+ return -ENOENT;
+
+ ksmbd_debug(SMB, "FileInfoClass %u, FileId 0x%llx\n",
+ req->FileInfoClass, le64_to_cpu(req->VolatileFileId));
+
+ switch (req->FileInfoClass) {
+ case FILE_STANDARD_INFORMATION:
+ get_standard_info_pipe(rsp);
+ rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
+ rsp, FILE_STANDARD_INFORMATION_SIZE);
+ break;
+ case FILE_INTERNAL_INFORMATION:
+ get_internal_info_pipe(rsp, id);
+ rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
+ rsp, FILE_INTERNAL_INFORMATION_SIZE);
+ break;
+ default:
+ ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n",
+ req->FileInfoClass);
+ rc = -EOPNOTSUPP;
+ }
+ return rc;
+}
+
+/**
+ * smb2_get_ea() - handler for smb2 get extended attribute command
+ * @work: smb work containing query info command buffer
+ * @fp: ksmbd_file pointer
+ * @req: get extended attribute request
+ * @rsp: response buffer pointer
+ * @rsp_org: base response buffer pointer in case of chained response
+ *
+ * Return: 0 on success, otherwise error
+ */
+static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
+ struct smb2_query_info_req *req,
+ struct smb2_query_info_rsp *rsp, void *rsp_org)
+{
+ struct smb2_ea_info *eainfo, *prev_eainfo;
+ char *name, *ptr, *xattr_list = NULL, *buf;
+ int rc, name_len, value_len, xattr_list_len, idx;
+ ssize_t buf_free_len, alignment_bytes, next_offset, rsp_data_cnt = 0;
+ struct smb2_ea_info_req *ea_req = NULL;
+ struct path *path;
+ struct user_namespace *user_ns = file_mnt_user_ns(fp->filp);
+
+ if (!(fp->daccess & FILE_READ_EA_LE)) {
+ pr_err("Not permitted to read ext attr : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ path = &fp->filp->f_path;
+ /* single EA entry is requested with given user.* name */
+ if (req->InputBufferLength) {
+ ea_req = (struct smb2_ea_info_req *)req->Buffer;
+ } else {
+ /* need to send all EAs, if no specific EA is requested*/
+ if (le32_to_cpu(req->Flags) & SL_RETURN_SINGLE_ENTRY)
+ ksmbd_debug(SMB,
+ "All EAs are requested but need to send single EA entry in rsp flags 0x%x\n",
+ le32_to_cpu(req->Flags));
+ }
+
+ buf_free_len = work->response_sz -
+ (get_rfc1002_len(rsp_org) + 4) -
+ sizeof(struct smb2_query_info_rsp);
+
+ if (le32_to_cpu(req->OutputBufferLength) < buf_free_len)
+ buf_free_len = le32_to_cpu(req->OutputBufferLength);
+
+ rc = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+ if (rc < 0) {
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ goto out;
+ } else if (!rc) { /* there is no EA in the file */
+ ksmbd_debug(SMB, "no ea data in the file\n");
+ goto done;
+ }
+ xattr_list_len = rc;
+
+ ptr = (char *)rsp->Buffer;
+ eainfo = (struct smb2_ea_info *)ptr;
+ prev_eainfo = eainfo;
+ idx = 0;
+
+ while (idx < xattr_list_len) {
+ name = xattr_list + idx;
+ name_len = strlen(name);
+
+ ksmbd_debug(SMB, "%s, len %d\n", name, name_len);
+ idx += name_len + 1;
+
+ /*
+ * CIFS does not support EA other than user.* namespace,
+ * still keep the framework generic, to list other attrs
+ * in future.
+ */
+ if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+ continue;
+
+ if (!strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX,
+ STREAM_PREFIX_LEN))
+ continue;
+
+ if (req->InputBufferLength &&
+ strncmp(&name[XATTR_USER_PREFIX_LEN], ea_req->name,
+ ea_req->EaNameLength))
+ continue;
+
+ if (!strncmp(&name[XATTR_USER_PREFIX_LEN],
+ DOS_ATTRIBUTE_PREFIX, DOS_ATTRIBUTE_PREFIX_LEN))
+ continue;
+
+ if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+ name_len -= XATTR_USER_PREFIX_LEN;
+
+ ptr = (char *)(&eainfo->name + name_len + 1);
+ buf_free_len -= (offsetof(struct smb2_ea_info, name) +
+ name_len + 1);
+ /* bailout if xattr can't fit in buf_free_len */
+ value_len = ksmbd_vfs_getxattr(user_ns, path->dentry,
+ name, &buf);
+ if (value_len <= 0) {
+ rc = -ENOENT;
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ goto out;
+ }
+
+ buf_free_len -= value_len;
+ if (buf_free_len < 0) {
+ kfree(buf);
+ break;
+ }
+
+ memcpy(ptr, buf, value_len);
+ kfree(buf);
+
+ ptr += value_len;
+ eainfo->Flags = 0;
+ eainfo->EaNameLength = name_len;
+
+ if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+ memcpy(eainfo->name, &name[XATTR_USER_PREFIX_LEN],
+ name_len);
+ else
+ memcpy(eainfo->name, name, name_len);
+
+ eainfo->name[name_len] = '\0';
+ eainfo->EaValueLength = cpu_to_le16(value_len);
+ next_offset = offsetof(struct smb2_ea_info, name) +
+ name_len + 1 + value_len;
+
+ /* align next xattr entry at 4 byte bundary */
+ alignment_bytes = ((next_offset + 3) & ~3) - next_offset;
+ if (alignment_bytes) {
+ memset(ptr, '\0', alignment_bytes);
+ ptr += alignment_bytes;
+ next_offset += alignment_bytes;
+ buf_free_len -= alignment_bytes;
+ }
+ eainfo->NextEntryOffset = cpu_to_le32(next_offset);
+ prev_eainfo = eainfo;
+ eainfo = (struct smb2_ea_info *)ptr;
+ rsp_data_cnt += next_offset;
+
+ if (req->InputBufferLength) {
+ ksmbd_debug(SMB, "single entry requested\n");
+ break;
+ }
+ }
+
+ /* no more ea entries */
+ prev_eainfo->NextEntryOffset = 0;
+done:
+ rc = 0;
+ if (rsp_data_cnt == 0)
+ rsp->hdr.Status = STATUS_NO_EAS_ON_FILE;
+ rsp->OutputBufferLength = cpu_to_le32(rsp_data_cnt);
+ inc_rfc1001_len(rsp_org, rsp_data_cnt);
+out:
+ kvfree(xattr_list);
+ return rc;
+}
+
+static void get_file_access_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_access_info *file_info;
+
+ file_info = (struct smb2_file_access_info *)rsp->Buffer;
+ file_info->AccessFlags = fp->daccess;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_access_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_access_info));
+}
+
+static int get_file_basic_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_all_info *basic_info;
+ struct kstat stat;
+ u64 time;
+
+ if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+ pr_err("no right to read the attributes : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ basic_info = (struct smb2_file_all_info *)rsp->Buffer;
+ generic_fillattr(file_mnt_user_ns(fp->filp), file_inode(fp->filp),
+ &stat);
+ basic_info->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(stat.atime);
+ basic_info->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.mtime);
+ basic_info->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.ctime);
+ basic_info->ChangeTime = cpu_to_le64(time);
+ basic_info->Attributes = fp->f_ci->m_fattr;
+ basic_info->Pad1 = 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(offsetof(struct smb2_file_all_info, AllocationSize));
+ inc_rfc1001_len(rsp_org, offsetof(struct smb2_file_all_info,
+ AllocationSize));
+ return 0;
+}
+
+static unsigned long long get_allocation_size(struct inode *inode,
+ struct kstat *stat)
+{
+ unsigned long long alloc_size = 0;
+
+ if (!S_ISDIR(stat->mode)) {
+ if ((inode->i_blocks << 9) <= stat->size)
+ alloc_size = stat->size;
+ else
+ alloc_size = inode->i_blocks << 9;
+ }
+
+ return alloc_size;
+}
+
+static void get_file_standard_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_standard_info *sinfo;
+ unsigned int delete_pending;
+ struct inode *inode;
+ struct kstat stat;
+
+ inode = file_inode(fp->filp);
+ generic_fillattr(file_mnt_user_ns(fp->filp), inode, &stat);
+
+ sinfo = (struct smb2_file_standard_info *)rsp->Buffer;
+ delete_pending = ksmbd_inode_pending_delete(fp);
+
+ sinfo->AllocationSize = cpu_to_le64(get_allocation_size(inode, &stat));
+ sinfo->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ sinfo->NumberOfLinks = cpu_to_le32(get_nlink(&stat) - delete_pending);
+ sinfo->DeletePending = delete_pending;
+ sinfo->Directory = S_ISDIR(stat.mode) ? 1 : 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_standard_info));
+ inc_rfc1001_len(rsp_org,
+ sizeof(struct smb2_file_standard_info));
+}
+
+static void get_file_alignment_info(struct smb2_query_info_rsp *rsp,
+ void *rsp_org)
+{
+ struct smb2_file_alignment_info *file_info;
+
+ file_info = (struct smb2_file_alignment_info *)rsp->Buffer;
+ file_info->AlignmentRequirement = 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_alignment_info));
+ inc_rfc1001_len(rsp_org,
+ sizeof(struct smb2_file_alignment_info));
+}
+
+static int get_file_all_info(struct ksmbd_work *work,
+ struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp,
+ void *rsp_org)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_file_all_info *file_info;
+ unsigned int delete_pending;
+ struct inode *inode;
+ struct kstat stat;
+ int conv_len;
+ char *filename;
+ u64 time;
+
+ if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+ ksmbd_debug(SMB, "no right to read the attributes : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ filename = convert_to_nt_pathname(fp->filename,
+ work->tcon->share_conf->path);
+ if (!filename)
+ return -ENOMEM;
+
+ inode = file_inode(fp->filp);
+ generic_fillattr(file_mnt_user_ns(fp->filp), inode, &stat);
+
+ ksmbd_debug(SMB, "filename = %s\n", filename);
+ delete_pending = ksmbd_inode_pending_delete(fp);
+ file_info = (struct smb2_file_all_info *)rsp->Buffer;
+
+ file_info->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(stat.atime);
+ file_info->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.mtime);
+ file_info->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.ctime);
+ file_info->ChangeTime = cpu_to_le64(time);
+ file_info->Attributes = fp->f_ci->m_fattr;
+ file_info->Pad1 = 0;
+ file_info->AllocationSize =
+ cpu_to_le64(get_allocation_size(inode, &stat));
+ file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ file_info->NumberOfLinks =
+ cpu_to_le32(get_nlink(&stat) - delete_pending);
+ file_info->DeletePending = delete_pending;
+ file_info->Directory = S_ISDIR(stat.mode) ? 1 : 0;
+ file_info->Pad2 = 0;
+ file_info->IndexNumber = cpu_to_le64(stat.ino);
+ file_info->EASize = 0;
+ file_info->AccessFlags = fp->daccess;
+ file_info->CurrentByteOffset = cpu_to_le64(fp->filp->f_pos);
+ file_info->Mode = fp->coption;
+ file_info->AlignmentRequirement = 0;
+ conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, filename,
+ PATH_MAX, conn->local_nls, 0);
+ conv_len *= 2;
+ file_info->FileNameLength = cpu_to_le32(conv_len);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_all_info) + conv_len - 1);
+ kfree(filename);
+ inc_rfc1001_len(rsp_org, le32_to_cpu(rsp->OutputBufferLength));
+ return 0;
+}
+
+static void get_file_alternate_info(struct ksmbd_work *work,
+ struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp,
+ void *rsp_org)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_file_alt_name_info *file_info;
+ struct dentry *dentry = fp->filp->f_path.dentry;
+ int conv_len;
+
+ spin_lock(&dentry->d_lock);
+ file_info = (struct smb2_file_alt_name_info *)rsp->Buffer;
+ conv_len = ksmbd_extract_shortname(conn,
+ dentry->d_name.name,
+ file_info->FileName);
+ spin_unlock(&dentry->d_lock);
+ file_info->FileNameLength = cpu_to_le32(conv_len);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_alt_name_info) + conv_len);
+ inc_rfc1001_len(rsp_org, le32_to_cpu(rsp->OutputBufferLength));
+}
+
+static void get_file_stream_info(struct ksmbd_work *work,
+ struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp,
+ void *rsp_org)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_file_stream_info *file_info;
+ char *stream_name, *xattr_list = NULL, *stream_buf;
+ struct kstat stat;
+ struct path *path = &fp->filp->f_path;
+ ssize_t xattr_list_len;
+ int nbytes = 0, streamlen, stream_name_len, next, idx = 0;
+
+ generic_fillattr(file_mnt_user_ns(fp->filp), file_inode(fp->filp),
+ &stat);
+ file_info = (struct smb2_file_stream_info *)rsp->Buffer;
+
+ xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+ if (xattr_list_len < 0) {
+ goto out;
+ } else if (!xattr_list_len) {
+ ksmbd_debug(SMB, "empty xattr in the file\n");
+ goto out;
+ }
+
+ while (idx < xattr_list_len) {
+ stream_name = xattr_list + idx;
+ streamlen = strlen(stream_name);
+ idx += streamlen + 1;
+
+ ksmbd_debug(SMB, "%s, len %d\n", stream_name, streamlen);
+
+ if (strncmp(&stream_name[XATTR_USER_PREFIX_LEN],
+ STREAM_PREFIX, STREAM_PREFIX_LEN))
+ continue;
+
+ stream_name_len = streamlen - (XATTR_USER_PREFIX_LEN +
+ STREAM_PREFIX_LEN);
+ streamlen = stream_name_len;
+
+ /* plus : size */
+ streamlen += 1;
+ stream_buf = kmalloc(streamlen + 1, GFP_KERNEL);
+ if (!stream_buf)
+ break;
+
+ streamlen = snprintf(stream_buf, streamlen + 1,
+ ":%s", &stream_name[XATTR_NAME_STREAM_LEN]);
+
+ file_info = (struct smb2_file_stream_info *)&rsp->Buffer[nbytes];
+ streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
+ stream_buf, streamlen,
+ conn->local_nls, 0);
+ streamlen *= 2;
+ kfree(stream_buf);
+ file_info->StreamNameLength = cpu_to_le32(streamlen);
+ file_info->StreamSize = cpu_to_le64(stream_name_len);
+ file_info->StreamAllocationSize = cpu_to_le64(stream_name_len);
+
+ next = sizeof(struct smb2_file_stream_info) + streamlen;
+ nbytes += next;
+ file_info->NextEntryOffset = cpu_to_le32(next);
+ }
+
+ if (nbytes) {
+ file_info = (struct smb2_file_stream_info *)
+ &rsp->Buffer[nbytes];
+ streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
+ "::$DATA", 7, conn->local_nls, 0);
+ streamlen *= 2;
+ file_info->StreamNameLength = cpu_to_le32(streamlen);
+ file_info->StreamSize = S_ISDIR(stat.mode) ? 0 :
+ cpu_to_le64(stat.size);
+ file_info->StreamAllocationSize = S_ISDIR(stat.mode) ? 0 :
+ cpu_to_le64(stat.size);
+ nbytes += sizeof(struct smb2_file_stream_info) + streamlen;
+ }
+
+ /* last entry offset should be 0 */
+ file_info->NextEntryOffset = 0;
+out:
+ kvfree(xattr_list);
+
+ rsp->OutputBufferLength = cpu_to_le32(nbytes);
+ inc_rfc1001_len(rsp_org, nbytes);
+}
+
+static void get_file_internal_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_internal_info *file_info;
+ struct kstat stat;
+
+ generic_fillattr(file_mnt_user_ns(fp->filp), file_inode(fp->filp),
+ &stat);
+ file_info = (struct smb2_file_internal_info *)rsp->Buffer;
+ file_info->IndexNumber = cpu_to_le64(stat.ino);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_internal_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_internal_info));
+}
+
+static int get_file_network_open_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_ntwrk_info *file_info;
+ struct inode *inode;
+ struct kstat stat;
+ u64 time;
+
+ if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+ pr_err("no right to read the attributes : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ file_info = (struct smb2_file_ntwrk_info *)rsp->Buffer;
+
+ inode = file_inode(fp->filp);
+ generic_fillattr(file_mnt_user_ns(fp->filp), inode, &stat);
+
+ file_info->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(stat.atime);
+ file_info->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.mtime);
+ file_info->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(stat.ctime);
+ file_info->ChangeTime = cpu_to_le64(time);
+ file_info->Attributes = fp->f_ci->m_fattr;
+ file_info->AllocationSize =
+ cpu_to_le64(get_allocation_size(inode, &stat));
+ file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
+ file_info->Reserved = cpu_to_le32(0);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_ntwrk_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_ntwrk_info));
+ return 0;
+}
+
+static void get_file_ea_info(struct smb2_query_info_rsp *rsp, void *rsp_org)
+{
+ struct smb2_file_ea_info *file_info;
+
+ file_info = (struct smb2_file_ea_info *)rsp->Buffer;
+ file_info->EASize = 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_ea_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_ea_info));
+}
+
+static void get_file_position_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_pos_info *file_info;
+
+ file_info = (struct smb2_file_pos_info *)rsp->Buffer;
+ file_info->CurrentByteOffset = cpu_to_le64(fp->filp->f_pos);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_pos_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_pos_info));
+}
+
+static void get_file_mode_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_mode_info *file_info;
+
+ file_info = (struct smb2_file_mode_info *)rsp->Buffer;
+ file_info->Mode = fp->coption & FILE_MODE_INFO_MASK;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_mode_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_mode_info));
+}
+
+static void get_file_compression_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_comp_info *file_info;
+ struct kstat stat;
+
+ generic_fillattr(file_mnt_user_ns(fp->filp), file_inode(fp->filp),
+ &stat);
+
+ file_info = (struct smb2_file_comp_info *)rsp->Buffer;
+ file_info->CompressedFileSize = cpu_to_le64(stat.blocks << 9);
+ file_info->CompressionFormat = COMPRESSION_FORMAT_NONE;
+ file_info->CompressionUnitShift = 0;
+ file_info->ChunkShift = 0;
+ file_info->ClusterShift = 0;
+ memset(&file_info->Reserved[0], 0, 3);
+
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_comp_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_comp_info));
+}
+
+static int get_file_attribute_tag_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb2_file_attr_tag_info *file_info;
+
+ if (!(fp->daccess & FILE_READ_ATTRIBUTES_LE)) {
+ pr_err("no right to read the attributes : 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ file_info = (struct smb2_file_attr_tag_info *)rsp->Buffer;
+ file_info->FileAttributes = fp->f_ci->m_fattr;
+ file_info->ReparseTag = 0;
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb2_file_attr_tag_info));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb2_file_attr_tag_info));
+ return 0;
+}
+
+static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
+ struct ksmbd_file *fp, void *rsp_org)
+{
+ struct smb311_posix_qinfo *file_info;
+ struct inode *inode = file_inode(fp->filp);
+ u64 time;
+
+ file_info = (struct smb311_posix_qinfo *)rsp->Buffer;
+ file_info->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(inode->i_atime);
+ file_info->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(inode->i_mtime);
+ file_info->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(inode->i_ctime);
+ file_info->ChangeTime = cpu_to_le64(time);
+ file_info->DosAttributes = fp->f_ci->m_fattr;
+ file_info->Inode = cpu_to_le64(inode->i_ino);
+ file_info->EndOfFile = cpu_to_le64(inode->i_size);
+ file_info->AllocationSize = cpu_to_le64(inode->i_blocks << 9);
+ file_info->HardLinks = cpu_to_le32(inode->i_nlink);
+ file_info->Mode = cpu_to_le32(inode->i_mode);
+ file_info->DeviceId = cpu_to_le32(inode->i_rdev);
+ rsp->OutputBufferLength =
+ cpu_to_le32(sizeof(struct smb311_posix_qinfo));
+ inc_rfc1001_len(rsp_org, sizeof(struct smb311_posix_qinfo));
+ return 0;
+}
+
+static int smb2_get_info_file(struct ksmbd_work *work,
+ struct smb2_query_info_req *req,
+ struct smb2_query_info_rsp *rsp, void *rsp_org)
+{
+ struct ksmbd_file *fp;
+ int fileinfoclass = 0;
+ int rc = 0;
+ int file_infoclass_size;
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_PIPE)) {
+ /* smb2 info file called for pipe */
+ return smb2_get_info_file_pipe(work->sess, req, rsp);
+ }
+
+ if (work->next_smb2_rcv_hdr_off) {
+ if (!has_file_id(le64_to_cpu(req->VolatileFileId))) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+ }
+
+ if (!has_file_id(id)) {
+ id = le64_to_cpu(req->VolatileFileId);
+ pid = le64_to_cpu(req->PersistentFileId);
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
+ if (!fp)
+ return -ENOENT;
+
+ fileinfoclass = req->FileInfoClass;
+
+ switch (fileinfoclass) {
+ case FILE_ACCESS_INFORMATION:
+ get_file_access_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_ACCESS_INFORMATION_SIZE;
+ break;
+
+ case FILE_BASIC_INFORMATION:
+ rc = get_file_basic_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_BASIC_INFORMATION_SIZE;
+ break;
+
+ case FILE_STANDARD_INFORMATION:
+ get_file_standard_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_STANDARD_INFORMATION_SIZE;
+ break;
+
+ case FILE_ALIGNMENT_INFORMATION:
+ get_file_alignment_info(rsp, rsp_org);
+ file_infoclass_size = FILE_ALIGNMENT_INFORMATION_SIZE;
+ break;
+
+ case FILE_ALL_INFORMATION:
+ rc = get_file_all_info(work, rsp, fp, rsp_org);
+ file_infoclass_size = FILE_ALL_INFORMATION_SIZE;
+ break;
+
+ case FILE_ALTERNATE_NAME_INFORMATION:
+ get_file_alternate_info(work, rsp, fp, rsp_org);
+ file_infoclass_size = FILE_ALTERNATE_NAME_INFORMATION_SIZE;
+ break;
+
+ case FILE_STREAM_INFORMATION:
+ get_file_stream_info(work, rsp, fp, rsp_org);
+ file_infoclass_size = FILE_STREAM_INFORMATION_SIZE;
+ break;
+
+ case FILE_INTERNAL_INFORMATION:
+ get_file_internal_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_INTERNAL_INFORMATION_SIZE;
+ break;
+
+ case FILE_NETWORK_OPEN_INFORMATION:
+ rc = get_file_network_open_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_NETWORK_OPEN_INFORMATION_SIZE;
+ break;
+
+ case FILE_EA_INFORMATION:
+ get_file_ea_info(rsp, rsp_org);
+ file_infoclass_size = FILE_EA_INFORMATION_SIZE;
+ break;
+
+ case FILE_FULL_EA_INFORMATION:
+ rc = smb2_get_ea(work, fp, req, rsp, rsp_org);
+ file_infoclass_size = FILE_FULL_EA_INFORMATION_SIZE;
+ break;
+
+ case FILE_POSITION_INFORMATION:
+ get_file_position_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_POSITION_INFORMATION_SIZE;
+ break;
+
+ case FILE_MODE_INFORMATION:
+ get_file_mode_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_MODE_INFORMATION_SIZE;
+ break;
+
+ case FILE_COMPRESSION_INFORMATION:
+ get_file_compression_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_COMPRESSION_INFORMATION_SIZE;
+ break;
+
+ case FILE_ATTRIBUTE_TAG_INFORMATION:
+ rc = get_file_attribute_tag_info(rsp, fp, rsp_org);
+ file_infoclass_size = FILE_ATTRIBUTE_TAG_INFORMATION_SIZE;
+ break;
+ case SMB_FIND_FILE_POSIX_INFO:
+ if (!work->tcon->posix_extensions) {
+ pr_err("client doesn't negotiate with SMB3.1.1 POSIX Extensions\n");
+ rc = -EOPNOTSUPP;
+ } else {
+ rc = find_file_posix_info(rsp, fp, rsp_org);
+ file_infoclass_size = sizeof(struct smb311_posix_qinfo);
+ }
+ break;
+ default:
+ ksmbd_debug(SMB, "fileinfoclass %d not supported yet\n",
+ fileinfoclass);
+ rc = -EOPNOTSUPP;
+ }
+ if (!rc)
+ rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
+ rsp,
+ file_infoclass_size);
+ ksmbd_fd_put(work, fp);
+ return rc;
+}
+
+static int smb2_get_info_filesystem(struct ksmbd_work *work,
+ struct smb2_query_info_req *req,
+ struct smb2_query_info_rsp *rsp, void *rsp_org)
+{
+ struct ksmbd_session *sess = work->sess;
+ struct ksmbd_conn *conn = sess->conn;
+ struct ksmbd_share_config *share = work->tcon->share_conf;
+ int fsinfoclass = 0;
+ struct kstatfs stfs;
+ struct path path;
+ int rc = 0, len;
+ int fs_infoclass_size = 0;
+ int lookup_flags = 0;
+
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS))
+ lookup_flags = LOOKUP_FOLLOW;
+
+ rc = ksmbd_vfs_kern_path(share->path, lookup_flags, &path, 0);
+ if (rc) {
+ pr_err("cannot create vfs path\n");
+ return -EIO;
+ }
+
+ rc = vfs_statfs(&path, &stfs);
+ if (rc) {
+ pr_err("cannot do stat of path %s\n", share->path);
+ path_put(&path);
+ return -EIO;
+ }
+
+ fsinfoclass = req->FileInfoClass;
+
+ switch (fsinfoclass) {
+ case FS_DEVICE_INFORMATION:
+ {
+ struct filesystem_device_info *info;
+
+ info = (struct filesystem_device_info *)rsp->Buffer;
+
+ info->DeviceType = cpu_to_le32(stfs.f_type);
+ info->DeviceCharacteristics = cpu_to_le32(0x00000020);
+ rsp->OutputBufferLength = cpu_to_le32(8);
+ inc_rfc1001_len(rsp_org, 8);
+ fs_infoclass_size = FS_DEVICE_INFORMATION_SIZE;
+ break;
+ }
+ case FS_ATTRIBUTE_INFORMATION:
+ {
+ struct filesystem_attribute_info *info;
+ size_t sz;
+
+ info = (struct filesystem_attribute_info *)rsp->Buffer;
+ info->Attributes = cpu_to_le32(FILE_SUPPORTS_OBJECT_IDS |
+ FILE_PERSISTENT_ACLS |
+ FILE_UNICODE_ON_DISK |
+ FILE_CASE_PRESERVED_NAMES |
+ FILE_CASE_SENSITIVE_SEARCH |
+ FILE_SUPPORTS_BLOCK_REFCOUNTING);
+
+ info->Attributes |= cpu_to_le32(server_conf.share_fake_fscaps);
+
+ info->MaxPathNameComponentLength = cpu_to_le32(stfs.f_namelen);
+ len = smbConvertToUTF16((__le16 *)info->FileSystemName,
+ "NTFS", PATH_MAX, conn->local_nls, 0);
+ len = len * 2;
+ info->FileSystemNameLen = cpu_to_le32(len);
+ sz = sizeof(struct filesystem_attribute_info) - 2 + len;
+ rsp->OutputBufferLength = cpu_to_le32(sz);
+ inc_rfc1001_len(rsp_org, sz);
+ fs_infoclass_size = FS_ATTRIBUTE_INFORMATION_SIZE;
+ break;
+ }
+ case FS_VOLUME_INFORMATION:
+ {
+ struct filesystem_vol_info *info;
+ size_t sz;
+
+ info = (struct filesystem_vol_info *)(rsp->Buffer);
+ info->VolumeCreationTime = 0;
+ /* Taking dummy value of serial number*/
+ info->SerialNumber = cpu_to_le32(0xbc3ac512);
+ len = smbConvertToUTF16((__le16 *)info->VolumeLabel,
+ share->name, PATH_MAX,
+ conn->local_nls, 0);
+ len = len * 2;
+ info->VolumeLabelSize = cpu_to_le32(len);
+ info->Reserved = 0;
+ sz = sizeof(struct filesystem_vol_info) - 2 + len;
+ rsp->OutputBufferLength = cpu_to_le32(sz);
+ inc_rfc1001_len(rsp_org, sz);
+ fs_infoclass_size = FS_VOLUME_INFORMATION_SIZE;
+ break;
+ }
+ case FS_SIZE_INFORMATION:
+ {
+ struct filesystem_info *info;
+
+ info = (struct filesystem_info *)(rsp->Buffer);
+ info->TotalAllocationUnits = cpu_to_le64(stfs.f_blocks);
+ info->FreeAllocationUnits = cpu_to_le64(stfs.f_bfree);
+ info->SectorsPerAllocationUnit = cpu_to_le32(1);
+ info->BytesPerSector = cpu_to_le32(stfs.f_bsize);
+ rsp->OutputBufferLength = cpu_to_le32(24);
+ inc_rfc1001_len(rsp_org, 24);
+ fs_infoclass_size = FS_SIZE_INFORMATION_SIZE;
+ break;
+ }
+ case FS_FULL_SIZE_INFORMATION:
+ {
+ struct smb2_fs_full_size_info *info;
+
+ info = (struct smb2_fs_full_size_info *)(rsp->Buffer);
+ info->TotalAllocationUnits = cpu_to_le64(stfs.f_blocks);
+ info->CallerAvailableAllocationUnits =
+ cpu_to_le64(stfs.f_bavail);
+ info->ActualAvailableAllocationUnits =
+ cpu_to_le64(stfs.f_bfree);
+ info->SectorsPerAllocationUnit = cpu_to_le32(1);
+ info->BytesPerSector = cpu_to_le32(stfs.f_bsize);
+ rsp->OutputBufferLength = cpu_to_le32(32);
+ inc_rfc1001_len(rsp_org, 32);
+ fs_infoclass_size = FS_FULL_SIZE_INFORMATION_SIZE;
+ break;
+ }
+ case FS_OBJECT_ID_INFORMATION:
+ {
+ struct object_id_info *info;
+
+ info = (struct object_id_info *)(rsp->Buffer);
+
+ if (!user_guest(sess->user))
+ memcpy(info->objid, user_passkey(sess->user), 16);
+ else
+ memset(info->objid, 0, 16);
+
+ info->extended_info.magic = cpu_to_le32(EXTENDED_INFO_MAGIC);
+ info->extended_info.version = cpu_to_le32(1);
+ info->extended_info.release = cpu_to_le32(1);
+ info->extended_info.rel_date = 0;
+ memcpy(info->extended_info.version_string, "1.1.0", strlen("1.1.0"));
+ rsp->OutputBufferLength = cpu_to_le32(64);
+ inc_rfc1001_len(rsp_org, 64);
+ fs_infoclass_size = FS_OBJECT_ID_INFORMATION_SIZE;
+ break;
+ }
+ case FS_SECTOR_SIZE_INFORMATION:
+ {
+ struct smb3_fs_ss_info *info;
+
+ info = (struct smb3_fs_ss_info *)(rsp->Buffer);
+
+ info->LogicalBytesPerSector = cpu_to_le32(stfs.f_bsize);
+ info->PhysicalBytesPerSectorForAtomicity =
+ cpu_to_le32(stfs.f_bsize);
+ info->PhysicalBytesPerSectorForPerf = cpu_to_le32(stfs.f_bsize);
+ info->FSEffPhysicalBytesPerSectorForAtomicity =
+ cpu_to_le32(stfs.f_bsize);
+ info->Flags = cpu_to_le32(SSINFO_FLAGS_ALIGNED_DEVICE |
+ SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE);
+ info->ByteOffsetForSectorAlignment = 0;
+ info->ByteOffsetForPartitionAlignment = 0;
+ rsp->OutputBufferLength = cpu_to_le32(28);
+ inc_rfc1001_len(rsp_org, 28);
+ fs_infoclass_size = FS_SECTOR_SIZE_INFORMATION_SIZE;
+ break;
+ }
+ case FS_CONTROL_INFORMATION:
+ {
+ /*
+ * TODO : The current implementation is based on
+ * test result with win7(NTFS) server. It's need to
+ * modify this to get valid Quota values
+ * from Linux kernel
+ */
+ struct smb2_fs_control_info *info;
+
+ info = (struct smb2_fs_control_info *)(rsp->Buffer);
+ info->FreeSpaceStartFiltering = 0;
+ info->FreeSpaceThreshold = 0;
+ info->FreeSpaceStopFiltering = 0;
+ info->DefaultQuotaThreshold = cpu_to_le64(SMB2_NO_FID);
+ info->DefaultQuotaLimit = cpu_to_le64(SMB2_NO_FID);
+ info->Padding = 0;
+ rsp->OutputBufferLength = cpu_to_le32(48);
+ inc_rfc1001_len(rsp_org, 48);
+ fs_infoclass_size = FS_CONTROL_INFORMATION_SIZE;
+ break;
+ }
+ case FS_POSIX_INFORMATION:
+ {
+ struct filesystem_posix_info *info;
+
+ if (!work->tcon->posix_extensions) {
+ pr_err("client doesn't negotiate with SMB3.1.1 POSIX Extensions\n");
+ rc = -EOPNOTSUPP;
+ } else {
+ info = (struct filesystem_posix_info *)(rsp->Buffer);
+ info->OptimalTransferSize = cpu_to_le32(stfs.f_bsize);
+ info->BlockSize = cpu_to_le32(stfs.f_bsize);
+ info->TotalBlocks = cpu_to_le64(stfs.f_blocks);
+ info->BlocksAvail = cpu_to_le64(stfs.f_bfree);
+ info->UserBlocksAvail = cpu_to_le64(stfs.f_bavail);
+ info->TotalFileNodes = cpu_to_le64(stfs.f_files);
+ info->FreeFileNodes = cpu_to_le64(stfs.f_ffree);
+ rsp->OutputBufferLength = cpu_to_le32(56);
+ inc_rfc1001_len(rsp_org, 56);
+ fs_infoclass_size = FS_POSIX_INFORMATION_SIZE;
+ }
+ break;
+ }
+ default:
+ path_put(&path);
+ return -EOPNOTSUPP;
+ }
+ rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
+ rsp,
+ fs_infoclass_size);
+ path_put(&path);
+ return rc;
+}
+
+static int smb2_get_info_sec(struct ksmbd_work *work,
+ struct smb2_query_info_req *req,
+ struct smb2_query_info_rsp *rsp, void *rsp_org)
+{
+ struct ksmbd_file *fp;
+ struct user_namespace *user_ns;
+ struct smb_ntsd *pntsd = (struct smb_ntsd *)rsp->Buffer, *ppntsd = NULL;
+ struct smb_fattr fattr = {{0}};
+ struct inode *inode;
+ __u32 secdesclen;
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
+ int addition_info = le32_to_cpu(req->AdditionalInformation);
+ int rc;
+
+ if (addition_info & ~(OWNER_SECINFO | GROUP_SECINFO | DACL_SECINFO |
+ PROTECTED_DACL_SECINFO |
+ UNPROTECTED_DACL_SECINFO)) {
+ pr_err("Unsupported addition info: 0x%x)\n",
+ addition_info);
+
+ pntsd->revision = cpu_to_le16(1);
+ pntsd->type = cpu_to_le16(SELF_RELATIVE | DACL_PROTECTED);
+ pntsd->osidoffset = 0;
+ pntsd->gsidoffset = 0;
+ pntsd->sacloffset = 0;
+ pntsd->dacloffset = 0;
+
+ secdesclen = sizeof(struct smb_ntsd);
+ rsp->OutputBufferLength = cpu_to_le32(secdesclen);
+ inc_rfc1001_len(rsp_org, secdesclen);
+
+ return 0;
+ }
+
+ if (work->next_smb2_rcv_hdr_off) {
+ if (!has_file_id(le64_to_cpu(req->VolatileFileId))) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+ }
+
+ if (!has_file_id(id)) {
+ id = le64_to_cpu(req->VolatileFileId);
+ pid = le64_to_cpu(req->PersistentFileId);
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
+ if (!fp)
+ return -ENOENT;
+
+ user_ns = file_mnt_user_ns(fp->filp);
+ inode = file_inode(fp->filp);
+ ksmbd_acls_fattr(&fattr, inode);
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ACL_XATTR))
+ ksmbd_vfs_get_sd_xattr(work->conn, user_ns,
+ fp->filp->f_path.dentry, &ppntsd);
+
+ rc = build_sec_desc(user_ns, pntsd, ppntsd, addition_info,
+ &secdesclen, &fattr);
+ posix_acl_release(fattr.cf_acls);
+ posix_acl_release(fattr.cf_dacls);
+ kfree(ppntsd);
+ ksmbd_fd_put(work, fp);
+ if (rc)
+ return rc;
+
+ rsp->OutputBufferLength = cpu_to_le32(secdesclen);
+ inc_rfc1001_len(rsp_org, secdesclen);
+ return 0;
+}
+
+/**
+ * smb2_query_info() - handler for smb2 query info command
+ * @work: smb work containing query info request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_query_info(struct ksmbd_work *work)
+{
+ struct smb2_query_info_req *req;
+ struct smb2_query_info_rsp *rsp, *rsp_org;
+ int rc = 0;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ ksmbd_debug(SMB, "GOT query info request\n");
+
+ switch (req->InfoType) {
+ case SMB2_O_INFO_FILE:
+ ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILE\n");
+ rc = smb2_get_info_file(work, req, rsp, (void *)rsp_org);
+ break;
+ case SMB2_O_INFO_FILESYSTEM:
+ ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILESYSTEM\n");
+ rc = smb2_get_info_filesystem(work, req, rsp, (void *)rsp_org);
+ break;
+ case SMB2_O_INFO_SECURITY:
+ ksmbd_debug(SMB, "GOT SMB2_O_INFO_SECURITY\n");
+ rc = smb2_get_info_sec(work, req, rsp, (void *)rsp_org);
+ break;
+ default:
+ ksmbd_debug(SMB, "InfoType %d not supported yet\n",
+ req->InfoType);
+ rc = -EOPNOTSUPP;
+ }
+
+ if (rc < 0) {
+ if (rc == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (rc == -ENOENT)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ else if (rc == -EIO)
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+ else if (rc == -EOPNOTSUPP || rsp->hdr.Status == 0)
+ rsp->hdr.Status = STATUS_INVALID_INFO_CLASS;
+ smb2_set_err_rsp(work);
+
+ ksmbd_debug(SMB, "error while processing smb2 query rc = %d\n",
+ rc);
+ return rc;
+ }
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->OutputBufferOffset = cpu_to_le16(72);
+ inc_rfc1001_len(rsp_org, 8);
+ return 0;
+}
+
+/**
+ * smb2_close_pipe() - handler for closing IPC pipe
+ * @work: smb work containing close request buffer
+ *
+ * Return: 0
+ */
+static noinline int smb2_close_pipe(struct ksmbd_work *work)
+{
+ u64 id;
+ struct smb2_close_req *req = work->request_buf;
+ struct smb2_close_rsp *rsp = work->response_buf;
+
+ id = le64_to_cpu(req->VolatileFileId);
+ ksmbd_session_rpc_close(work->sess, id);
+
+ rsp->StructureSize = cpu_to_le16(60);
+ rsp->Flags = 0;
+ rsp->Reserved = 0;
+ rsp->CreationTime = 0;
+ rsp->LastAccessTime = 0;
+ rsp->LastWriteTime = 0;
+ rsp->ChangeTime = 0;
+ rsp->AllocationSize = 0;
+ rsp->EndOfFile = 0;
+ rsp->Attributes = 0;
+ inc_rfc1001_len(rsp, 60);
+ return 0;
+}
+
+/**
+ * smb2_close() - handler for smb2 close file command
+ * @work: smb work containing close request buffer
+ *
+ * Return: 0
+ */
+int smb2_close(struct ksmbd_work *work)
+{
+ u64 volatile_id = KSMBD_NO_FID;
+ u64 sess_id;
+ struct smb2_close_req *req;
+ struct smb2_close_rsp *rsp;
+ struct smb2_close_rsp *rsp_org;
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_file *fp;
+ struct inode *inode;
+ u64 time;
+ int err = 0;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_PIPE)) {
+ ksmbd_debug(SMB, "IPC pipe close request\n");
+ return smb2_close_pipe(work);
+ }
+
+ sess_id = le64_to_cpu(req->hdr.SessionId);
+ if (req->hdr.Flags & SMB2_FLAGS_RELATED_OPERATIONS)
+ sess_id = work->compound_sid;
+
+ work->compound_sid = 0;
+ if (check_session_id(conn, sess_id)) {
+ work->compound_sid = sess_id;
+ } else {
+ rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
+ if (req->hdr.Flags & SMB2_FLAGS_RELATED_OPERATIONS)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ err = -EBADF;
+ goto out;
+ }
+
+ if (work->next_smb2_rcv_hdr_off &&
+ !has_file_id(le64_to_cpu(req->VolatileFileId))) {
+ if (!has_file_id(work->compound_fid)) {
+ /* file already closed, return FILE_CLOSED */
+ ksmbd_debug(SMB, "file already closed\n");
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ err = -EBADF;
+ goto out;
+ } else {
+ ksmbd_debug(SMB,
+ "Compound request set FID = %llu:%llu\n",
+ work->compound_fid,
+ work->compound_pfid);
+ volatile_id = work->compound_fid;
+
+ /* file closed, stored id is not valid anymore */
+ work->compound_fid = KSMBD_NO_FID;
+ work->compound_pfid = KSMBD_NO_FID;
+ }
+ } else {
+ volatile_id = le64_to_cpu(req->VolatileFileId);
+ }
+ ksmbd_debug(SMB, "volatile_id = %llu\n", volatile_id);
+
+ rsp->StructureSize = cpu_to_le16(60);
+ rsp->Reserved = 0;
+
+ if (req->Flags == SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB) {
+ fp = ksmbd_lookup_fd_fast(work, volatile_id);
+ if (!fp) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ inode = file_inode(fp->filp);
+ rsp->Flags = SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB;
+ rsp->AllocationSize = S_ISDIR(inode->i_mode) ? 0 :
+ cpu_to_le64(inode->i_blocks << 9);
+ rsp->EndOfFile = cpu_to_le64(inode->i_size);
+ rsp->Attributes = fp->f_ci->m_fattr;
+ rsp->CreationTime = cpu_to_le64(fp->create_time);
+ time = ksmbd_UnixTimeToNT(inode->i_atime);
+ rsp->LastAccessTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(inode->i_mtime);
+ rsp->LastWriteTime = cpu_to_le64(time);
+ time = ksmbd_UnixTimeToNT(inode->i_ctime);
+ rsp->ChangeTime = cpu_to_le64(time);
+ ksmbd_fd_put(work, fp);
+ } else {
+ rsp->Flags = 0;
+ rsp->AllocationSize = 0;
+ rsp->EndOfFile = 0;
+ rsp->Attributes = 0;
+ rsp->CreationTime = 0;
+ rsp->LastAccessTime = 0;
+ rsp->LastWriteTime = 0;
+ rsp->ChangeTime = 0;
+ }
+
+ err = ksmbd_close_fd(work, volatile_id);
+out:
+ if (err) {
+ if (rsp->hdr.Status == 0)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ } else {
+ inc_rfc1001_len(rsp_org, 60);
+ }
+
+ return 0;
+}
+
+/**
+ * smb2_echo() - handler for smb2 echo(ping) command
+ * @work: smb work containing echo request buffer
+ *
+ * Return: 0
+ */
+int smb2_echo(struct ksmbd_work *work)
+{
+ struct smb2_echo_rsp *rsp = work->response_buf;
+
+ rsp->StructureSize = cpu_to_le16(4);
+ rsp->Reserved = 0;
+ inc_rfc1001_len(rsp, 4);
+ return 0;
+}
+
+static int smb2_rename(struct ksmbd_work *work, struct ksmbd_file *fp,
+ struct smb2_file_rename_info *file_info,
+ struct nls_table *local_nls)
+{
+ struct ksmbd_share_config *share = fp->tcon->share_conf;
+ char *new_name = NULL, *abs_oldname = NULL, *old_name = NULL;
+ char *pathname = NULL;
+ struct path path;
+ bool file_present = true;
+ int rc;
+
+ ksmbd_debug(SMB, "setting FILE_RENAME_INFO\n");
+ pathname = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!pathname)
+ return -ENOMEM;
+
+ abs_oldname = d_path(&fp->filp->f_path, pathname, PATH_MAX);
+ if (IS_ERR(abs_oldname)) {
+ rc = -EINVAL;
+ goto out;
+ }
+ old_name = strrchr(abs_oldname, '/');
+ if (old_name && old_name[1] != '\0') {
+ old_name++;
+ } else {
+ ksmbd_debug(SMB, "can't get last component in path %s\n",
+ abs_oldname);
+ rc = -ENOENT;
+ goto out;
+ }
+
+ new_name = smb2_get_name(share,
+ file_info->FileName,
+ le32_to_cpu(file_info->FileNameLength),
+ local_nls);
+ if (IS_ERR(new_name)) {
+ rc = PTR_ERR(new_name);
+ goto out;
+ }
+
+ if (strchr(new_name, ':')) {
+ int s_type;
+ char *xattr_stream_name, *stream_name = NULL;
+ size_t xattr_stream_size;
+ int len;
+
+ rc = parse_stream_name(new_name, &stream_name, &s_type);
+ if (rc < 0)
+ goto out;
+
+ len = strlen(new_name);
+ if (new_name[len - 1] != '/') {
+ pr_err("not allow base filename in rename\n");
+ rc = -ESHARE;
+ goto out;
+ }
+
+ rc = ksmbd_vfs_xattr_stream_name(stream_name,
+ &xattr_stream_name,
+ &xattr_stream_size,
+ s_type);
+ if (rc)
+ goto out;
+
+ rc = ksmbd_vfs_setxattr(file_mnt_user_ns(fp->filp),
+ fp->filp->f_path.dentry,
+ xattr_stream_name,
+ NULL, 0, 0);
+ if (rc < 0) {
+ pr_err("failed to store stream name in xattr: %d\n",
+ rc);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ goto out;
+ }
+
+ ksmbd_debug(SMB, "new name %s\n", new_name);
+ rc = ksmbd_vfs_kern_path(new_name, 0, &path, 1);
+ if (rc)
+ file_present = false;
+ else
+ path_put(&path);
+
+ if (ksmbd_share_veto_filename(share, new_name)) {
+ rc = -ENOENT;
+ ksmbd_debug(SMB, "Can't rename vetoed file: %s\n", new_name);
+ goto out;
+ }
+
+ if (file_info->ReplaceIfExists) {
+ if (file_present) {
+ rc = ksmbd_vfs_remove_file(work, new_name);
+ if (rc) {
+ if (rc != -ENOTEMPTY)
+ rc = -EINVAL;
+ ksmbd_debug(SMB, "cannot delete %s, rc %d\n",
+ new_name, rc);
+ goto out;
+ }
+ }
+ } else {
+ if (file_present &&
+ strncmp(old_name, path.dentry->d_name.name, strlen(old_name))) {
+ rc = -EEXIST;
+ ksmbd_debug(SMB,
+ "cannot rename already existing file\n");
+ goto out;
+ }
+ }
+
+ rc = ksmbd_vfs_fp_rename(work, fp, new_name);
+out:
+ kfree(pathname);
+ if (!IS_ERR(new_name))
+ kfree(new_name);
+ return rc;
+}
+
+static int smb2_create_link(struct ksmbd_work *work,
+ struct ksmbd_share_config *share,
+ struct smb2_file_link_info *file_info,
+ struct file *filp,
+ struct nls_table *local_nls)
+{
+ char *link_name = NULL, *target_name = NULL, *pathname = NULL;
+ struct path path;
+ bool file_present = true;
+ int rc;
+
+ ksmbd_debug(SMB, "setting FILE_LINK_INFORMATION\n");
+ pathname = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!pathname)
+ return -ENOMEM;
+
+ link_name = smb2_get_name(share,
+ file_info->FileName,
+ le32_to_cpu(file_info->FileNameLength),
+ local_nls);
+ if (IS_ERR(link_name) || S_ISDIR(file_inode(filp)->i_mode)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ ksmbd_debug(SMB, "link name is %s\n", link_name);
+ target_name = d_path(&filp->f_path, pathname, PATH_MAX);
+ if (IS_ERR(target_name)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ ksmbd_debug(SMB, "target name is %s\n", target_name);
+ rc = ksmbd_vfs_kern_path(link_name, 0, &path, 0);
+ if (rc)
+ file_present = false;
+ else
+ path_put(&path);
+
+ if (file_info->ReplaceIfExists) {
+ if (file_present) {
+ rc = ksmbd_vfs_remove_file(work, link_name);
+ if (rc) {
+ rc = -EINVAL;
+ ksmbd_debug(SMB, "cannot delete %s\n",
+ link_name);
+ goto out;
+ }
+ }
+ } else {
+ if (file_present) {
+ rc = -EEXIST;
+ ksmbd_debug(SMB, "link already exists\n");
+ goto out;
+ }
+ }
+
+ rc = ksmbd_vfs_link(work, target_name, link_name);
+ if (rc)
+ rc = -EINVAL;
+out:
+ if (!IS_ERR(link_name))
+ kfree(link_name);
+ kfree(pathname);
+ return rc;
+}
+
+static int set_file_basic_info(struct ksmbd_file *fp, char *buf,
+ struct ksmbd_share_config *share)
+{
+ struct smb2_file_all_info *file_info;
+ struct iattr attrs;
+ struct iattr temp_attrs;
+ struct file *filp;
+ struct inode *inode;
+ struct user_namespace *user_ns;
+ int rc;
+
+ if (!(fp->daccess & FILE_WRITE_ATTRIBUTES_LE))
+ return -EACCES;
+
+ file_info = (struct smb2_file_all_info *)buf;
+ attrs.ia_valid = 0;
+ filp = fp->filp;
+ inode = file_inode(filp);
+ user_ns = file_mnt_user_ns(filp);
+
+ if (file_info->CreationTime)
+ fp->create_time = le64_to_cpu(file_info->CreationTime);
+
+ if (file_info->LastAccessTime) {
+ attrs.ia_atime = ksmbd_NTtimeToUnix(file_info->LastAccessTime);
+ attrs.ia_valid |= (ATTR_ATIME | ATTR_ATIME_SET);
+ }
+
+ if (file_info->ChangeTime) {
+ temp_attrs.ia_ctime = ksmbd_NTtimeToUnix(file_info->ChangeTime);
+ attrs.ia_ctime = temp_attrs.ia_ctime;
+ attrs.ia_valid |= ATTR_CTIME;
+ } else {
+ temp_attrs.ia_ctime = inode->i_ctime;
+ }
+
+ if (file_info->LastWriteTime) {
+ attrs.ia_mtime = ksmbd_NTtimeToUnix(file_info->LastWriteTime);
+ attrs.ia_valid |= (ATTR_MTIME | ATTR_MTIME_SET);
+ }
+
+ if (file_info->Attributes) {
+ if (!S_ISDIR(inode->i_mode) &&
+ file_info->Attributes & ATTR_DIRECTORY_LE) {
+ pr_err("can't change a file to a directory\n");
+ return -EINVAL;
+ }
+
+ if (!(S_ISDIR(inode->i_mode) && file_info->Attributes == ATTR_NORMAL_LE))
+ fp->f_ci->m_fattr = file_info->Attributes |
+ (fp->f_ci->m_fattr & ATTR_DIRECTORY_LE);
+ }
+
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_STORE_DOS_ATTRS) &&
+ (file_info->CreationTime || file_info->Attributes)) {
+ struct xattr_dos_attrib da = {0};
+
+ da.version = 4;
+ da.itime = fp->itime;
+ da.create_time = fp->create_time;
+ da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+ da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
+ XATTR_DOSINFO_ITIME;
+
+ rc = ksmbd_vfs_set_dos_attrib_xattr(user_ns,
+ filp->f_path.dentry, &da);
+ if (rc)
+ ksmbd_debug(SMB,
+ "failed to restore file attribute in EA\n");
+ rc = 0;
+ }
+
+ /*
+ * HACK : set ctime here to avoid ctime changed
+ * when file_info->ChangeTime is zero.
+ */
+ attrs.ia_ctime = temp_attrs.ia_ctime;
+ attrs.ia_valid |= ATTR_CTIME;
+
+ if (attrs.ia_valid) {
+ struct dentry *dentry = filp->f_path.dentry;
+ struct inode *inode = d_inode(dentry);
+
+ if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+ return -EACCES;
+
+ rc = setattr_prepare(user_ns, dentry, &attrs);
+ if (rc)
+ return -EINVAL;
+
+ inode_lock(inode);
+ setattr_copy(user_ns, inode, &attrs);
+ attrs.ia_valid &= ~ATTR_CTIME;
+ rc = notify_change(user_ns, dentry, &attrs, NULL);
+ inode_unlock(inode);
+ }
+ return 0;
+}
+
+static int set_file_allocation_info(struct ksmbd_work *work,
+ struct ksmbd_file *fp, char *buf)
+{
+ /*
+ * TODO : It's working fine only when store dos attributes
+ * is not yes. need to implement a logic which works
+ * properly with any smb.conf option
+ */
+
+ struct smb2_file_alloc_info *file_alloc_info;
+ loff_t alloc_blks;
+ struct inode *inode;
+ int rc;
+
+ if (!(fp->daccess & FILE_WRITE_DATA_LE))
+ return -EACCES;
+
+ file_alloc_info = (struct smb2_file_alloc_info *)buf;
+ alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9;
+ inode = file_inode(fp->filp);
+
+ if (alloc_blks > inode->i_blocks) {
+ smb_break_all_levII_oplock(work, fp, 1);
+ rc = vfs_fallocate(fp->filp, FALLOC_FL_KEEP_SIZE, 0,
+ alloc_blks * 512);
+ if (rc && rc != -EOPNOTSUPP) {
+ pr_err("vfs_fallocate is failed : %d\n", rc);
+ return rc;
+ }
+ } else if (alloc_blks < inode->i_blocks) {
+ loff_t size;
+
+ /*
+ * Allocation size could be smaller than original one
+ * which means allocated blocks in file should be
+ * deallocated. use truncate to cut out it, but inode
+ * size is also updated with truncate offset.
+ * inode size is retained by backup inode size.
+ */
+ size = i_size_read(inode);
+ rc = ksmbd_vfs_truncate(work, NULL, fp, alloc_blks * 512);
+ if (rc) {
+ pr_err("truncate failed! filename : %s, err %d\n",
+ fp->filename, rc);
+ return rc;
+ }
+ if (size < alloc_blks * 512)
+ i_size_write(inode, size);
+ }
+ return 0;
+}
+
+static int set_end_of_file_info(struct ksmbd_work *work, struct ksmbd_file *fp,
+ char *buf)
+{
+ struct smb2_file_eof_info *file_eof_info;
+ loff_t newsize;
+ struct inode *inode;
+ int rc;
+
+ if (!(fp->daccess & FILE_WRITE_DATA_LE))
+ return -EACCES;
+
+ file_eof_info = (struct smb2_file_eof_info *)buf;
+ newsize = le64_to_cpu(file_eof_info->EndOfFile);
+ inode = file_inode(fp->filp);
+
+ /*
+ * If FILE_END_OF_FILE_INFORMATION of set_info_file is called
+ * on FAT32 shared device, truncate execution time is too long
+ * and network error could cause from windows client. because
+ * truncate of some filesystem like FAT32 fill zero data in
+ * truncated range.
+ */
+ if (inode->i_sb->s_magic != MSDOS_SUPER_MAGIC) {
+ ksmbd_debug(SMB, "filename : %s truncated to newsize %lld\n",
+ fp->filename, newsize);
+ rc = ksmbd_vfs_truncate(work, NULL, fp, newsize);
+ if (rc) {
+ ksmbd_debug(SMB, "truncate failed! filename : %s err %d\n",
+ fp->filename, rc);
+ if (rc != -EAGAIN)
+ rc = -EBADF;
+ return rc;
+ }
+ }
+ return 0;
+}
+
+static int set_rename_info(struct ksmbd_work *work, struct ksmbd_file *fp,
+ char *buf)
+{
+ struct ksmbd_file *parent_fp;
+ struct dentry *parent;
+ struct dentry *dentry = fp->filp->f_path.dentry;
+ int ret;
+
+ if (!(fp->daccess & FILE_DELETE_LE)) {
+ pr_err("no right to delete : 0x%x\n", fp->daccess);
+ return -EACCES;
+ }
+
+ if (ksmbd_stream_fd(fp))
+ goto next;
+
+ parent = dget_parent(dentry);
+ ret = ksmbd_vfs_lock_parent(parent, dentry);
+ if (ret) {
+ dput(parent);
+ return ret;
+ }
+
+ parent_fp = ksmbd_lookup_fd_inode(d_inode(parent));
+ inode_unlock(d_inode(parent));
+ dput(parent);
+
+ if (parent_fp) {
+ if (parent_fp->daccess & FILE_DELETE_LE) {
+ pr_err("parent dir is opened with delete access\n");
+ return -ESHARE;
+ }
+ }
+next:
+ return smb2_rename(work, fp,
+ (struct smb2_file_rename_info *)buf,
+ work->sess->conn->local_nls);
+}
+
+static int set_file_disposition_info(struct ksmbd_file *fp, char *buf)
+{
+ struct smb2_file_disposition_info *file_info;
+ struct inode *inode;
+
+ if (!(fp->daccess & FILE_DELETE_LE)) {
+ pr_err("no right to delete : 0x%x\n", fp->daccess);
+ return -EACCES;
+ }
+
+ inode = file_inode(fp->filp);
+ file_info = (struct smb2_file_disposition_info *)buf;
+ if (file_info->DeletePending) {
+ if (S_ISDIR(inode->i_mode) &&
+ ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
+ return -EBUSY;
+ ksmbd_set_inode_pending_delete(fp);
+ } else {
+ ksmbd_clear_inode_pending_delete(fp);
+ }
+ return 0;
+}
+
+static int set_file_position_info(struct ksmbd_file *fp, char *buf)
+{
+ struct smb2_file_pos_info *file_info;
+ loff_t current_byte_offset;
+ unsigned long sector_size;
+ struct inode *inode;
+
+ inode = file_inode(fp->filp);
+ file_info = (struct smb2_file_pos_info *)buf;
+ current_byte_offset = le64_to_cpu(file_info->CurrentByteOffset);
+ sector_size = inode->i_sb->s_blocksize;
+
+ if (current_byte_offset < 0 ||
+ (fp->coption == FILE_NO_INTERMEDIATE_BUFFERING_LE &&
+ current_byte_offset & (sector_size - 1))) {
+ pr_err("CurrentByteOffset is not valid : %llu\n",
+ current_byte_offset);
+ return -EINVAL;
+ }
+
+ fp->filp->f_pos = current_byte_offset;
+ return 0;
+}
+
+static int set_file_mode_info(struct ksmbd_file *fp, char *buf)
+{
+ struct smb2_file_mode_info *file_info;
+ __le32 mode;
+
+ file_info = (struct smb2_file_mode_info *)buf;
+ mode = file_info->Mode;
+
+ if ((mode & ~FILE_MODE_INFO_MASK) ||
+ (mode & FILE_SYNCHRONOUS_IO_ALERT_LE &&
+ mode & FILE_SYNCHRONOUS_IO_NONALERT_LE)) {
+ pr_err("Mode is not valid : 0x%x\n", le32_to_cpu(mode));
+ return -EINVAL;
+ }
+
+ /*
+ * TODO : need to implement consideration for
+ * FILE_SYNCHRONOUS_IO_ALERT and FILE_SYNCHRONOUS_IO_NONALERT
+ */
+ ksmbd_vfs_set_fadvise(fp->filp, mode);
+ fp->coption = mode;
+ return 0;
+}
+
+/**
+ * smb2_set_info_file() - handler for smb2 set info command
+ * @work: smb work containing set info command buffer
+ * @fp: ksmbd_file pointer
+ * @info_class: smb2 set info class
+ * @share: ksmbd_share_config pointer
+ *
+ * Return: 0 on success, otherwise error
+ * TODO: need to implement an error handling for STATUS_INFO_LENGTH_MISMATCH
+ */
+static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
+ int info_class, char *buf,
+ struct ksmbd_share_config *share)
+{
+ switch (info_class) {
+ case FILE_BASIC_INFORMATION:
+ return set_file_basic_info(fp, buf, share);
+
+ case FILE_ALLOCATION_INFORMATION:
+ return set_file_allocation_info(work, fp, buf);
+
+ case FILE_END_OF_FILE_INFORMATION:
+ return set_end_of_file_info(work, fp, buf);
+
+ case FILE_RENAME_INFORMATION:
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ return -EACCES;
+ }
+ return set_rename_info(work, fp, buf);
+
+ case FILE_LINK_INFORMATION:
+ return smb2_create_link(work, work->tcon->share_conf,
+ (struct smb2_file_link_info *)buf, fp->filp,
+ work->sess->conn->local_nls);
+
+ case FILE_DISPOSITION_INFORMATION:
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ return -EACCES;
+ }
+ return set_file_disposition_info(fp, buf);
+
+ case FILE_FULL_EA_INFORMATION:
+ {
+ if (!(fp->daccess & FILE_WRITE_EA_LE)) {
+ pr_err("Not permitted to write ext attr: 0x%x\n",
+ fp->daccess);
+ return -EACCES;
+ }
+
+ return smb2_set_ea((struct smb2_ea_info *)buf,
+ &fp->filp->f_path);
+ }
+
+ case FILE_POSITION_INFORMATION:
+ return set_file_position_info(fp, buf);
+
+ case FILE_MODE_INFORMATION:
+ return set_file_mode_info(fp, buf);
+ }
+
+ pr_err("Unimplemented Fileinfoclass :%d\n", info_class);
+ return -EOPNOTSUPP;
+}
+
+static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
+ char *buffer, int buf_len)
+{
+ struct smb_ntsd *pntsd = (struct smb_ntsd *)buffer;
+
+ fp->saccess |= FILE_SHARE_DELETE_LE;
+
+ return set_info_sec(fp->conn, fp->tcon, &fp->filp->f_path, pntsd,
+ buf_len, false);
+}
+
+/**
+ * smb2_set_info() - handler for smb2 set info command handler
+ * @work: smb work containing set info request buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_set_info(struct ksmbd_work *work)
+{
+ struct smb2_set_info_req *req;
+ struct smb2_set_info_rsp *rsp, *rsp_org;
+ struct ksmbd_file *fp;
+ int rc = 0;
+ unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
+
+ ksmbd_debug(SMB, "Received set info request\n");
+
+ rsp_org = work->response_buf;
+ if (work->next_smb2_rcv_hdr_off) {
+ req = ksmbd_req_buf_next(work);
+ rsp = ksmbd_resp_buf_next(work);
+ if (!has_file_id(le64_to_cpu(req->VolatileFileId))) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ pid = work->compound_pfid;
+ }
+ } else {
+ req = work->request_buf;
+ rsp = work->response_buf;
+ }
+
+ if (!has_file_id(id)) {
+ id = le64_to_cpu(req->VolatileFileId);
+ pid = le64_to_cpu(req->PersistentFileId);
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, id, pid);
+ if (!fp) {
+ ksmbd_debug(SMB, "Invalid id for close: %u\n", id);
+ rc = -ENOENT;
+ goto err_out;
+ }
+
+ switch (req->InfoType) {
+ case SMB2_O_INFO_FILE:
+ ksmbd_debug(SMB, "GOT SMB2_O_INFO_FILE\n");
+ rc = smb2_set_info_file(work, fp, req->FileInfoClass,
+ req->Buffer, work->tcon->share_conf);
+ break;
+ case SMB2_O_INFO_SECURITY:
+ ksmbd_debug(SMB, "GOT SMB2_O_INFO_SECURITY\n");
+ if (ksmbd_override_fsids(work)) {
+ rc = -ENOMEM;
+ goto err_out;
+ }
+ rc = smb2_set_info_sec(fp,
+ le32_to_cpu(req->AdditionalInformation),
+ req->Buffer,
+ le32_to_cpu(req->BufferLength));
+ ksmbd_revert_fsids(work);
+ break;
+ default:
+ rc = -EOPNOTSUPP;
+ }
+
+ if (rc < 0)
+ goto err_out;
+
+ rsp->StructureSize = cpu_to_le16(2);
+ inc_rfc1001_len(rsp_org, 2);
+ ksmbd_fd_put(work, fp);
+ return 0;
+
+err_out:
+ if (rc == -EACCES || rc == -EPERM)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (rc == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (rc == -ESHARE)
+ rsp->hdr.Status = STATUS_SHARING_VIOLATION;
+ else if (rc == -ENOENT)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
+ else if (rc == -EBUSY || rc == -ENOTEMPTY)
+ rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
+ else if (rc == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ else if (rc == -EBADF || rc == -ESTALE)
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ else if (rc == -EEXIST)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
+ else if (rsp->hdr.Status == 0 || rc == -EOPNOTSUPP)
+ rsp->hdr.Status = STATUS_INVALID_INFO_CLASS;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ ksmbd_debug(SMB, "error while processing smb2 query rc = %d\n", rc);
+ return rc;
+}
+
+/**
+ * smb2_read_pipe() - handler for smb2 read from IPC pipe
+ * @work: smb work containing read IPC pipe command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+static noinline int smb2_read_pipe(struct ksmbd_work *work)
+{
+ int nbytes = 0, err;
+ u64 id;
+ struct ksmbd_rpc_command *rpc_resp;
+ struct smb2_read_req *req = work->request_buf;
+ struct smb2_read_rsp *rsp = work->response_buf;
+
+ id = le64_to_cpu(req->VolatileFileId);
+
+ inc_rfc1001_len(rsp, 16);
+ rpc_resp = ksmbd_rpc_read(work->sess, id);
+ if (rpc_resp) {
+ if (rpc_resp->flags != KSMBD_RPC_OK) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ work->aux_payload_buf =
+ kvmalloc(rpc_resp->payload_sz, GFP_KERNEL | __GFP_ZERO);
+ if (!work->aux_payload_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(work->aux_payload_buf, rpc_resp->payload,
+ rpc_resp->payload_sz);
+
+ nbytes = rpc_resp->payload_sz;
+ work->resp_hdr_sz = get_rfc1002_len(rsp) + 4;
+ work->aux_payload_sz = nbytes;
+ kvfree(rpc_resp);
+ }
+
+ rsp->StructureSize = cpu_to_le16(17);
+ rsp->DataOffset = 80;
+ rsp->Reserved = 0;
+ rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataRemaining = 0;
+ rsp->Reserved2 = 0;
+ inc_rfc1001_len(rsp, nbytes);
+ return 0;
+
+out:
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+ smb2_set_err_rsp(work);
+ kvfree(rpc_resp);
+ return err;
+}
+
+static ssize_t smb2_read_rdma_channel(struct ksmbd_work *work,
+ struct smb2_read_req *req, void *data_buf,
+ size_t length)
+{
+ struct smb2_buffer_desc_v1 *desc =
+ (struct smb2_buffer_desc_v1 *)&req->Buffer[0];
+ int err;
+
+ if (work->conn->dialect == SMB30_PROT_ID &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1)
+ return -EINVAL;
+
+ if (req->ReadChannelInfoOffset == 0 ||
+ le16_to_cpu(req->ReadChannelInfoLength) < sizeof(*desc))
+ return -EINVAL;
+
+ work->need_invalidate_rkey =
+ (req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE);
+ work->remote_key = le32_to_cpu(desc->token);
+
+ err = ksmbd_conn_rdma_write(work->conn, data_buf, length,
+ le32_to_cpu(desc->token),
+ le64_to_cpu(desc->offset),
+ le32_to_cpu(desc->length));
+ if (err)
+ return err;
+
+ return length;
+}
+
+/**
+ * smb2_read() - handler for smb2 read from file
+ * @work: smb work containing read command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_read(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_read_req *req;
+ struct smb2_read_rsp *rsp, *rsp_org;
+ struct ksmbd_file *fp;
+ loff_t offset;
+ size_t length, mincount;
+ ssize_t nbytes = 0, remain_bytes = 0;
+ int err = 0;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ if (test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_PIPE)) {
+ ksmbd_debug(SMB, "IPC pipe read request\n");
+ return smb2_read_pipe(work);
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (!fp) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
+ pr_err("Not permitted to read : 0x%x\n", fp->daccess);
+ err = -EACCES;
+ goto out;
+ }
+
+ offset = le64_to_cpu(req->Offset);
+ length = le32_to_cpu(req->Length);
+ mincount = le32_to_cpu(req->MinimumCount);
+
+ if (length > conn->vals->max_read_size) {
+ ksmbd_debug(SMB, "limiting read size to max size(%u)\n",
+ conn->vals->max_read_size);
+ err = -EINVAL;
+ goto out;
+ }
+
+ ksmbd_debug(SMB, "filename %pd, offset %lld, len %zu\n",
+ fp->filp->f_path.dentry, offset, length);
+
+ work->aux_payload_buf = kvmalloc(length, GFP_KERNEL | __GFP_ZERO);
+ if (!work->aux_payload_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ nbytes = ksmbd_vfs_read(work, fp, length, &offset);
+ if (nbytes < 0) {
+ err = nbytes;
+ goto out;
+ }
+
+ if ((nbytes == 0 && length != 0) || nbytes < mincount) {
+ kvfree(work->aux_payload_buf);
+ work->aux_payload_buf = NULL;
+ rsp->hdr.Status = STATUS_END_OF_FILE;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return 0;
+ }
+
+ ksmbd_debug(SMB, "nbytes %zu, offset %lld mincount %zu\n",
+ nbytes, offset, mincount);
+
+ if (req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE ||
+ req->Channel == SMB2_CHANNEL_RDMA_V1) {
+ /* write data to the client using rdma channel */
+ remain_bytes = smb2_read_rdma_channel(work, req,
+ work->aux_payload_buf,
+ nbytes);
+ kvfree(work->aux_payload_buf);
+ work->aux_payload_buf = NULL;
+
+ nbytes = 0;
+ if (remain_bytes < 0) {
+ err = (int)remain_bytes;
+ goto out;
+ }
+ }
+
+ rsp->StructureSize = cpu_to_le16(17);
+ rsp->DataOffset = 80;
+ rsp->Reserved = 0;
+ rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataRemaining = cpu_to_le32(remain_bytes);
+ rsp->Reserved2 = 0;
+ inc_rfc1001_len(rsp_org, 16);
+ work->resp_hdr_sz = get_rfc1002_len(rsp_org) + 4;
+ work->aux_payload_sz = nbytes;
+ inc_rfc1001_len(rsp_org, nbytes);
+ ksmbd_fd_put(work, fp);
+ return 0;
+
+out:
+ if (err) {
+ if (err == -EISDIR)
+ rsp->hdr.Status = STATUS_INVALID_DEVICE_REQUEST;
+ else if (err == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ else if (err == -ENOENT)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ else if (err == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (err == -ESHARE)
+ rsp->hdr.Status = STATUS_SHARING_VIOLATION;
+ else if (err == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+
+ smb2_set_err_rsp(work);
+ }
+ ksmbd_fd_put(work, fp);
+ return err;
+}
+
+/**
+ * smb2_write_pipe() - handler for smb2 write on IPC pipe
+ * @work: smb work containing write IPC pipe command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+static noinline int smb2_write_pipe(struct ksmbd_work *work)
+{
+ struct smb2_write_req *req = work->request_buf;
+ struct smb2_write_rsp *rsp = work->response_buf;
+ struct ksmbd_rpc_command *rpc_resp;
+ u64 id = 0;
+ int err = 0, ret = 0;
+ char *data_buf;
+ size_t length;
+
+ length = le32_to_cpu(req->Length);
+ id = le64_to_cpu(req->VolatileFileId);
+
+ if (le16_to_cpu(req->DataOffset) ==
+ (offsetof(struct smb2_write_req, Buffer) - 4)) {
+ data_buf = (char *)&req->Buffer[0];
+ } else {
+ if ((le16_to_cpu(req->DataOffset) > get_rfc1002_len(req)) ||
+ (le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req))) {
+ pr_err("invalid write data offset %u, smb_len %u\n",
+ le16_to_cpu(req->DataOffset),
+ get_rfc1002_len(req));
+ err = -EINVAL;
+ goto out;
+ }
+
+ data_buf = (char *)(((char *)&req->hdr.ProtocolId) +
+ le16_to_cpu(req->DataOffset));
+ }
+
+ rpc_resp = ksmbd_rpc_write(work->sess, id, data_buf, length);
+ if (rpc_resp) {
+ if (rpc_resp->flags == KSMBD_RPC_ENOTIMPLEMENTED) {
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ kvfree(rpc_resp);
+ smb2_set_err_rsp(work);
+ return -EOPNOTSUPP;
+ }
+ if (rpc_resp->flags != KSMBD_RPC_OK) {
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ smb2_set_err_rsp(work);
+ kvfree(rpc_resp);
+ return ret;
+ }
+ kvfree(rpc_resp);
+ }
+
+ rsp->StructureSize = cpu_to_le16(17);
+ rsp->DataOffset = 0;
+ rsp->Reserved = 0;
+ rsp->DataLength = cpu_to_le32(length);
+ rsp->DataRemaining = 0;
+ rsp->Reserved2 = 0;
+ inc_rfc1001_len(rsp, 16);
+ return 0;
+out:
+ if (err) {
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ smb2_set_err_rsp(work);
+ }
+
+ return err;
+}
+
+static ssize_t smb2_write_rdma_channel(struct ksmbd_work *work,
+ struct smb2_write_req *req,
+ struct ksmbd_file *fp,
+ loff_t offset, size_t length, bool sync)
+{
+ struct smb2_buffer_desc_v1 *desc;
+ char *data_buf;
+ int ret;
+ ssize_t nbytes;
+
+ desc = (struct smb2_buffer_desc_v1 *)&req->Buffer[0];
+
+ if (work->conn->dialect == SMB30_PROT_ID &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1)
+ return -EINVAL;
+
+ if (req->Length != 0 || req->DataOffset != 0)
+ return -EINVAL;
+
+ if (req->WriteChannelInfoOffset == 0 ||
+ le16_to_cpu(req->WriteChannelInfoLength) < sizeof(*desc))
+ return -EINVAL;
+
+ work->need_invalidate_rkey =
+ (req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE);
+ work->remote_key = le32_to_cpu(desc->token);
+
+ data_buf = kvmalloc(length, GFP_KERNEL | __GFP_ZERO);
+ if (!data_buf)
+ return -ENOMEM;
+
+ ret = ksmbd_conn_rdma_read(work->conn, data_buf, length,
+ le32_to_cpu(desc->token),
+ le64_to_cpu(desc->offset),
+ le32_to_cpu(desc->length));
+ if (ret < 0) {
+ kvfree(data_buf);
+ return ret;
+ }
+
+ ret = ksmbd_vfs_write(work, fp, data_buf, length, &offset, sync, &nbytes);
+ kvfree(data_buf);
+ if (ret < 0)
+ return ret;
+
+ return nbytes;
+}
+
+/**
+ * smb2_write() - handler for smb2 write from file
+ * @work: smb work containing write command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_write(struct ksmbd_work *work)
+{
+ struct smb2_write_req *req;
+ struct smb2_write_rsp *rsp, *rsp_org;
+ struct ksmbd_file *fp = NULL;
+ loff_t offset;
+ size_t length;
+ ssize_t nbytes;
+ char *data_buf;
+ bool writethrough = false;
+ int err = 0;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) {
+ ksmbd_debug(SMB, "IPC pipe write request\n");
+ return smb2_write_pipe(work);
+ }
+
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB, "User does not have write permission\n");
+ err = -EACCES;
+ goto out;
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (!fp) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
+ pr_err("Not permitted to write : 0x%x\n", fp->daccess);
+ err = -EACCES;
+ goto out;
+ }
+
+ offset = le64_to_cpu(req->Offset);
+ length = le32_to_cpu(req->Length);
+
+ if (length > work->conn->vals->max_write_size) {
+ ksmbd_debug(SMB, "limiting write size to max size(%u)\n",
+ work->conn->vals->max_write_size);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (le32_to_cpu(req->Flags) & SMB2_WRITEFLAG_WRITE_THROUGH)
+ writethrough = true;
+
+ if (req->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE) {
+ if (le16_to_cpu(req->DataOffset) ==
+ (offsetof(struct smb2_write_req, Buffer) - 4)) {
+ data_buf = (char *)&req->Buffer[0];
+ } else {
+ if ((le16_to_cpu(req->DataOffset) > get_rfc1002_len(req)) ||
+ (le16_to_cpu(req->DataOffset) + length > get_rfc1002_len(req))) {
+ pr_err("invalid write data offset %u, smb_len %u\n",
+ le16_to_cpu(req->DataOffset),
+ get_rfc1002_len(req));
+ err = -EINVAL;
+ goto out;
+ }
+
+ data_buf = (char *)(((char *)&req->hdr.ProtocolId) +
+ le16_to_cpu(req->DataOffset));
+ }
+
+ ksmbd_debug(SMB, "flags %u\n", le32_to_cpu(req->Flags));
+ if (le32_to_cpu(req->Flags) & SMB2_WRITEFLAG_WRITE_THROUGH)
+ writethrough = true;
+
+ ksmbd_debug(SMB, "filename %pd, offset %lld, len %zu\n",
+ fp->filp->f_path.dentry, offset, length);
+ err = ksmbd_vfs_write(work, fp, data_buf, length, &offset,
+ writethrough, &nbytes);
+ if (err < 0)
+ goto out;
+ } else {
+ /* read data from the client using rdma channel, and
+ * write the data.
+ */
+ nbytes = smb2_write_rdma_channel(work, req, fp, offset,
+ le32_to_cpu(req->RemainingBytes),
+ writethrough);
+ if (nbytes < 0) {
+ err = (int)nbytes;
+ goto out;
+ }
+ }
+
+ rsp->StructureSize = cpu_to_le16(17);
+ rsp->DataOffset = 0;
+ rsp->Reserved = 0;
+ rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataRemaining = 0;
+ rsp->Reserved2 = 0;
+ inc_rfc1001_len(rsp_org, 16);
+ ksmbd_fd_put(work, fp);
+ return 0;
+
+out:
+ if (err == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ else if (err == -ENOSPC || err == -EFBIG)
+ rsp->hdr.Status = STATUS_DISK_FULL;
+ else if (err == -ENOENT)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ else if (err == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (err == -ESHARE)
+ rsp->hdr.Status = STATUS_SHARING_VIOLATION;
+ else if (err == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return err;
+}
+
+/**
+ * smb2_flush() - handler for smb2 flush file - fsync
+ * @work: smb work containing flush command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_flush(struct ksmbd_work *work)
+{
+ struct smb2_flush_req *req;
+ struct smb2_flush_rsp *rsp, *rsp_org;
+ int err;
+
+ rsp_org = work->response_buf;
+ WORK_BUFFERS(work, req, rsp);
+
+ ksmbd_debug(SMB, "SMB2_FLUSH called for fid %llu\n",
+ le64_to_cpu(req->VolatileFileId));
+
+ err = ksmbd_vfs_fsync(work,
+ le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (err)
+ goto out;
+
+ rsp->StructureSize = cpu_to_le16(4);
+ rsp->Reserved = 0;
+ inc_rfc1001_len(rsp_org, 4);
+ return 0;
+
+out:
+ if (err) {
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ smb2_set_err_rsp(work);
+ }
+
+ return err;
+}
+
+/**
+ * smb2_cancel() - handler for smb2 cancel command
+ * @work: smb work containing cancel command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_cancel(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_hdr *hdr = work->request_buf;
+ struct smb2_hdr *chdr;
+ struct ksmbd_work *cancel_work = NULL;
+ int canceled = 0;
+ struct list_head *command_list;
+
+ ksmbd_debug(SMB, "smb2 cancel called on mid %llu, async flags 0x%x\n",
+ hdr->MessageId, hdr->Flags);
+
+ if (hdr->Flags & SMB2_FLAGS_ASYNC_COMMAND) {
+ command_list = &conn->async_requests;
+
+ spin_lock(&conn->request_lock);
+ list_for_each_entry(cancel_work, command_list,
+ async_request_entry) {
+ chdr = cancel_work->request_buf;
+
+ if (cancel_work->async_id !=
+ le64_to_cpu(hdr->Id.AsyncId))
+ continue;
+
+ ksmbd_debug(SMB,
+ "smb2 with AsyncId %llu cancelled command = 0x%x\n",
+ le64_to_cpu(hdr->Id.AsyncId),
+ le16_to_cpu(chdr->Command));
+ canceled = 1;
+ break;
+ }
+ spin_unlock(&conn->request_lock);
+ } else {
+ command_list = &conn->requests;
+
+ spin_lock(&conn->request_lock);
+ list_for_each_entry(cancel_work, command_list, request_entry) {
+ chdr = cancel_work->request_buf;
+
+ if (chdr->MessageId != hdr->MessageId ||
+ cancel_work == work)
+ continue;
+
+ ksmbd_debug(SMB,
+ "smb2 with mid %llu cancelled command = 0x%x\n",
+ le64_to_cpu(hdr->MessageId),
+ le16_to_cpu(chdr->Command));
+ canceled = 1;
+ break;
+ }
+ spin_unlock(&conn->request_lock);
+ }
+
+ if (canceled) {
+ cancel_work->state = KSMBD_WORK_CANCELLED;
+ if (cancel_work->cancel_fn)
+ cancel_work->cancel_fn(cancel_work->cancel_argv);
+ }
+
+ /* For SMB2_CANCEL command itself send no response*/
+ work->send_no_response = 1;
+ return 0;
+}
+
+struct file_lock *smb_flock_init(struct file *f)
+{
+ struct file_lock *fl;
+
+ fl = locks_alloc_lock();
+ if (!fl)
+ goto out;
+
+ locks_init_lock(fl);
+
+ fl->fl_owner = f;
+ fl->fl_pid = current->tgid;
+ fl->fl_file = f;
+ fl->fl_flags = FL_POSIX;
+ fl->fl_ops = NULL;
+ fl->fl_lmops = NULL;
+
+out:
+ return fl;
+}
+
+static int smb2_set_flock_flags(struct file_lock *flock, int flags)
+{
+ int cmd = -EINVAL;
+
+ /* Checking for wrong flag combination during lock request*/
+ switch (flags) {
+ case SMB2_LOCKFLAG_SHARED:
+ ksmbd_debug(SMB, "received shared request\n");
+ cmd = F_SETLKW;
+ flock->fl_type = F_RDLCK;
+ flock->fl_flags |= FL_SLEEP;
+ break;
+ case SMB2_LOCKFLAG_EXCLUSIVE:
+ ksmbd_debug(SMB, "received exclusive request\n");
+ cmd = F_SETLKW;
+ flock->fl_type = F_WRLCK;
+ flock->fl_flags |= FL_SLEEP;
+ break;
+ case SMB2_LOCKFLAG_SHARED | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
+ ksmbd_debug(SMB,
+ "received shared & fail immediately request\n");
+ cmd = F_SETLK;
+ flock->fl_type = F_RDLCK;
+ break;
+ case SMB2_LOCKFLAG_EXCLUSIVE | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
+ ksmbd_debug(SMB,
+ "received exclusive & fail immediately request\n");
+ cmd = F_SETLK;
+ flock->fl_type = F_WRLCK;
+ break;
+ case SMB2_LOCKFLAG_UNLOCK:
+ ksmbd_debug(SMB, "received unlock request\n");
+ flock->fl_type = F_UNLCK;
+ cmd = 0;
+ break;
+ }
+
+ return cmd;
+}
+
+static struct ksmbd_lock *smb2_lock_init(struct file_lock *flock,
+ unsigned int cmd, int flags,
+ struct list_head *lock_list)
+{
+ struct ksmbd_lock *lock;
+
+ lock = kzalloc(sizeof(struct ksmbd_lock), GFP_KERNEL);
+ if (!lock)
+ return NULL;
+
+ lock->cmd = cmd;
+ lock->fl = flock;
+ lock->start = flock->fl_start;
+ lock->end = flock->fl_end;
+ lock->flags = flags;
+ if (lock->start == lock->end)
+ lock->zero_len = 1;
+ INIT_LIST_HEAD(&lock->clist);
+ INIT_LIST_HEAD(&lock->flist);
+ INIT_LIST_HEAD(&lock->llist);
+ list_add_tail(&lock->llist, lock_list);
+
+ return lock;
+}
+
+static void smb2_remove_blocked_lock(void **argv)
+{
+ struct file_lock *flock = (struct file_lock *)argv[0];
+
+ ksmbd_vfs_posix_lock_unblock(flock);
+ wake_up(&flock->fl_wait);
+}
+
+static inline bool lock_defer_pending(struct file_lock *fl)
+{
+ /* check pending lock waiters */
+ return waitqueue_active(&fl->fl_wait);
+}
+
+/**
+ * smb2_lock() - handler for smb2 file lock command
+ * @work: smb work containing lock command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_lock(struct ksmbd_work *work)
+{
+ struct smb2_lock_req *req = work->request_buf;
+ struct smb2_lock_rsp *rsp = work->response_buf;
+ struct smb2_lock_element *lock_ele;
+ struct ksmbd_file *fp = NULL;
+ struct file_lock *flock = NULL;
+ struct file *filp = NULL;
+ int lock_count;
+ int flags = 0;
+ int cmd = 0;
+ int err = -EIO, i, rc = 0;
+ u64 lock_start, lock_length;
+ struct ksmbd_lock *smb_lock = NULL, *cmp_lock, *tmp, *tmp2;
+ struct ksmbd_conn *conn;
+ int nolock = 0;
+ LIST_HEAD(lock_list);
+ LIST_HEAD(rollback_list);
+ int prior_lock = 0;
+
+ ksmbd_debug(SMB, "Received lock request\n");
+ fp = ksmbd_lookup_fd_slow(work,
+ le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (!fp) {
+ ksmbd_debug(SMB, "Invalid file id for lock : %llu\n",
+ le64_to_cpu(req->VolatileFileId));
+ err = -ENOENT;
+ goto out2;
+ }
+
+ filp = fp->filp;
+ lock_count = le16_to_cpu(req->LockCount);
+ lock_ele = req->locks;
+
+ ksmbd_debug(SMB, "lock count is %d\n", lock_count);
+ if (!lock_count) {
+ err = -EINVAL;
+ goto out2;
+ }
+
+ for (i = 0; i < lock_count; i++) {
+ flags = le32_to_cpu(lock_ele[i].Flags);
+
+ flock = smb_flock_init(filp);
+ if (!flock)
+ goto out;
+
+ cmd = smb2_set_flock_flags(flock, flags);
+
+ lock_start = le64_to_cpu(lock_ele[i].Offset);
+ lock_length = le64_to_cpu(lock_ele[i].Length);
+ if (lock_start > U64_MAX - lock_length) {
+ pr_err("Invalid lock range requested\n");
+ rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
+ goto out;
+ }
+
+ if (lock_start > OFFSET_MAX)
+ flock->fl_start = OFFSET_MAX;
+ else
+ flock->fl_start = lock_start;
+
+ lock_length = le64_to_cpu(lock_ele[i].Length);
+ if (lock_length > OFFSET_MAX - flock->fl_start)
+ lock_length = OFFSET_MAX - flock->fl_start;
+
+ flock->fl_end = flock->fl_start + lock_length;
+
+ if (flock->fl_end < flock->fl_start) {
+ ksmbd_debug(SMB,
+ "the end offset(%llx) is smaller than the start offset(%llx)\n",
+ flock->fl_end, flock->fl_start);
+ rsp->hdr.Status = STATUS_INVALID_LOCK_RANGE;
+ goto out;
+ }
+
+ /* Check conflict locks in one request */
+ list_for_each_entry(cmp_lock, &lock_list, llist) {
+ if (cmp_lock->fl->fl_start <= flock->fl_start &&
+ cmp_lock->fl->fl_end >= flock->fl_end) {
+ if (cmp_lock->fl->fl_type != F_UNLCK &&
+ flock->fl_type != F_UNLCK) {
+ pr_err("conflict two locks in one request\n");
+ err = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ smb_lock = smb2_lock_init(flock, cmd, flags, &lock_list);
+ if (!smb_lock) {
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ list_for_each_entry_safe(smb_lock, tmp, &lock_list, llist) {
+ if (smb_lock->cmd < 0) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!(smb_lock->flags & SMB2_LOCKFLAG_MASK)) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if ((prior_lock & (SMB2_LOCKFLAG_EXCLUSIVE | SMB2_LOCKFLAG_SHARED) &&
+ smb_lock->flags & SMB2_LOCKFLAG_UNLOCK) ||
+ (prior_lock == SMB2_LOCKFLAG_UNLOCK &&
+ !(smb_lock->flags & SMB2_LOCKFLAG_UNLOCK))) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ prior_lock = smb_lock->flags;
+
+ if (!(smb_lock->flags & SMB2_LOCKFLAG_UNLOCK) &&
+ !(smb_lock->flags & SMB2_LOCKFLAG_FAIL_IMMEDIATELY))
+ goto no_check_cl;
+
+ nolock = 1;
+ /* check locks in connection list */
+ read_lock(&conn_list_lock);
+ list_for_each_entry(conn, &conn_list, conns_list) {
+ spin_lock(&conn->llist_lock);
+ list_for_each_entry_safe(cmp_lock, tmp2, &conn->lock_list, clist) {
+ if (file_inode(cmp_lock->fl->fl_file) !=
+ file_inode(smb_lock->fl->fl_file))
+ continue;
+
+ if (smb_lock->fl->fl_type == F_UNLCK) {
+ if (cmp_lock->fl->fl_file == smb_lock->fl->fl_file &&
+ cmp_lock->start == smb_lock->start &&
+ cmp_lock->end == smb_lock->end &&
+ !lock_defer_pending(cmp_lock->fl)) {
+ nolock = 0;
+ list_del(&cmp_lock->flist);
+ list_del(&cmp_lock->clist);
+ spin_unlock(&conn->llist_lock);
+ read_unlock(&conn_list_lock);
+
+ locks_free_lock(cmp_lock->fl);
+ kfree(cmp_lock);
+ goto out_check_cl;
+ }
+ continue;
+ }
+
+ if (cmp_lock->fl->fl_file == smb_lock->fl->fl_file) {
+ if (smb_lock->flags & SMB2_LOCKFLAG_SHARED)
+ continue;
+ } else {
+ if (cmp_lock->flags & SMB2_LOCKFLAG_SHARED)
+ continue;
+ }
+
+ /* check zero byte lock range */
+ if (cmp_lock->zero_len && !smb_lock->zero_len &&
+ cmp_lock->start > smb_lock->start &&
+ cmp_lock->start < smb_lock->end) {
+ spin_unlock(&conn->llist_lock);
+ read_unlock(&conn_list_lock);
+ pr_err("previous lock conflict with zero byte lock range\n");
+ goto out;
+ }
+
+ if (smb_lock->zero_len && !cmp_lock->zero_len &&
+ smb_lock->start > cmp_lock->start &&
+ smb_lock->start < cmp_lock->end) {
+ spin_unlock(&conn->llist_lock);
+ read_unlock(&conn_list_lock);
+ pr_err("current lock conflict with zero byte lock range\n");
+ goto out;
+ }
+
+ if (((cmp_lock->start <= smb_lock->start &&
+ cmp_lock->end > smb_lock->start) ||
+ (cmp_lock->start < smb_lock->end &&
+ cmp_lock->end >= smb_lock->end)) &&
+ !cmp_lock->zero_len && !smb_lock->zero_len) {
+ spin_unlock(&conn->llist_lock);
+ read_unlock(&conn_list_lock);
+ pr_err("Not allow lock operation on exclusive lock range\n");
+ goto out;
+ }
+ }
+ spin_unlock(&conn->llist_lock);
+ }
+ read_unlock(&conn_list_lock);
+out_check_cl:
+ if (smb_lock->fl->fl_type == F_UNLCK && nolock) {
+ pr_err("Try to unlock nolocked range\n");
+ rsp->hdr.Status = STATUS_RANGE_NOT_LOCKED;
+ goto out;
+ }
+
+no_check_cl:
+ if (smb_lock->zero_len) {
+ err = 0;
+ goto skip;
+ }
+
+ flock = smb_lock->fl;
+ list_del(&smb_lock->llist);
+retry:
+ rc = vfs_lock_file(filp, smb_lock->cmd, flock, NULL);
+skip:
+ if (flags & SMB2_LOCKFLAG_UNLOCK) {
+ if (!rc) {
+ ksmbd_debug(SMB, "File unlocked\n");
+ } else if (rc == -ENOENT) {
+ rsp->hdr.Status = STATUS_NOT_LOCKED;
+ goto out;
+ }
+ locks_free_lock(flock);
+ kfree(smb_lock);
+ } else {
+ if (rc == FILE_LOCK_DEFERRED) {
+ void **argv;
+
+ ksmbd_debug(SMB,
+ "would have to wait for getting lock\n");
+ spin_lock(&work->conn->llist_lock);
+ list_add_tail(&smb_lock->clist,
+ &work->conn->lock_list);
+ spin_unlock(&work->conn->llist_lock);
+ list_add(&smb_lock->llist, &rollback_list);
+
+ argv = kmalloc(sizeof(void *), GFP_KERNEL);
+ if (!argv) {
+ err = -ENOMEM;
+ goto out;
+ }
+ argv[0] = flock;
+
+ rc = setup_async_work(work,
+ smb2_remove_blocked_lock,
+ argv);
+ if (rc) {
+ err = -ENOMEM;
+ goto out;
+ }
+ spin_lock(&fp->f_lock);
+ list_add(&work->fp_entry, &fp->blocked_works);
+ spin_unlock(&fp->f_lock);
+
+ smb2_send_interim_resp(work, STATUS_PENDING);
+
+ ksmbd_vfs_posix_lock_wait(flock);
+
+ if (work->state != KSMBD_WORK_ACTIVE) {
+ list_del(&smb_lock->llist);
+ spin_lock(&work->conn->llist_lock);
+ list_del(&smb_lock->clist);
+ spin_unlock(&work->conn->llist_lock);
+ locks_free_lock(flock);
+
+ if (work->state == KSMBD_WORK_CANCELLED) {
+ spin_lock(&fp->f_lock);
+ list_del(&work->fp_entry);
+ spin_unlock(&fp->f_lock);
+ rsp->hdr.Status =
+ STATUS_CANCELLED;
+ kfree(smb_lock);
+ smb2_send_interim_resp(work,
+ STATUS_CANCELLED);
+ work->send_no_response = 1;
+ goto out;
+ }
+ init_smb2_rsp_hdr(work);
+ smb2_set_err_rsp(work);
+ rsp->hdr.Status =
+ STATUS_RANGE_NOT_LOCKED;
+ kfree(smb_lock);
+ goto out2;
+ }
+
+ list_del(&smb_lock->llist);
+ spin_lock(&work->conn->llist_lock);
+ list_del(&smb_lock->clist);
+ spin_unlock(&work->conn->llist_lock);
+
+ spin_lock(&fp->f_lock);
+ list_del(&work->fp_entry);
+ spin_unlock(&fp->f_lock);
+ goto retry;
+ } else if (!rc) {
+ spin_lock(&work->conn->llist_lock);
+ list_add_tail(&smb_lock->clist,
+ &work->conn->lock_list);
+ list_add_tail(&smb_lock->flist,
+ &fp->lock_list);
+ spin_unlock(&work->conn->llist_lock);
+ list_add(&smb_lock->llist, &rollback_list);
+ ksmbd_debug(SMB, "successful in taking lock\n");
+ } else {
+ goto out;
+ }
+ }
+ }
+
+ if (atomic_read(&fp->f_ci->op_count) > 1)
+ smb_break_all_oplock(work, fp);
+
+ rsp->StructureSize = cpu_to_le16(4);
+ ksmbd_debug(SMB, "successful in taking lock\n");
+ rsp->hdr.Status = STATUS_SUCCESS;
+ rsp->Reserved = 0;
+ inc_rfc1001_len(rsp, 4);
+ ksmbd_fd_put(work, fp);
+ return 0;
+
+out:
+ list_for_each_entry_safe(smb_lock, tmp, &lock_list, llist) {
+ locks_free_lock(smb_lock->fl);
+ list_del(&smb_lock->llist);
+ kfree(smb_lock);
+ }
+
+ list_for_each_entry_safe(smb_lock, tmp, &rollback_list, llist) {
+ struct file_lock *rlock = NULL;
+
+ rlock = smb_flock_init(filp);
+ rlock->fl_type = F_UNLCK;
+ rlock->fl_start = smb_lock->start;
+ rlock->fl_end = smb_lock->end;
+
+ rc = vfs_lock_file(filp, 0, rlock, NULL);
+ if (rc)
+ pr_err("rollback unlock fail : %d\n", rc);
+
+ list_del(&smb_lock->llist);
+ spin_lock(&work->conn->llist_lock);
+ if (!list_empty(&smb_lock->flist))
+ list_del(&smb_lock->flist);
+ list_del(&smb_lock->clist);
+ spin_unlock(&work->conn->llist_lock);
+
+ locks_free_lock(smb_lock->fl);
+ locks_free_lock(rlock);
+ kfree(smb_lock);
+ }
+out2:
+ ksmbd_debug(SMB, "failed in taking lock(flags : %x), err : %d\n", flags, err);
+
+ if (!rsp->hdr.Status) {
+ if (err == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (err == -ENOMEM)
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ else if (err == -ENOENT)
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ else
+ rsp->hdr.Status = STATUS_LOCK_NOT_GRANTED;
+ }
+
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return err;
+}
+
+static int fsctl_copychunk(struct ksmbd_work *work, struct smb2_ioctl_req *req,
+ struct smb2_ioctl_rsp *rsp)
+{
+ struct copychunk_ioctl_req *ci_req;
+ struct copychunk_ioctl_rsp *ci_rsp;
+ struct ksmbd_file *src_fp = NULL, *dst_fp = NULL;
+ struct srv_copychunk *chunks;
+ unsigned int i, chunk_count, chunk_count_written = 0;
+ unsigned int chunk_size_written = 0;
+ loff_t total_size_written = 0;
+ int ret, cnt_code;
+
+ cnt_code = le32_to_cpu(req->CntCode);
+ ci_req = (struct copychunk_ioctl_req *)&req->Buffer[0];
+ ci_rsp = (struct copychunk_ioctl_rsp *)&rsp->Buffer[0];
+
+ rsp->VolatileFileId = req->VolatileFileId;
+ rsp->PersistentFileId = req->PersistentFileId;
+ ci_rsp->ChunksWritten =
+ cpu_to_le32(ksmbd_server_side_copy_max_chunk_count());
+ ci_rsp->ChunkBytesWritten =
+ cpu_to_le32(ksmbd_server_side_copy_max_chunk_size());
+ ci_rsp->TotalBytesWritten =
+ cpu_to_le32(ksmbd_server_side_copy_max_total_size());
+
+ chunks = (struct srv_copychunk *)&ci_req->Chunks[0];
+ chunk_count = le32_to_cpu(ci_req->ChunkCount);
+ total_size_written = 0;
+
+ /* verify the SRV_COPYCHUNK_COPY packet */
+ if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
+ le32_to_cpu(req->InputCount) <
+ offsetof(struct copychunk_ioctl_req, Chunks) +
+ chunk_count * sizeof(struct srv_copychunk)) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ return -EINVAL;
+ }
+
+ for (i = 0; i < chunk_count; i++) {
+ if (le32_to_cpu(chunks[i].Length) == 0 ||
+ le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
+ break;
+ total_size_written += le32_to_cpu(chunks[i].Length);
+ }
+
+ if (i < chunk_count ||
+ total_size_written > ksmbd_server_side_copy_max_total_size()) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ return -EINVAL;
+ }
+
+ src_fp = ksmbd_lookup_foreign_fd(work,
+ le64_to_cpu(ci_req->ResumeKey[0]));
+ dst_fp = ksmbd_lookup_fd_slow(work,
+ le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ ret = -EINVAL;
+ if (!src_fp ||
+ src_fp->persistent_id != le64_to_cpu(ci_req->ResumeKey[1])) {
+ rsp->hdr.Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ if (!dst_fp) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ goto out;
+ }
+
+ /*
+ * FILE_READ_DATA should only be included in
+ * the FSCTL_COPYCHUNK case
+ */
+ if (cnt_code == FSCTL_COPYCHUNK &&
+ !(dst_fp->daccess & (FILE_READ_DATA_LE | FILE_GENERIC_READ_LE))) {
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ ret = ksmbd_vfs_copy_file_ranges(work, src_fp, dst_fp,
+ chunks, chunk_count,
+ &chunk_count_written,
+ &chunk_size_written,
+ &total_size_written);
+ if (ret < 0) {
+ if (ret == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ if (ret == -EAGAIN)
+ rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
+ else if (ret == -EBADF)
+ rsp->hdr.Status = STATUS_INVALID_HANDLE;
+ else if (ret == -EFBIG || ret == -ENOSPC)
+ rsp->hdr.Status = STATUS_DISK_FULL;
+ else if (ret == -EINVAL)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (ret == -EISDIR)
+ rsp->hdr.Status = STATUS_FILE_IS_A_DIRECTORY;
+ else if (ret == -E2BIG)
+ rsp->hdr.Status = STATUS_INVALID_VIEW_SIZE;
+ else
+ rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ ci_rsp->ChunksWritten = cpu_to_le32(chunk_count_written);
+ ci_rsp->ChunkBytesWritten = cpu_to_le32(chunk_size_written);
+ ci_rsp->TotalBytesWritten = cpu_to_le32(total_size_written);
+out:
+ ksmbd_fd_put(work, src_fp);
+ ksmbd_fd_put(work, dst_fp);
+ return ret;
+}
+
+static __be32 idev_ipv4_address(struct in_device *idev)
+{
+ __be32 addr = 0;
+
+ struct in_ifaddr *ifa;
+
+ rcu_read_lock();
+ in_dev_for_each_ifa_rcu(ifa, idev) {
+ if (ifa->ifa_flags & IFA_F_SECONDARY)
+ continue;
+
+ addr = ifa->ifa_address;
+ break;
+ }
+ rcu_read_unlock();
+ return addr;
+}
+
+static int fsctl_query_iface_info_ioctl(struct ksmbd_conn *conn,
+ struct smb2_ioctl_req *req,
+ struct smb2_ioctl_rsp *rsp)
+{
+ struct network_interface_info_ioctl_rsp *nii_rsp = NULL;
+ int nbytes = 0;
+ struct net_device *netdev;
+ struct sockaddr_storage_rsp *sockaddr_storage;
+ unsigned int flags;
+ unsigned long long speed;
+ struct sockaddr_in6 *csin6 = (struct sockaddr_in6 *)&conn->peer_addr;
+
+ rtnl_lock();
+ for_each_netdev(&init_net, netdev) {
+ if (netdev->type == ARPHRD_LOOPBACK)
+ continue;
+
+ flags = dev_get_flags(netdev);
+ if (!(flags & IFF_RUNNING))
+ continue;
+
+ nii_rsp = (struct network_interface_info_ioctl_rsp *)
+ &rsp->Buffer[nbytes];
+ nii_rsp->IfIndex = cpu_to_le32(netdev->ifindex);
+
+ nii_rsp->Capability = 0;
+ if (ksmbd_rdma_capable_netdev(netdev))
+ nii_rsp->Capability |= cpu_to_le32(RDMA_CAPABLE);
+
+ nii_rsp->Next = cpu_to_le32(152);
+ nii_rsp->Reserved = 0;
+
+ if (netdev->ethtool_ops->get_link_ksettings) {
+ struct ethtool_link_ksettings cmd;
+
+ netdev->ethtool_ops->get_link_ksettings(netdev, &cmd);
+ speed = cmd.base.speed;
+ } else {
+ pr_err("%s %s\n", netdev->name,
+ "speed is unknown, defaulting to 1Gb/sec");
+ speed = SPEED_1000;
+ }
+
+ speed *= 1000000;
+ nii_rsp->LinkSpeed = cpu_to_le64(speed);
+
+ sockaddr_storage = (struct sockaddr_storage_rsp *)
+ nii_rsp->SockAddr_Storage;
+ memset(sockaddr_storage, 0, 128);
+
+ if (conn->peer_addr.ss_family == PF_INET ||
+ ipv6_addr_v4mapped(&csin6->sin6_addr)) {
+ struct in_device *idev;
+
+ sockaddr_storage->Family = cpu_to_le16(INTERNETWORK);
+ sockaddr_storage->addr4.Port = 0;
+
+ idev = __in_dev_get_rtnl(netdev);
+ if (!idev)
+ continue;
+ sockaddr_storage->addr4.IPv4address =
+ idev_ipv4_address(idev);
+ } else {
+ struct inet6_dev *idev6;
+ struct inet6_ifaddr *ifa;
+ __u8 *ipv6_addr = sockaddr_storage->addr6.IPv6address;
+
+ sockaddr_storage->Family = cpu_to_le16(INTERNETWORKV6);
+ sockaddr_storage->addr6.Port = 0;
+ sockaddr_storage->addr6.FlowInfo = 0;
+
+ idev6 = __in6_dev_get(netdev);
+ if (!idev6)
+ continue;
+
+ list_for_each_entry(ifa, &idev6->addr_list, if_list) {
+ if (ifa->flags & (IFA_F_TENTATIVE |
+ IFA_F_DEPRECATED))
+ continue;
+ memcpy(ipv6_addr, ifa->addr.s6_addr, 16);
+ break;
+ }
+ sockaddr_storage->addr6.ScopeId = 0;
+ }
+
+ nbytes += sizeof(struct network_interface_info_ioctl_rsp);
+ }
+ rtnl_unlock();
+
+ /* zero if this is last one */
+ if (nii_rsp)
+ nii_rsp->Next = 0;
+
+ if (!nbytes) {
+ rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
+ return -EINVAL;
+ }
+
+ rsp->PersistentFileId = cpu_to_le64(SMB2_NO_FID);
+ rsp->VolatileFileId = cpu_to_le64(SMB2_NO_FID);
+ return nbytes;
+}
+
+static int fsctl_validate_negotiate_info(struct ksmbd_conn *conn,
+ struct validate_negotiate_info_req *neg_req,
+ struct validate_negotiate_info_rsp *neg_rsp)
+{
+ int ret = 0;
+ int dialect;
+
+ dialect = ksmbd_lookup_dialect_by_id(neg_req->Dialects,
+ neg_req->DialectCount);
+ if (dialect == BAD_PROT_ID || dialect != conn->dialect) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ if (strncmp(neg_req->Guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE)) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ if (le16_to_cpu(neg_req->SecurityMode) != conn->cli_sec_mode) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ if (le32_to_cpu(neg_req->Capabilities) != conn->cli_cap) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ neg_rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
+ memset(neg_rsp->Guid, 0, SMB2_CLIENT_GUID_SIZE);
+ neg_rsp->SecurityMode = cpu_to_le16(conn->srv_sec_mode);
+ neg_rsp->Dialect = cpu_to_le16(conn->dialect);
+err_out:
+ return ret;
+}
+
+static int fsctl_query_allocated_ranges(struct ksmbd_work *work, u64 id,
+ struct file_allocated_range_buffer *qar_req,
+ struct file_allocated_range_buffer *qar_rsp,
+ int in_count, int *out_count)
+{
+ struct ksmbd_file *fp;
+ loff_t start, length;
+ int ret = 0;
+
+ *out_count = 0;
+ if (in_count == 0)
+ return -EINVAL;
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp)
+ return -ENOENT;
+
+ start = le64_to_cpu(qar_req->file_offset);
+ length = le64_to_cpu(qar_req->length);
+
+ ret = ksmbd_vfs_fqar_lseek(fp, start, length,
+ qar_rsp, in_count, out_count);
+ if (ret && ret != -E2BIG)
+ *out_count = 0;
+
+ ksmbd_fd_put(work, fp);
+ return ret;
+}
+
+static int fsctl_pipe_transceive(struct ksmbd_work *work, u64 id,
+ int out_buf_len, struct smb2_ioctl_req *req,
+ struct smb2_ioctl_rsp *rsp)
+{
+ struct ksmbd_rpc_command *rpc_resp;
+ char *data_buf = (char *)&req->Buffer[0];
+ int nbytes = 0;
+
+ rpc_resp = ksmbd_rpc_ioctl(work->sess, id, data_buf,
+ le32_to_cpu(req->InputCount));
+ if (rpc_resp) {
+ if (rpc_resp->flags == KSMBD_RPC_SOME_NOT_MAPPED) {
+ /*
+ * set STATUS_SOME_NOT_MAPPED response
+ * for unknown domain sid.
+ */
+ rsp->hdr.Status = STATUS_SOME_NOT_MAPPED;
+ } else if (rpc_resp->flags == KSMBD_RPC_ENOTIMPLEMENTED) {
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ goto out;
+ } else if (rpc_resp->flags != KSMBD_RPC_OK) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ nbytes = rpc_resp->payload_sz;
+ if (rpc_resp->payload_sz > out_buf_len) {
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ nbytes = out_buf_len;
+ }
+
+ if (!rpc_resp->payload_sz) {
+ rsp->hdr.Status =
+ STATUS_UNEXPECTED_IO_ERROR;
+ goto out;
+ }
+
+ memcpy((char *)rsp->Buffer, rpc_resp->payload, nbytes);
+ }
+out:
+ kvfree(rpc_resp);
+ return nbytes;
+}
+
+static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
+ struct file_sparse *sparse)
+{
+ struct ksmbd_file *fp;
+ struct user_namespace *user_ns;
+ int ret = 0;
+ __le32 old_fattr;
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp)
+ return -ENOENT;
+ user_ns = file_mnt_user_ns(fp->filp);
+
+ old_fattr = fp->f_ci->m_fattr;
+ if (sparse->SetSparse)
+ fp->f_ci->m_fattr |= ATTR_SPARSE_FILE_LE;
+ else
+ fp->f_ci->m_fattr &= ~ATTR_SPARSE_FILE_LE;
+
+ if (fp->f_ci->m_fattr != old_fattr &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
+ struct xattr_dos_attrib da;
+
+ ret = ksmbd_vfs_get_dos_attrib_xattr(user_ns,
+ fp->filp->f_path.dentry, &da);
+ if (ret <= 0)
+ goto out;
+
+ da.attr = le32_to_cpu(fp->f_ci->m_fattr);
+ ret = ksmbd_vfs_set_dos_attrib_xattr(user_ns,
+ fp->filp->f_path.dentry, &da);
+ if (ret)
+ fp->f_ci->m_fattr = old_fattr;
+ }
+
+out:
+ ksmbd_fd_put(work, fp);
+ return ret;
+}
+
+static int fsctl_request_resume_key(struct ksmbd_work *work,
+ struct smb2_ioctl_req *req,
+ struct resume_key_ioctl_rsp *key_rsp)
+{
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_fd_slow(work,
+ le64_to_cpu(req->VolatileFileId),
+ le64_to_cpu(req->PersistentFileId));
+ if (!fp)
+ return -ENOENT;
+
+ memset(key_rsp, 0, sizeof(*key_rsp));
+ key_rsp->ResumeKey[0] = req->VolatileFileId;
+ key_rsp->ResumeKey[1] = req->PersistentFileId;
+ ksmbd_fd_put(work, fp);
+
+ return 0;
+}
+
+/**
+ * smb2_ioctl() - handler for smb2 ioctl command
+ * @work: smb work containing ioctl command buffer
+ *
+ * Return: 0 on success, otherwise error
+ */
+int smb2_ioctl(struct ksmbd_work *work)
+{
+ struct smb2_ioctl_req *req;
+ struct smb2_ioctl_rsp *rsp, *rsp_org;
+ int cnt_code, nbytes = 0;
+ int out_buf_len;
+ u64 id = KSMBD_NO_FID;
+ struct ksmbd_conn *conn = work->conn;
+ int ret = 0;
+
+ rsp_org = work->response_buf;
+ if (work->next_smb2_rcv_hdr_off) {
+ req = ksmbd_req_buf_next(work);
+ rsp = ksmbd_resp_buf_next(work);
+ if (!has_file_id(le64_to_cpu(req->VolatileFileId))) {
+ ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+ work->compound_fid);
+ id = work->compound_fid;
+ }
+ } else {
+ req = work->request_buf;
+ rsp = work->response_buf;
+ }
+
+ if (!has_file_id(id))
+ id = le64_to_cpu(req->VolatileFileId);
+
+ if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) {
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ goto out;
+ }
+
+ cnt_code = le32_to_cpu(req->CntCode);
+ out_buf_len = le32_to_cpu(req->MaxOutputResponse);
+ out_buf_len = min(KSMBD_IPC_MAX_PAYLOAD, out_buf_len);
+
+ switch (cnt_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ case FSCTL_DFS_GET_REFERRALS_EX:
+ /* Not support DFS yet */
+ rsp->hdr.Status = STATUS_FS_DRIVER_REQUIRED;
+ goto out;
+ case FSCTL_CREATE_OR_GET_OBJECT_ID:
+ {
+ struct file_object_buf_type1_ioctl_rsp *obj_buf;
+
+ nbytes = sizeof(struct file_object_buf_type1_ioctl_rsp);
+ obj_buf = (struct file_object_buf_type1_ioctl_rsp *)
+ &rsp->Buffer[0];
+
+ /*
+ * TODO: This is dummy implementation to pass smbtorture
+ * Need to check correct response later
+ */
+ memset(obj_buf->ObjectId, 0x0, 16);
+ memset(obj_buf->BirthVolumeId, 0x0, 16);
+ memset(obj_buf->BirthObjectId, 0x0, 16);
+ memset(obj_buf->DomainId, 0x0, 16);
+
+ break;
+ }
+ case FSCTL_PIPE_TRANSCEIVE:
+ nbytes = fsctl_pipe_transceive(work, id, out_buf_len, req, rsp);
+ break;
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ if (conn->dialect < SMB30_PROT_ID) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ ret = fsctl_validate_negotiate_info(conn,
+ (struct validate_negotiate_info_req *)&req->Buffer[0],
+ (struct validate_negotiate_info_rsp *)&rsp->Buffer[0]);
+ if (ret < 0)
+ goto out;
+
+ nbytes = sizeof(struct validate_negotiate_info_rsp);
+ rsp->PersistentFileId = cpu_to_le64(SMB2_NO_FID);
+ rsp->VolatileFileId = cpu_to_le64(SMB2_NO_FID);
+ break;
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ nbytes = fsctl_query_iface_info_ioctl(conn, req, rsp);
+ if (nbytes < 0)
+ goto out;
+ break;
+ case FSCTL_REQUEST_RESUME_KEY:
+ if (out_buf_len < sizeof(struct resume_key_ioctl_rsp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = fsctl_request_resume_key(work, req,
+ (struct resume_key_ioctl_rsp *)&rsp->Buffer[0]);
+ if (ret < 0)
+ goto out;
+ rsp->PersistentFileId = req->PersistentFileId;
+ rsp->VolatileFileId = req->VolatileFileId;
+ nbytes = sizeof(struct resume_key_ioctl_rsp);
+ break;
+ case FSCTL_COPYCHUNK:
+ case FSCTL_COPYCHUNK_WRITE:
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ if (out_buf_len < sizeof(struct copychunk_ioctl_rsp)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ nbytes = sizeof(struct copychunk_ioctl_rsp);
+ fsctl_copychunk(work, req, rsp);
+ break;
+ case FSCTL_SET_SPARSE:
+ ret = fsctl_set_sparse(work, id,
+ (struct file_sparse *)&req->Buffer[0]);
+ if (ret < 0)
+ goto out;
+ break;
+ case FSCTL_SET_ZERO_DATA:
+ {
+ struct file_zero_data_information *zero_data;
+ struct ksmbd_file *fp;
+ loff_t off, len;
+
+ if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
+ ksmbd_debug(SMB,
+ "User does not have write permission\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ zero_data =
+ (struct file_zero_data_information *)&req->Buffer[0];
+
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ off = le64_to_cpu(zero_data->FileOffset);
+ len = le64_to_cpu(zero_data->BeyondFinalZero) - off;
+
+ ret = ksmbd_vfs_zero_data(work, fp, off, len);
+ ksmbd_fd_put(work, fp);
+ if (ret < 0)
+ goto out;
+ break;
+ }
+ case FSCTL_QUERY_ALLOCATED_RANGES:
+ ret = fsctl_query_allocated_ranges(work, id,
+ (struct file_allocated_range_buffer *)&req->Buffer[0],
+ (struct file_allocated_range_buffer *)&rsp->Buffer[0],
+ out_buf_len /
+ sizeof(struct file_allocated_range_buffer), &nbytes);
+ if (ret == -E2BIG) {
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ } else if (ret < 0) {
+ nbytes = 0;
+ goto out;
+ }
+
+ nbytes *= sizeof(struct file_allocated_range_buffer);
+ break;
+ case FSCTL_GET_REPARSE_POINT:
+ {
+ struct reparse_data_buffer *reparse_ptr;
+ struct ksmbd_file *fp;
+
+ reparse_ptr = (struct reparse_data_buffer *)&rsp->Buffer[0];
+ fp = ksmbd_lookup_fd_fast(work, id);
+ if (!fp) {
+ pr_err("not found fp!!\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ reparse_ptr->ReparseTag =
+ smb2_get_reparse_tag_special_file(file_inode(fp->filp)->i_mode);
+ reparse_ptr->ReparseDataLength = 0;
+ ksmbd_fd_put(work, fp);
+ nbytes = sizeof(struct reparse_data_buffer);
+ break;
+ }
+ case FSCTL_DUPLICATE_EXTENTS_TO_FILE:
+ {
+ struct ksmbd_file *fp_in, *fp_out = NULL;
+ struct duplicate_extents_to_file *dup_ext;
+ loff_t src_off, dst_off, length, cloned;
+
+ dup_ext = (struct duplicate_extents_to_file *)&req->Buffer[0];
+
+ fp_in = ksmbd_lookup_fd_slow(work, dup_ext->VolatileFileHandle,
+ dup_ext->PersistentFileHandle);
+ if (!fp_in) {
+ pr_err("not found file handle in duplicate extent to file\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ fp_out = ksmbd_lookup_fd_fast(work, id);
+ if (!fp_out) {
+ pr_err("not found fp\n");
+ ret = -ENOENT;
+ goto dup_ext_out;
+ }
+
+ src_off = le64_to_cpu(dup_ext->SourceFileOffset);
+ dst_off = le64_to_cpu(dup_ext->TargetFileOffset);
+ length = le64_to_cpu(dup_ext->ByteCount);
+ cloned = vfs_clone_file_range(fp_in->filp, src_off, fp_out->filp,
+ dst_off, length, 0);
+ if (cloned == -EXDEV || cloned == -EOPNOTSUPP) {
+ ret = -EOPNOTSUPP;
+ goto dup_ext_out;
+ } else if (cloned != length) {
+ cloned = vfs_copy_file_range(fp_in->filp, src_off,
+ fp_out->filp, dst_off, length, 0);
+ if (cloned != length) {
+ if (cloned < 0)
+ ret = cloned;
+ else
+ ret = -EINVAL;
+ }
+ }
+
+dup_ext_out:
+ ksmbd_fd_put(work, fp_in);
+ ksmbd_fd_put(work, fp_out);
+ if (ret < 0)
+ goto out;
+ break;
+ }
+ default:
+ ksmbd_debug(SMB, "not implemented yet ioctl command 0x%x\n",
+ cnt_code);
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ rsp->CntCode = cpu_to_le32(cnt_code);
+ rsp->InputCount = cpu_to_le32(0);
+ rsp->InputOffset = cpu_to_le32(112);
+ rsp->OutputOffset = cpu_to_le32(112);
+ rsp->OutputCount = cpu_to_le32(nbytes);
+ rsp->StructureSize = cpu_to_le16(49);
+ rsp->Reserved = cpu_to_le16(0);
+ rsp->Flags = cpu_to_le32(0);
+ rsp->Reserved2 = cpu_to_le32(0);
+ inc_rfc1001_len(rsp_org, 48 + nbytes);
+
+ return 0;
+
+out:
+ if (ret == -EACCES)
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ else if (ret == -ENOENT)
+ rsp->hdr.Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ else if (ret == -EOPNOTSUPP)
+ rsp->hdr.Status = STATUS_NOT_SUPPORTED;
+ else if (ret < 0 || rsp->hdr.Status == 0)
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ smb2_set_err_rsp(work);
+ return 0;
+}
+
+/**
+ * smb20_oplock_break_ack() - handler for smb2.0 oplock break command
+ * @work: smb work containing oplock break command buffer
+ *
+ * Return: 0
+ */
+static void smb20_oplock_break_ack(struct ksmbd_work *work)
+{
+ struct smb2_oplock_break *req = work->request_buf;
+ struct smb2_oplock_break *rsp = work->response_buf;
+ struct ksmbd_file *fp;
+ struct oplock_info *opinfo = NULL;
+ __le32 err = 0;
+ int ret = 0;
+ u64 volatile_id, persistent_id;
+ char req_oplevel = 0, rsp_oplevel = 0;
+ unsigned int oplock_change_type;
+
+ volatile_id = le64_to_cpu(req->VolatileFid);
+ persistent_id = le64_to_cpu(req->PersistentFid);
+ req_oplevel = req->OplockLevel;
+ ksmbd_debug(OPLOCK, "v_id %llu, p_id %llu request oplock level %d\n",
+ volatile_id, persistent_id, req_oplevel);
+
+ fp = ksmbd_lookup_fd_slow(work, volatile_id, persistent_id);
+ if (!fp) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ return;
+ }
+
+ opinfo = opinfo_get(fp);
+ if (!opinfo) {
+ pr_err("unexpected null oplock_info\n");
+ rsp->hdr.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return;
+ }
+
+ if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE) {
+ rsp->hdr.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto err_out;
+ }
+
+ if (opinfo->op_state == OPLOCK_STATE_NONE) {
+ ksmbd_debug(SMB, "unexpected oplock state 0x%x\n", opinfo->op_state);
+ rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
+ opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
+ (req_oplevel != SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_NONE)) {
+ err = STATUS_INVALID_OPLOCK_PROTOCOL;
+ oplock_change_type = OPLOCK_WRITE_TO_NONE;
+ } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel != SMB2_OPLOCK_LEVEL_NONE) {
+ err = STATUS_INVALID_OPLOCK_PROTOCOL;
+ oplock_change_type = OPLOCK_READ_TO_NONE;
+ } else if (req_oplevel == SMB2_OPLOCK_LEVEL_II ||
+ req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
+ err = STATUS_INVALID_DEVICE_STATE;
+ if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
+ opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
+ req_oplevel == SMB2_OPLOCK_LEVEL_II) {
+ oplock_change_type = OPLOCK_WRITE_TO_READ;
+ } else if ((opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE ||
+ opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) &&
+ req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
+ oplock_change_type = OPLOCK_WRITE_TO_NONE;
+ } else if (opinfo->level == SMB2_OPLOCK_LEVEL_II &&
+ req_oplevel == SMB2_OPLOCK_LEVEL_NONE) {
+ oplock_change_type = OPLOCK_READ_TO_NONE;
+ } else {
+ oplock_change_type = 0;
+ }
+ } else {
+ oplock_change_type = 0;
+ }
+
+ switch (oplock_change_type) {
+ case OPLOCK_WRITE_TO_READ:
+ ret = opinfo_write_to_read(opinfo);
+ rsp_oplevel = SMB2_OPLOCK_LEVEL_II;
+ break;
+ case OPLOCK_WRITE_TO_NONE:
+ ret = opinfo_write_to_none(opinfo);
+ rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
+ break;
+ case OPLOCK_READ_TO_NONE:
+ ret = opinfo_read_to_none(opinfo);
+ rsp_oplevel = SMB2_OPLOCK_LEVEL_NONE;
+ break;
+ default:
+ pr_err("unknown oplock change 0x%x -> 0x%x\n",
+ opinfo->level, rsp_oplevel);
+ }
+
+ if (ret < 0) {
+ rsp->hdr.Status = err;
+ goto err_out;
+ }
+
+ opinfo_put(opinfo);
+ ksmbd_fd_put(work, fp);
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ wake_up_interruptible_all(&opinfo->oplock_q);
+
+ rsp->StructureSize = cpu_to_le16(24);
+ rsp->OplockLevel = rsp_oplevel;
+ rsp->Reserved = 0;
+ rsp->Reserved2 = 0;
+ rsp->VolatileFid = cpu_to_le64(volatile_id);
+ rsp->PersistentFid = cpu_to_le64(persistent_id);
+ inc_rfc1001_len(rsp, 24);
+ return;
+
+err_out:
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ wake_up_interruptible_all(&opinfo->oplock_q);
+
+ opinfo_put(opinfo);
+ ksmbd_fd_put(work, fp);
+ smb2_set_err_rsp(work);
+}
+
+static int check_lease_state(struct lease *lease, __le32 req_state)
+{
+ if ((lease->new_state ==
+ (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE)) &&
+ !(req_state & SMB2_LEASE_WRITE_CACHING_LE)) {
+ lease->new_state = req_state;
+ return 0;
+ }
+
+ if (lease->new_state == req_state)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * smb21_lease_break_ack() - handler for smb2.1 lease break command
+ * @work: smb work containing lease break command buffer
+ *
+ * Return: 0
+ */
+static void smb21_lease_break_ack(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_lease_ack *req = work->request_buf;
+ struct smb2_lease_ack *rsp = work->response_buf;
+ struct oplock_info *opinfo;
+ __le32 err = 0;
+ int ret = 0;
+ unsigned int lease_change_type;
+ __le32 lease_state;
+ struct lease *lease;
+
+ ksmbd_debug(OPLOCK, "smb21 lease break, lease state(0x%x)\n",
+ le32_to_cpu(req->LeaseState));
+ opinfo = lookup_lease_in_table(conn, req->LeaseKey);
+ if (!opinfo) {
+ ksmbd_debug(OPLOCK, "file not opened\n");
+ smb2_set_err_rsp(work);
+ rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+ return;
+ }
+ lease = opinfo->o_lease;
+
+ if (opinfo->op_state == OPLOCK_STATE_NONE) {
+ pr_err("unexpected lease break state 0x%x\n",
+ opinfo->op_state);
+ rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ if (check_lease_state(lease, req->LeaseState)) {
+ rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
+ ksmbd_debug(OPLOCK,
+ "req lease state: 0x%x, expected state: 0x%x\n",
+ req->LeaseState, lease->new_state);
+ goto err_out;
+ }
+
+ if (!atomic_read(&opinfo->breaking_cnt)) {
+ rsp->hdr.Status = STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ /* check for bad lease state */
+ if (req->LeaseState &
+ (~(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE))) {
+ err = STATUS_INVALID_OPLOCK_PROTOCOL;
+ if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
+ lease_change_type = OPLOCK_WRITE_TO_NONE;
+ else
+ lease_change_type = OPLOCK_READ_TO_NONE;
+ ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
+ le32_to_cpu(lease->state),
+ le32_to_cpu(req->LeaseState));
+ } else if (lease->state == SMB2_LEASE_READ_CACHING_LE &&
+ req->LeaseState != SMB2_LEASE_NONE_LE) {
+ err = STATUS_INVALID_OPLOCK_PROTOCOL;
+ lease_change_type = OPLOCK_READ_TO_NONE;
+ ksmbd_debug(OPLOCK, "handle bad lease state 0x%x -> 0x%x\n",
+ le32_to_cpu(lease->state),
+ le32_to_cpu(req->LeaseState));
+ } else {
+ /* valid lease state changes */
+ err = STATUS_INVALID_DEVICE_STATE;
+ if (req->LeaseState == SMB2_LEASE_NONE_LE) {
+ if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
+ lease_change_type = OPLOCK_WRITE_TO_NONE;
+ else
+ lease_change_type = OPLOCK_READ_TO_NONE;
+ } else if (req->LeaseState & SMB2_LEASE_READ_CACHING_LE) {
+ if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
+ lease_change_type = OPLOCK_WRITE_TO_READ;
+ else
+ lease_change_type = OPLOCK_READ_HANDLE_TO_READ;
+ } else {
+ lease_change_type = 0;
+ }
+ }
+
+ switch (lease_change_type) {
+ case OPLOCK_WRITE_TO_READ:
+ ret = opinfo_write_to_read(opinfo);
+ break;
+ case OPLOCK_READ_HANDLE_TO_READ:
+ ret = opinfo_read_handle_to_read(opinfo);
+ break;
+ case OPLOCK_WRITE_TO_NONE:
+ ret = opinfo_write_to_none(opinfo);
+ break;
+ case OPLOCK_READ_TO_NONE:
+ ret = opinfo_read_to_none(opinfo);
+ break;
+ default:
+ ksmbd_debug(OPLOCK, "unknown lease change 0x%x -> 0x%x\n",
+ le32_to_cpu(lease->state),
+ le32_to_cpu(req->LeaseState));
+ }
+
+ lease_state = lease->state;
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ wake_up_interruptible_all(&opinfo->oplock_q);
+ atomic_dec(&opinfo->breaking_cnt);
+ wake_up_interruptible_all(&opinfo->oplock_brk);
+ opinfo_put(opinfo);
+
+ if (ret < 0) {
+ rsp->hdr.Status = err;
+ goto err_out;
+ }
+
+ rsp->StructureSize = cpu_to_le16(36);
+ rsp->Reserved = 0;
+ rsp->Flags = 0;
+ memcpy(rsp->LeaseKey, req->LeaseKey, 16);
+ rsp->LeaseState = lease_state;
+ rsp->LeaseDuration = 0;
+ inc_rfc1001_len(rsp, 36);
+ return;
+
+err_out:
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ wake_up_interruptible_all(&opinfo->oplock_q);
+ atomic_dec(&opinfo->breaking_cnt);
+ wake_up_interruptible_all(&opinfo->oplock_brk);
+
+ opinfo_put(opinfo);
+ smb2_set_err_rsp(work);
+}
+
+/**
+ * smb2_oplock_break() - dispatcher for smb2.0 and 2.1 oplock/lease break
+ * @work: smb work containing oplock/lease break command buffer
+ *
+ * Return: 0
+ */
+int smb2_oplock_break(struct ksmbd_work *work)
+{
+ struct smb2_oplock_break *req = work->request_buf;
+ struct smb2_oplock_break *rsp = work->response_buf;
+
+ switch (le16_to_cpu(req->StructureSize)) {
+ case OP_BREAK_STRUCT_SIZE_20:
+ smb20_oplock_break_ack(work);
+ break;
+ case OP_BREAK_STRUCT_SIZE_21:
+ smb21_lease_break_ack(work);
+ break;
+ default:
+ ksmbd_debug(OPLOCK, "invalid break cmd %d\n",
+ le16_to_cpu(req->StructureSize));
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ smb2_set_err_rsp(work);
+ }
+
+ return 0;
+}
+
+/**
+ * smb2_notify() - handler for smb2 notify request
+ * @work: smb work containing notify command buffer
+ *
+ * Return: 0
+ */
+int smb2_notify(struct ksmbd_work *work)
+{
+ struct smb2_notify_req *req;
+ struct smb2_notify_rsp *rsp;
+
+ WORK_BUFFERS(work, req, rsp);
+
+ if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
+ rsp->hdr.Status = STATUS_INTERNAL_ERROR;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ smb2_set_err_rsp(work);
+ rsp->hdr.Status = STATUS_NOT_IMPLEMENTED;
+ return 0;
+}
+
+/**
+ * smb2_is_sign_req() - handler for checking packet signing status
+ * @work: smb work containing notify command buffer
+ * @command: SMB2 command id
+ *
+ * Return: true if packed is signed, false otherwise
+ */
+bool smb2_is_sign_req(struct ksmbd_work *work, unsigned int command)
+{
+ struct smb2_hdr *rcv_hdr2 = work->request_buf;
+
+ if ((rcv_hdr2->Flags & SMB2_FLAGS_SIGNED) &&
+ command != SMB2_NEGOTIATE_HE &&
+ command != SMB2_SESSION_SETUP_HE &&
+ command != SMB2_OPLOCK_BREAK_HE)
+ return true;
+
+ return false;
+}
+
+/**
+ * smb2_check_sign_req() - handler for req packet sign processing
+ * @work: smb work containing notify command buffer
+ *
+ * Return: 1 on success, 0 otherwise
+ */
+int smb2_check_sign_req(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr, *hdr_org;
+ char signature_req[SMB2_SIGNATURE_SIZE];
+ char signature[SMB2_HMACSHA256_SIZE];
+ struct kvec iov[1];
+ size_t len;
+
+ hdr_org = hdr = work->request_buf;
+ if (work->next_smb2_rcv_hdr_off)
+ hdr = ksmbd_req_buf_next(work);
+
+ if (!hdr->NextCommand && !work->next_smb2_rcv_hdr_off)
+ len = be32_to_cpu(hdr_org->smb2_buf_length);
+ else if (hdr->NextCommand)
+ len = le32_to_cpu(hdr->NextCommand);
+ else
+ len = be32_to_cpu(hdr_org->smb2_buf_length) -
+ work->next_smb2_rcv_hdr_off;
+
+ memcpy(signature_req, hdr->Signature, SMB2_SIGNATURE_SIZE);
+ memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
+
+ iov[0].iov_base = (char *)&hdr->ProtocolId;
+ iov[0].iov_len = len;
+
+ if (ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, 1,
+ signature))
+ return 0;
+
+ if (memcmp(signature, signature_req, SMB2_SIGNATURE_SIZE)) {
+ pr_err("bad smb2 signature\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * smb2_set_sign_rsp() - handler for rsp packet sign processing
+ * @work: smb work containing notify command buffer
+ *
+ */
+void smb2_set_sign_rsp(struct ksmbd_work *work)
+{
+ struct smb2_hdr *hdr, *hdr_org;
+ struct smb2_hdr *req_hdr;
+ char signature[SMB2_HMACSHA256_SIZE];
+ struct kvec iov[2];
+ size_t len;
+ int n_vec = 1;
+
+ hdr_org = hdr = work->response_buf;
+ if (work->next_smb2_rsp_hdr_off)
+ hdr = ksmbd_resp_buf_next(work);
+
+ req_hdr = ksmbd_req_buf_next(work);
+
+ if (!work->next_smb2_rsp_hdr_off) {
+ len = get_rfc1002_len(hdr_org);
+ if (req_hdr->NextCommand)
+ len = ALIGN(len, 8);
+ } else {
+ len = get_rfc1002_len(hdr_org) - work->next_smb2_rsp_hdr_off;
+ len = ALIGN(len, 8);
+ }
+
+ if (req_hdr->NextCommand)
+ hdr->NextCommand = cpu_to_le32(len);
+
+ hdr->Flags |= SMB2_FLAGS_SIGNED;
+ memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
+
+ iov[0].iov_base = (char *)&hdr->ProtocolId;
+ iov[0].iov_len = len;
+
+ if (work->aux_payload_sz) {
+ iov[0].iov_len -= work->aux_payload_sz;
+
+ iov[1].iov_base = work->aux_payload_buf;
+ iov[1].iov_len = work->aux_payload_sz;
+ n_vec++;
+ }
+
+ if (!ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, n_vec,
+ signature))
+ memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE);
+}
+
+/**
+ * smb3_check_sign_req() - handler for req packet sign processing
+ * @work: smb work containing notify command buffer
+ *
+ * Return: 1 on success, 0 otherwise
+ */
+int smb3_check_sign_req(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ char *signing_key;
+ struct smb2_hdr *hdr, *hdr_org;
+ struct channel *chann;
+ char signature_req[SMB2_SIGNATURE_SIZE];
+ char signature[SMB2_CMACAES_SIZE];
+ struct kvec iov[1];
+ size_t len;
+
+ hdr_org = hdr = work->request_buf;
+ if (work->next_smb2_rcv_hdr_off)
+ hdr = ksmbd_req_buf_next(work);
+
+ if (!hdr->NextCommand && !work->next_smb2_rcv_hdr_off)
+ len = be32_to_cpu(hdr_org->smb2_buf_length);
+ else if (hdr->NextCommand)
+ len = le32_to_cpu(hdr->NextCommand);
+ else
+ len = be32_to_cpu(hdr_org->smb2_buf_length) -
+ work->next_smb2_rcv_hdr_off;
+
+ if (le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE) {
+ signing_key = work->sess->smb3signingkey;
+ } else {
+ chann = lookup_chann_list(work->sess, conn);
+ if (!chann)
+ return 0;
+ signing_key = chann->smb3signingkey;
+ }
+
+ if (!signing_key) {
+ pr_err("SMB3 signing key is not generated\n");
+ return 0;
+ }
+
+ memcpy(signature_req, hdr->Signature, SMB2_SIGNATURE_SIZE);
+ memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
+ iov[0].iov_base = (char *)&hdr->ProtocolId;
+ iov[0].iov_len = len;
+
+ if (ksmbd_sign_smb3_pdu(conn, signing_key, iov, 1, signature))
+ return 0;
+
+ if (memcmp(signature, signature_req, SMB2_SIGNATURE_SIZE)) {
+ pr_err("bad smb2 signature\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * smb3_set_sign_rsp() - handler for rsp packet sign processing
+ * @work: smb work containing notify command buffer
+ *
+ */
+void smb3_set_sign_rsp(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_hdr *req_hdr;
+ struct smb2_hdr *hdr, *hdr_org;
+ struct channel *chann;
+ char signature[SMB2_CMACAES_SIZE];
+ struct kvec iov[2];
+ int n_vec = 1;
+ size_t len;
+ char *signing_key;
+
+ hdr_org = hdr = work->response_buf;
+ if (work->next_smb2_rsp_hdr_off)
+ hdr = ksmbd_resp_buf_next(work);
+
+ req_hdr = ksmbd_req_buf_next(work);
+
+ if (!work->next_smb2_rsp_hdr_off) {
+ len = get_rfc1002_len(hdr_org);
+ if (req_hdr->NextCommand)
+ len = ALIGN(len, 8);
+ } else {
+ len = get_rfc1002_len(hdr_org) - work->next_smb2_rsp_hdr_off;
+ len = ALIGN(len, 8);
+ }
+
+ if (conn->binding == false &&
+ le16_to_cpu(hdr->Command) == SMB2_SESSION_SETUP_HE) {
+ signing_key = work->sess->smb3signingkey;
+ } else {
+ chann = lookup_chann_list(work->sess, work->conn);
+ if (!chann)
+ return;
+ signing_key = chann->smb3signingkey;
+ }
+
+ if (!signing_key)
+ return;
+
+ if (req_hdr->NextCommand)
+ hdr->NextCommand = cpu_to_le32(len);
+
+ hdr->Flags |= SMB2_FLAGS_SIGNED;
+ memset(hdr->Signature, 0, SMB2_SIGNATURE_SIZE);
+ iov[0].iov_base = (char *)&hdr->ProtocolId;
+ iov[0].iov_len = len;
+ if (work->aux_payload_sz) {
+ iov[0].iov_len -= work->aux_payload_sz;
+ iov[1].iov_base = work->aux_payload_buf;
+ iov[1].iov_len = work->aux_payload_sz;
+ n_vec++;
+ }
+
+ if (!ksmbd_sign_smb3_pdu(conn, signing_key, iov, n_vec, signature))
+ memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE);
+}
+
+/**
+ * smb3_preauth_hash_rsp() - handler for computing preauth hash on response
+ * @work: smb work containing response buffer
+ *
+ */
+void smb3_preauth_hash_rsp(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ struct smb2_hdr *req, *rsp;
+
+ if (conn->dialect != SMB311_PROT_ID)
+ return;
+
+ WORK_BUFFERS(work, req, rsp);
+
+ if (le16_to_cpu(req->Command) == SMB2_NEGOTIATE_HE)
+ ksmbd_gen_preauth_integrity_hash(conn, (char *)rsp,
+ conn->preauth_info->Preauth_HashValue);
+
+ if (le16_to_cpu(rsp->Command) == SMB2_SESSION_SETUP_HE && sess) {
+ __u8 *hash_value;
+
+ if (conn->binding) {
+ struct preauth_session *preauth_sess;
+
+ preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
+ if (!preauth_sess)
+ return;
+ hash_value = preauth_sess->Preauth_HashValue;
+ } else {
+ hash_value = sess->Preauth_HashValue;
+ if (!hash_value)
+ return;
+ }
+ ksmbd_gen_preauth_integrity_hash(conn, (char *)rsp,
+ hash_value);
+ }
+}
+
+static void fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, char *old_buf,
+ __le16 cipher_type)
+{
+ struct smb2_hdr *hdr = (struct smb2_hdr *)old_buf;
+ unsigned int orig_len = get_rfc1002_len(old_buf);
+
+ memset(tr_hdr, 0, sizeof(struct smb2_transform_hdr));
+ tr_hdr->ProtocolId = SMB2_TRANSFORM_PROTO_NUM;
+ tr_hdr->OriginalMessageSize = cpu_to_le32(orig_len);
+ tr_hdr->Flags = cpu_to_le16(0x01);
+ if (cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ get_random_bytes(&tr_hdr->Nonce, SMB3_AES_GCM_NONCE);
+ else
+ get_random_bytes(&tr_hdr->Nonce, SMB3_AES_CCM_NONCE);
+ memcpy(&tr_hdr->SessionId, &hdr->SessionId, 8);
+ inc_rfc1001_len(tr_hdr, sizeof(struct smb2_transform_hdr) - 4);
+ inc_rfc1001_len(tr_hdr, orig_len);
+}
+
+int smb3_encrypt_resp(struct ksmbd_work *work)
+{
+ char *buf = work->response_buf;
+ struct smb2_transform_hdr *tr_hdr;
+ struct kvec iov[3];
+ int rc = -ENOMEM;
+ int buf_size = 0, rq_nvec = 2 + (work->aux_payload_sz ? 1 : 0);
+
+ if (ARRAY_SIZE(iov) < rq_nvec)
+ return -ENOMEM;
+
+ tr_hdr = kzalloc(sizeof(struct smb2_transform_hdr), GFP_KERNEL);
+ if (!tr_hdr)
+ return rc;
+
+ /* fill transform header */
+ fill_transform_hdr(tr_hdr, buf, work->conn->cipher_type);
+
+ iov[0].iov_base = tr_hdr;
+ iov[0].iov_len = sizeof(struct smb2_transform_hdr);
+ buf_size += iov[0].iov_len - 4;
+
+ iov[1].iov_base = buf + 4;
+ iov[1].iov_len = get_rfc1002_len(buf);
+ if (work->aux_payload_sz) {
+ iov[1].iov_len = work->resp_hdr_sz - 4;
+
+ iov[2].iov_base = work->aux_payload_buf;
+ iov[2].iov_len = work->aux_payload_sz;
+ buf_size += iov[2].iov_len;
+ }
+ buf_size += iov[1].iov_len;
+ work->resp_hdr_sz = iov[1].iov_len;
+
+ rc = ksmbd_crypt_message(work->conn, iov, rq_nvec, 1);
+ if (rc)
+ return rc;
+
+ memmove(buf, iov[1].iov_base, iov[1].iov_len);
+ tr_hdr->smb2_buf_length = cpu_to_be32(buf_size);
+ work->tr_buf = tr_hdr;
+
+ return rc;
+}
+
+bool smb3_is_transform_hdr(void *buf)
+{
+ struct smb2_transform_hdr *trhdr = buf;
+
+ return trhdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM;
+}
+
+int smb3_decrypt_req(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess;
+ char *buf = work->request_buf;
+ struct smb2_hdr *hdr;
+ unsigned int pdu_length = get_rfc1002_len(buf);
+ struct kvec iov[2];
+ unsigned int buf_data_size = pdu_length + 4 -
+ sizeof(struct smb2_transform_hdr);
+ struct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf;
+ unsigned int orig_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
+ int rc = 0;
+
+ sess = ksmbd_session_lookup_all(conn, le64_to_cpu(tr_hdr->SessionId));
+ if (!sess) {
+ pr_err("invalid session id(%llx) in transform header\n",
+ le64_to_cpu(tr_hdr->SessionId));
+ return -ECONNABORTED;
+ }
+
+ if (pdu_length + 4 <
+ sizeof(struct smb2_transform_hdr) + sizeof(struct smb2_hdr)) {
+ pr_err("Transform message is too small (%u)\n",
+ pdu_length);
+ return -ECONNABORTED;
+ }
+
+ if (pdu_length + 4 < orig_len + sizeof(struct smb2_transform_hdr)) {
+ pr_err("Transform message is broken\n");
+ return -ECONNABORTED;
+ }
+
+ iov[0].iov_base = buf;
+ iov[0].iov_len = sizeof(struct smb2_transform_hdr);
+ iov[1].iov_base = buf + sizeof(struct smb2_transform_hdr);
+ iov[1].iov_len = buf_data_size;
+ rc = ksmbd_crypt_message(conn, iov, 2, 0);
+ if (rc)
+ return rc;
+
+ memmove(buf + 4, iov[1].iov_base, buf_data_size);
+ hdr = (struct smb2_hdr *)buf;
+ hdr->smb2_buf_length = cpu_to_be32(buf_data_size);
+
+ return rc;
+}
+
+bool smb3_11_final_sess_setup_resp(struct ksmbd_work *work)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_hdr *rsp = work->response_buf;
+
+ if (conn->dialect < SMB30_PROT_ID)
+ return false;
+
+ if (work->next_smb2_rcv_hdr_off)
+ rsp = ksmbd_resp_buf_next(work);
+
+ if (le16_to_cpu(rsp->Command) == SMB2_SESSION_SETUP_HE &&
+ rsp->Status == STATUS_SUCCESS)
+ return true;
+ return false;
+}
diff --git a/fs/ksmbd/smb2pdu.h b/fs/ksmbd/smb2pdu.h
new file mode 100644
index 000000000000..bcec845b03f3
--- /dev/null
+++ b/fs/ksmbd/smb2pdu.h
@@ -0,0 +1,1698 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef _SMB2PDU_H
+#define _SMB2PDU_H
+
+#include "ntlmssp.h"
+#include "smbacl.h"
+
+/*
+ * Note that, due to trying to use names similar to the protocol specifications,
+ * there are many mixed case field names in the structures below. Although
+ * this does not match typical Linux kernel style, it is necessary to be
+ * able to match against the protocol specfication.
+ *
+ * SMB2 commands
+ * Some commands have minimal (wct=0,bcc=0), or uninteresting, responses
+ * (ie no useful data other than the SMB error code itself) and are marked such.
+ * Knowing this helps avoid response buffer allocations and copy in some cases.
+ */
+
+/* List of commands in host endian */
+#define SMB2_NEGOTIATE_HE 0x0000
+#define SMB2_SESSION_SETUP_HE 0x0001
+#define SMB2_LOGOFF_HE 0x0002 /* trivial request/resp */
+#define SMB2_TREE_CONNECT_HE 0x0003
+#define SMB2_TREE_DISCONNECT_HE 0x0004 /* trivial req/resp */
+#define SMB2_CREATE_HE 0x0005
+#define SMB2_CLOSE_HE 0x0006
+#define SMB2_FLUSH_HE 0x0007 /* trivial resp */
+#define SMB2_READ_HE 0x0008
+#define SMB2_WRITE_HE 0x0009
+#define SMB2_LOCK_HE 0x000A
+#define SMB2_IOCTL_HE 0x000B
+#define SMB2_CANCEL_HE 0x000C
+#define SMB2_ECHO_HE 0x000D
+#define SMB2_QUERY_DIRECTORY_HE 0x000E
+#define SMB2_CHANGE_NOTIFY_HE 0x000F
+#define SMB2_QUERY_INFO_HE 0x0010
+#define SMB2_SET_INFO_HE 0x0011
+#define SMB2_OPLOCK_BREAK_HE 0x0012
+
+/* The same list in little endian */
+#define SMB2_NEGOTIATE cpu_to_le16(SMB2_NEGOTIATE_HE)
+#define SMB2_SESSION_SETUP cpu_to_le16(SMB2_SESSION_SETUP_HE)
+#define SMB2_LOGOFF cpu_to_le16(SMB2_LOGOFF_HE)
+#define SMB2_TREE_CONNECT cpu_to_le16(SMB2_TREE_CONNECT_HE)
+#define SMB2_TREE_DISCONNECT cpu_to_le16(SMB2_TREE_DISCONNECT_HE)
+#define SMB2_CREATE cpu_to_le16(SMB2_CREATE_HE)
+#define SMB2_CLOSE cpu_to_le16(SMB2_CLOSE_HE)
+#define SMB2_FLUSH cpu_to_le16(SMB2_FLUSH_HE)
+#define SMB2_READ cpu_to_le16(SMB2_READ_HE)
+#define SMB2_WRITE cpu_to_le16(SMB2_WRITE_HE)
+#define SMB2_LOCK cpu_to_le16(SMB2_LOCK_HE)
+#define SMB2_IOCTL cpu_to_le16(SMB2_IOCTL_HE)
+#define SMB2_CANCEL cpu_to_le16(SMB2_CANCEL_HE)
+#define SMB2_ECHO cpu_to_le16(SMB2_ECHO_HE)
+#define SMB2_QUERY_DIRECTORY cpu_to_le16(SMB2_QUERY_DIRECTORY_HE)
+#define SMB2_CHANGE_NOTIFY cpu_to_le16(SMB2_CHANGE_NOTIFY_HE)
+#define SMB2_QUERY_INFO cpu_to_le16(SMB2_QUERY_INFO_HE)
+#define SMB2_SET_INFO cpu_to_le16(SMB2_SET_INFO_HE)
+#define SMB2_OPLOCK_BREAK cpu_to_le16(SMB2_OPLOCK_BREAK_HE)
+
+/*Create Action Flags*/
+#define FILE_SUPERSEDED 0x00000000
+#define FILE_OPENED 0x00000001
+#define FILE_CREATED 0x00000002
+#define FILE_OVERWRITTEN 0x00000003
+
+/*
+ * Size of the session key (crypto key encrypted with the password
+ */
+#define SMB2_NTLMV2_SESSKEY_SIZE 16
+#define SMB2_SIGNATURE_SIZE 16
+#define SMB2_HMACSHA256_SIZE 32
+#define SMB2_CMACAES_SIZE 16
+#define SMB3_GCM128_CRYPTKEY_SIZE 16
+#define SMB3_GCM256_CRYPTKEY_SIZE 32
+
+/*
+ * Size of the smb3 encryption/decryption keys
+ */
+#define SMB3_ENC_DEC_KEY_SIZE 32
+
+/*
+ * Size of the smb3 signing key
+ */
+#define SMB3_SIGN_KEY_SIZE 16
+
+#define CIFS_CLIENT_CHALLENGE_SIZE 8
+#define SMB_SERVER_CHALLENGE_SIZE 8
+
+/* SMB2 Max Credits */
+#define SMB2_MAX_CREDITS 8192
+
+#define SMB2_CLIENT_GUID_SIZE 16
+#define SMB2_CREATE_GUID_SIZE 16
+
+/* Maximum buffer size value we can send with 1 credit */
+#define SMB2_MAX_BUFFER_SIZE 65536
+
+#define NUMBER_OF_SMB2_COMMANDS 0x0013
+
+/* BB FIXME - analyze following length BB */
+#define MAX_SMB2_HDR_SIZE 0x78 /* 4 len + 64 hdr + (2*24 wct) + 2 bct + 2 pad */
+
+#define SMB2_PROTO_NUMBER cpu_to_le32(0x424d53fe) /* 'B''M''S' */
+#define SMB2_TRANSFORM_PROTO_NUM cpu_to_le32(0x424d53fd)
+
+#define SMB21_DEFAULT_IOSIZE (1024 * 1024)
+#define SMB3_DEFAULT_IOSIZE (4 * 1024 * 1024)
+#define SMB3_DEFAULT_TRANS_SIZE (1024 * 1024)
+
+/*
+ * SMB2 Header Definition
+ *
+ * "MBZ" : Must be Zero
+ * "BB" : BugBug, Something to check/review/analyze later
+ * "PDU" : "Protocol Data Unit" (ie a network "frame")
+ *
+ */
+
+#define __SMB2_HEADER_STRUCTURE_SIZE 64
+#define SMB2_HEADER_STRUCTURE_SIZE \
+ cpu_to_le16(__SMB2_HEADER_STRUCTURE_SIZE)
+
+struct smb2_hdr {
+ __be32 smb2_buf_length; /* big endian on wire */
+ /*
+ * length is only two or three bytes - with
+ * one or two byte type preceding it that MBZ
+ */
+ __le32 ProtocolId; /* 0xFE 'S' 'M' 'B' */
+ __le16 StructureSize; /* 64 */
+ __le16 CreditCharge; /* MBZ */
+ __le32 Status; /* Error from server */
+ __le16 Command;
+ __le16 CreditRequest; /* CreditResponse */
+ __le32 Flags;
+ __le32 NextCommand;
+ __le64 MessageId;
+ union {
+ struct {
+ __le32 ProcessId;
+ __le32 TreeId;
+ } __packed SyncId;
+ __le64 AsyncId;
+ } __packed Id;
+ __le64 SessionId;
+ __u8 Signature[16];
+} __packed;
+
+struct smb2_pdu {
+ struct smb2_hdr hdr;
+ __le16 StructureSize2; /* size of wct area (varies, request specific) */
+} __packed;
+
+#define SMB3_AES_CCM_NONCE 11
+#define SMB3_AES_GCM_NONCE 12
+
+struct smb2_transform_hdr {
+ __be32 smb2_buf_length; /* big endian on wire */
+ /*
+ * length is only two or three bytes - with
+ * one or two byte type preceding it that MBZ
+ */
+ __le32 ProtocolId; /* 0xFD 'S' 'M' 'B' */
+ __u8 Signature[16];
+ __u8 Nonce[16];
+ __le32 OriginalMessageSize;
+ __u16 Reserved1;
+ __le16 Flags; /* EncryptionAlgorithm */
+ __le64 SessionId;
+} __packed;
+
+/*
+ * SMB2 flag definitions
+ */
+#define SMB2_FLAGS_SERVER_TO_REDIR cpu_to_le32(0x00000001)
+#define SMB2_FLAGS_ASYNC_COMMAND cpu_to_le32(0x00000002)
+#define SMB2_FLAGS_RELATED_OPERATIONS cpu_to_le32(0x00000004)
+#define SMB2_FLAGS_SIGNED cpu_to_le32(0x00000008)
+#define SMB2_FLAGS_DFS_OPERATIONS cpu_to_le32(0x10000000)
+#define SMB2_FLAGS_REPLAY_OPERATIONS cpu_to_le32(0x20000000)
+
+/*
+ * Definitions for SMB2 Protocol Data Units (network frames)
+ *
+ * See MS-SMB2.PDF specification for protocol details.
+ * The Naming convention is the lower case version of the SMB2
+ * command code name for the struct. Note that structures must be packed.
+ *
+ */
+
+#define SMB2_ERROR_STRUCTURE_SIZE2 9
+#define SMB2_ERROR_STRUCTURE_SIZE2_LE cpu_to_le16(SMB2_ERROR_STRUCTURE_SIZE2)
+
+struct smb2_err_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize;
+ __u8 ErrorContextCount;
+ __u8 Reserved;
+ __le32 ByteCount; /* even if zero, at least one byte follows */
+ __u8 ErrorData[1]; /* variable length */
+} __packed;
+
+struct smb2_negotiate_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 36 */
+ __le16 DialectCount;
+ __le16 SecurityMode;
+ __le16 Reserved; /* MBZ */
+ __le32 Capabilities;
+ __u8 ClientGUID[SMB2_CLIENT_GUID_SIZE];
+ /* In SMB3.02 and earlier next three were MBZ le64 ClientStartTime */
+ __le32 NegotiateContextOffset; /* SMB3.1.1 only. MBZ earlier */
+ __le16 NegotiateContextCount; /* SMB3.1.1 only. MBZ earlier */
+ __le16 Reserved2;
+ __le16 Dialects[1]; /* One dialect (vers=) at a time for now */
+} __packed;
+
+/* SecurityMode flags */
+#define SMB2_NEGOTIATE_SIGNING_ENABLED_LE cpu_to_le16(0x0001)
+#define SMB2_NEGOTIATE_SIGNING_REQUIRED 0x0002
+#define SMB2_NEGOTIATE_SIGNING_REQUIRED_LE cpu_to_le16(0x0002)
+/* Capabilities flags */
+#define SMB2_GLOBAL_CAP_DFS 0x00000001
+#define SMB2_GLOBAL_CAP_LEASING 0x00000002 /* Resp only New to SMB2.1 */
+#define SMB2_GLOBAL_CAP_LARGE_MTU 0X00000004 /* Resp only New to SMB2.1 */
+#define SMB2_GLOBAL_CAP_MULTI_CHANNEL 0x00000008 /* New to SMB3 */
+#define SMB2_GLOBAL_CAP_PERSISTENT_HANDLES 0x00000010 /* New to SMB3 */
+#define SMB2_GLOBAL_CAP_DIRECTORY_LEASING 0x00000020 /* New to SMB3 */
+#define SMB2_GLOBAL_CAP_ENCRYPTION 0x00000040 /* New to SMB3 */
+/* Internal types */
+#define SMB2_NT_FIND 0x00100000
+#define SMB2_LARGE_FILES 0x00200000
+
+#define SMB311_SALT_SIZE 32
+/* Hash Algorithm Types */
+#define SMB2_PREAUTH_INTEGRITY_SHA512 cpu_to_le16(0x0001)
+
+#define PREAUTH_HASHVALUE_SIZE 64
+
+struct preauth_integrity_info {
+ /* PreAuth integrity Hash ID */
+ __le16 Preauth_HashId;
+ /* PreAuth integrity Hash Value */
+ __u8 Preauth_HashValue[PREAUTH_HASHVALUE_SIZE];
+};
+
+/* offset is sizeof smb2_negotiate_rsp - 4 but rounded up to 8 bytes. */
+#ifdef CONFIG_SMB_SERVER_KERBEROS5
+/* sizeof(struct smb2_negotiate_rsp) - 4 =
+ * header(64) + response(64) + GSS_LENGTH(96) + GSS_PADDING(0)
+ */
+#define OFFSET_OF_NEG_CONTEXT 0xe0
+#else
+/* sizeof(struct smb2_negotiate_rsp) - 4 =
+ * header(64) + response(64) + GSS_LENGTH(74) + GSS_PADDING(6)
+ */
+#define OFFSET_OF_NEG_CONTEXT 0xd0
+#endif
+
+#define SMB2_PREAUTH_INTEGRITY_CAPABILITIES cpu_to_le16(1)
+#define SMB2_ENCRYPTION_CAPABILITIES cpu_to_le16(2)
+#define SMB2_COMPRESSION_CAPABILITIES cpu_to_le16(3)
+#define SMB2_NETNAME_NEGOTIATE_CONTEXT_ID cpu_to_le16(5)
+#define SMB2_SIGNING_CAPABILITIES cpu_to_le16(8)
+#define SMB2_POSIX_EXTENSIONS_AVAILABLE cpu_to_le16(0x100)
+
+struct smb2_neg_context {
+ __le16 ContextType;
+ __le16 DataLength;
+ __le32 Reserved;
+ /* Followed by array of data */
+} __packed;
+
+struct smb2_preauth_neg_context {
+ __le16 ContextType; /* 1 */
+ __le16 DataLength;
+ __le32 Reserved;
+ __le16 HashAlgorithmCount; /* 1 */
+ __le16 SaltLength;
+ __le16 HashAlgorithms; /* HashAlgorithms[0] since only one defined */
+ __u8 Salt[SMB311_SALT_SIZE];
+} __packed;
+
+/* Encryption Algorithms Ciphers */
+#define SMB2_ENCRYPTION_AES128_CCM cpu_to_le16(0x0001)
+#define SMB2_ENCRYPTION_AES128_GCM cpu_to_le16(0x0002)
+#define SMB2_ENCRYPTION_AES256_CCM cpu_to_le16(0x0003)
+#define SMB2_ENCRYPTION_AES256_GCM cpu_to_le16(0x0004)
+
+struct smb2_encryption_neg_context {
+ __le16 ContextType; /* 2 */
+ __le16 DataLength;
+ __le32 Reserved;
+ /* CipherCount usally 2, but can be 3 when AES256-GCM enabled */
+ __le16 CipherCount; /* AES-128-GCM and AES-128-CCM by default */
+ __le16 Ciphers[];
+} __packed;
+
+#define SMB3_COMPRESS_NONE cpu_to_le16(0x0000)
+#define SMB3_COMPRESS_LZNT1 cpu_to_le16(0x0001)
+#define SMB3_COMPRESS_LZ77 cpu_to_le16(0x0002)
+#define SMB3_COMPRESS_LZ77_HUFF cpu_to_le16(0x0003)
+
+struct smb2_compression_ctx {
+ __le16 ContextType; /* 3 */
+ __le16 DataLength;
+ __le32 Reserved;
+ __le16 CompressionAlgorithmCount;
+ __u16 Padding;
+ __le32 Reserved1;
+ __le16 CompressionAlgorithms[];
+} __packed;
+
+#define POSIX_CTXT_DATA_LEN 16
+struct smb2_posix_neg_context {
+ __le16 ContextType; /* 0x100 */
+ __le16 DataLength;
+ __le32 Reserved;
+ __u8 Name[16]; /* POSIX ctxt GUID 93AD25509CB411E7B42383DE968BCD7C */
+} __packed;
+
+struct smb2_netname_neg_context {
+ __le16 ContextType; /* 0x100 */
+ __le16 DataLength;
+ __le32 Reserved;
+ __le16 NetName[]; /* hostname of target converted to UCS-2 */
+} __packed;
+
+/* Signing algorithms */
+#define SIGNING_ALG_HMAC_SHA256 cpu_to_le16(0)
+#define SIGNING_ALG_AES_CMAC cpu_to_le16(1)
+#define SIGNING_ALG_AES_GMAC cpu_to_le16(2)
+
+struct smb2_signing_capabilities {
+ __le16 ContextType; /* 8 */
+ __le16 DataLength;
+ __le32 Reserved;
+ __le16 SigningAlgorithmCount;
+ __le16 SigningAlgorithms[];
+} __packed;
+
+struct smb2_negotiate_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 65 */
+ __le16 SecurityMode;
+ __le16 DialectRevision;
+ __le16 NegotiateContextCount; /* Prior to SMB3.1.1 was Reserved & MBZ */
+ __u8 ServerGUID[16];
+ __le32 Capabilities;
+ __le32 MaxTransactSize;
+ __le32 MaxReadSize;
+ __le32 MaxWriteSize;
+ __le64 SystemTime; /* MBZ */
+ __le64 ServerStartTime;
+ __le16 SecurityBufferOffset;
+ __le16 SecurityBufferLength;
+ __le32 NegotiateContextOffset; /* Pre:SMB3.1.1 was reserved/ignored */
+ __u8 Buffer[1]; /* variable length GSS security buffer */
+} __packed;
+
+/* Flags */
+#define SMB2_SESSION_REQ_FLAG_BINDING 0x01
+#define SMB2_SESSION_REQ_FLAG_ENCRYPT_DATA 0x04
+
+#define SMB2_SESSION_EXPIRED (0)
+#define SMB2_SESSION_IN_PROGRESS BIT(0)
+#define SMB2_SESSION_VALID BIT(1)
+
+/* Flags */
+#define SMB2_SESSION_REQ_FLAG_BINDING 0x01
+#define SMB2_SESSION_REQ_FLAG_ENCRYPT_DATA 0x04
+
+struct smb2_sess_setup_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 25 */
+ __u8 Flags;
+ __u8 SecurityMode;
+ __le32 Capabilities;
+ __le32 Channel;
+ __le16 SecurityBufferOffset;
+ __le16 SecurityBufferLength;
+ __le64 PreviousSessionId;
+ __u8 Buffer[1]; /* variable length GSS security buffer */
+} __packed;
+
+/* Flags/Reserved for SMB3.1.1 */
+#define SMB2_SHAREFLAG_CLUSTER_RECONNECT 0x0001
+
+/* Currently defined SessionFlags */
+#define SMB2_SESSION_FLAG_IS_GUEST_LE cpu_to_le16(0x0001)
+#define SMB2_SESSION_FLAG_IS_NULL_LE cpu_to_le16(0x0002)
+#define SMB2_SESSION_FLAG_ENCRYPT_DATA_LE cpu_to_le16(0x0004)
+struct smb2_sess_setup_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 9 */
+ __le16 SessionFlags;
+ __le16 SecurityBufferOffset;
+ __le16 SecurityBufferLength;
+ __u8 Buffer[1]; /* variable length GSS security buffer */
+} __packed;
+
+struct smb2_logoff_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __le16 Reserved;
+} __packed;
+
+struct smb2_logoff_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __le16 Reserved;
+} __packed;
+
+struct smb2_tree_connect_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 9 */
+ __le16 Reserved; /* Flags in SMB3.1.1 */
+ __le16 PathOffset;
+ __le16 PathLength;
+ __u8 Buffer[1]; /* variable length */
+} __packed;
+
+struct smb2_tree_connect_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 16 */
+ __u8 ShareType; /* see below */
+ __u8 Reserved;
+ __le32 ShareFlags; /* see below */
+ __le32 Capabilities; /* see below */
+ __le32 MaximalAccess;
+} __packed;
+
+/* Possible ShareType values */
+#define SMB2_SHARE_TYPE_DISK 0x01
+#define SMB2_SHARE_TYPE_PIPE 0x02
+#define SMB2_SHARE_TYPE_PRINT 0x03
+
+/*
+ * Possible ShareFlags - exactly one and only one of the first 4 caching flags
+ * must be set (any of the remaining, SHI1005, flags may be set individually
+ * or in combination.
+ */
+#define SMB2_SHAREFLAG_MANUAL_CACHING 0x00000000
+#define SMB2_SHAREFLAG_AUTO_CACHING 0x00000010
+#define SMB2_SHAREFLAG_VDO_CACHING 0x00000020
+#define SMB2_SHAREFLAG_NO_CACHING 0x00000030
+#define SHI1005_FLAGS_DFS 0x00000001
+#define SHI1005_FLAGS_DFS_ROOT 0x00000002
+#define SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS 0x00000100
+#define SHI1005_FLAGS_FORCE_SHARED_DELETE 0x00000200
+#define SHI1005_FLAGS_ALLOW_NAMESPACE_CACHING 0x00000400
+#define SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM 0x00000800
+#define SHI1005_FLAGS_FORCE_LEVELII_OPLOCK 0x00001000
+#define SHI1005_FLAGS_ENABLE_HASH 0x00002000
+
+/* Possible share capabilities */
+#define SMB2_SHARE_CAP_DFS cpu_to_le32(0x00000008)
+
+struct smb2_tree_disconnect_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __le16 Reserved;
+} __packed;
+
+struct smb2_tree_disconnect_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __le16 Reserved;
+} __packed;
+
+#define ATTR_READONLY_LE cpu_to_le32(ATTR_READONLY)
+#define ATTR_HIDDEN_LE cpu_to_le32(ATTR_HIDDEN)
+#define ATTR_SYSTEM_LE cpu_to_le32(ATTR_SYSTEM)
+#define ATTR_DIRECTORY_LE cpu_to_le32(ATTR_DIRECTORY)
+#define ATTR_ARCHIVE_LE cpu_to_le32(ATTR_ARCHIVE)
+#define ATTR_NORMAL_LE cpu_to_le32(ATTR_NORMAL)
+#define ATTR_TEMPORARY_LE cpu_to_le32(ATTR_TEMPORARY)
+#define ATTR_SPARSE_FILE_LE cpu_to_le32(ATTR_SPARSE)
+#define ATTR_REPARSE_POINT_LE cpu_to_le32(ATTR_REPARSE)
+#define ATTR_COMPRESSED_LE cpu_to_le32(ATTR_COMPRESSED)
+#define ATTR_OFFLINE_LE cpu_to_le32(ATTR_OFFLINE)
+#define ATTR_NOT_CONTENT_INDEXED_LE cpu_to_le32(ATTR_NOT_CONTENT_INDEXED)
+#define ATTR_ENCRYPTED_LE cpu_to_le32(ATTR_ENCRYPTED)
+#define ATTR_INTEGRITY_STREAML_LE cpu_to_le32(0x00008000)
+#define ATTR_NO_SCRUB_DATA_LE cpu_to_le32(0x00020000)
+#define ATTR_MASK_LE cpu_to_le32(0x00007FB7)
+
+/* Oplock levels */
+#define SMB2_OPLOCK_LEVEL_NONE 0x00
+#define SMB2_OPLOCK_LEVEL_II 0x01
+#define SMB2_OPLOCK_LEVEL_EXCLUSIVE 0x08
+#define SMB2_OPLOCK_LEVEL_BATCH 0x09
+#define SMB2_OPLOCK_LEVEL_LEASE 0xFF
+/* Non-spec internal type */
+#define SMB2_OPLOCK_LEVEL_NOCHANGE 0x99
+
+/* Desired Access Flags */
+#define FILE_READ_DATA_LE cpu_to_le32(0x00000001)
+#define FILE_LIST_DIRECTORY_LE cpu_to_le32(0x00000001)
+#define FILE_WRITE_DATA_LE cpu_to_le32(0x00000002)
+#define FILE_ADD_FILE_LE cpu_to_le32(0x00000002)
+#define FILE_APPEND_DATA_LE cpu_to_le32(0x00000004)
+#define FILE_ADD_SUBDIRECTORY_LE cpu_to_le32(0x00000004)
+#define FILE_READ_EA_LE cpu_to_le32(0x00000008)
+#define FILE_WRITE_EA_LE cpu_to_le32(0x00000010)
+#define FILE_EXECUTE_LE cpu_to_le32(0x00000020)
+#define FILE_TRAVERSE_LE cpu_to_le32(0x00000020)
+#define FILE_DELETE_CHILD_LE cpu_to_le32(0x00000040)
+#define FILE_READ_ATTRIBUTES_LE cpu_to_le32(0x00000080)
+#define FILE_WRITE_ATTRIBUTES_LE cpu_to_le32(0x00000100)
+#define FILE_DELETE_LE cpu_to_le32(0x00010000)
+#define FILE_READ_CONTROL_LE cpu_to_le32(0x00020000)
+#define FILE_WRITE_DAC_LE cpu_to_le32(0x00040000)
+#define FILE_WRITE_OWNER_LE cpu_to_le32(0x00080000)
+#define FILE_SYNCHRONIZE_LE cpu_to_le32(0x00100000)
+#define FILE_ACCESS_SYSTEM_SECURITY_LE cpu_to_le32(0x01000000)
+#define FILE_MAXIMAL_ACCESS_LE cpu_to_le32(0x02000000)
+#define FILE_GENERIC_ALL_LE cpu_to_le32(0x10000000)
+#define FILE_GENERIC_EXECUTE_LE cpu_to_le32(0x20000000)
+#define FILE_GENERIC_WRITE_LE cpu_to_le32(0x40000000)
+#define FILE_GENERIC_READ_LE cpu_to_le32(0x80000000)
+#define DESIRED_ACCESS_MASK cpu_to_le32(0xF21F01FF)
+
+/* ShareAccess Flags */
+#define FILE_SHARE_READ_LE cpu_to_le32(0x00000001)
+#define FILE_SHARE_WRITE_LE cpu_to_le32(0x00000002)
+#define FILE_SHARE_DELETE_LE cpu_to_le32(0x00000004)
+#define FILE_SHARE_ALL_LE cpu_to_le32(0x00000007)
+
+/* CreateDisposition Flags */
+#define FILE_SUPERSEDE_LE cpu_to_le32(0x00000000)
+#define FILE_OPEN_LE cpu_to_le32(0x00000001)
+#define FILE_CREATE_LE cpu_to_le32(0x00000002)
+#define FILE_OPEN_IF_LE cpu_to_le32(0x00000003)
+#define FILE_OVERWRITE_LE cpu_to_le32(0x00000004)
+#define FILE_OVERWRITE_IF_LE cpu_to_le32(0x00000005)
+#define FILE_CREATE_MASK_LE cpu_to_le32(0x00000007)
+
+#define FILE_READ_DESIRED_ACCESS_LE (FILE_READ_DATA_LE | \
+ FILE_READ_EA_LE | \
+ FILE_GENERIC_READ_LE)
+#define FILE_WRITE_DESIRE_ACCESS_LE (FILE_WRITE_DATA_LE | \
+ FILE_APPEND_DATA_LE | \
+ FILE_WRITE_EA_LE | \
+ FILE_WRITE_ATTRIBUTES_LE | \
+ FILE_GENERIC_WRITE_LE)
+
+/* Impersonation Levels */
+#define IL_ANONYMOUS_LE cpu_to_le32(0x00000000)
+#define IL_IDENTIFICATION_LE cpu_to_le32(0x00000001)
+#define IL_IMPERSONATION_LE cpu_to_le32(0x00000002)
+#define IL_DELEGATE_LE cpu_to_le32(0x00000003)
+
+/* Create Context Values */
+#define SMB2_CREATE_EA_BUFFER "ExtA" /* extended attributes */
+#define SMB2_CREATE_SD_BUFFER "SecD" /* security descriptor */
+#define SMB2_CREATE_DURABLE_HANDLE_REQUEST "DHnQ"
+#define SMB2_CREATE_DURABLE_HANDLE_RECONNECT "DHnC"
+#define SMB2_CREATE_ALLOCATION_SIZE "AlSi"
+#define SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST "MxAc"
+#define SMB2_CREATE_TIMEWARP_REQUEST "TWrp"
+#define SMB2_CREATE_QUERY_ON_DISK_ID "QFid"
+#define SMB2_CREATE_REQUEST_LEASE "RqLs"
+#define SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2 "DH2Q"
+#define SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 "DH2C"
+#define SMB2_CREATE_APP_INSTANCE_ID "\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74"
+ #define SMB2_CREATE_APP_INSTANCE_VERSION "\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10"
+#define SVHDX_OPEN_DEVICE_CONTEXT 0x83CE6F1AD851E0986E34401CC9BCFCE9
+#define SMB2_CREATE_TAG_POSIX "\x93\xAD\x25\x50\x9C\xB4\x11\xE7\xB4\x23\x83\xDE\x96\x8B\xCD\x7C"
+
+struct smb2_create_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 57 */
+ __u8 SecurityFlags;
+ __u8 RequestedOplockLevel;
+ __le32 ImpersonationLevel;
+ __le64 SmbCreateFlags;
+ __le64 Reserved;
+ __le32 DesiredAccess;
+ __le32 FileAttributes;
+ __le32 ShareAccess;
+ __le32 CreateDisposition;
+ __le32 CreateOptions;
+ __le16 NameOffset;
+ __le16 NameLength;
+ __le32 CreateContextsOffset;
+ __le32 CreateContextsLength;
+ __u8 Buffer[0];
+} __packed;
+
+struct smb2_create_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 89 */
+ __u8 OplockLevel;
+ __u8 Reserved;
+ __le32 CreateAction;
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le64 AllocationSize;
+ __le64 EndofFile;
+ __le32 FileAttributes;
+ __le32 Reserved2;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le32 CreateContextsOffset;
+ __le32 CreateContextsLength;
+ __u8 Buffer[1];
+} __packed;
+
+struct create_context {
+ __le32 Next;
+ __le16 NameOffset;
+ __le16 NameLength;
+ __le16 Reserved;
+ __le16 DataOffset;
+ __le32 DataLength;
+ __u8 Buffer[0];
+} __packed;
+
+struct create_durable_req_v2 {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le32 Timeout;
+ __le32 Flags;
+ __u8 Reserved[8];
+ __u8 CreateGuid[16];
+} __packed;
+
+struct create_durable_reconn_req {
+ struct create_context ccontext;
+ __u8 Name[8];
+ union {
+ __u8 Reserved[16];
+ struct {
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ } Fid;
+ } Data;
+} __packed;
+
+struct create_durable_reconn_v2_req {
+ struct create_context ccontext;
+ __u8 Name[8];
+ struct {
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ } Fid;
+ __u8 CreateGuid[16];
+ __le32 Flags;
+} __packed;
+
+struct create_app_inst_id {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __u8 Reserved[8];
+ __u8 AppInstanceId[16];
+} __packed;
+
+struct create_app_inst_id_vers {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __u8 Reserved[2];
+ __u8 Padding[4];
+ __le64 AppInstanceVersionHigh;
+ __le64 AppInstanceVersionLow;
+} __packed;
+
+struct create_mxac_req {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le64 Timestamp;
+} __packed;
+
+struct create_alloc_size_req {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le64 AllocationSize;
+} __packed;
+
+struct create_posix {
+ struct create_context ccontext;
+ __u8 Name[16];
+ __le32 Mode;
+ __u32 Reserved;
+} __packed;
+
+struct create_durable_rsp {
+ struct create_context ccontext;
+ __u8 Name[8];
+ union {
+ __u8 Reserved[8];
+ __u64 data;
+ } Data;
+} __packed;
+
+struct create_durable_v2_rsp {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le32 Timeout;
+ __le32 Flags;
+} __packed;
+
+struct create_mxac_rsp {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le32 QueryStatus;
+ __le32 MaximalAccess;
+} __packed;
+
+struct create_disk_id_rsp {
+ struct create_context ccontext;
+ __u8 Name[8];
+ __le64 DiskFileId;
+ __le64 VolumeId;
+ __u8 Reserved[16];
+} __packed;
+
+/* equivalent of the contents of SMB3.1.1 POSIX open context response */
+struct create_posix_rsp {
+ struct create_context ccontext;
+ __u8 Name[16];
+ __le32 nlink;
+ __le32 reparse_tag;
+ __le32 mode;
+ u8 SidBuffer[40];
+} __packed;
+
+#define SMB2_LEASE_NONE_LE cpu_to_le32(0x00)
+#define SMB2_LEASE_READ_CACHING_LE cpu_to_le32(0x01)
+#define SMB2_LEASE_HANDLE_CACHING_LE cpu_to_le32(0x02)
+#define SMB2_LEASE_WRITE_CACHING_LE cpu_to_le32(0x04)
+
+#define SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE cpu_to_le32(0x02)
+
+struct lease_context {
+ __le64 LeaseKeyLow;
+ __le64 LeaseKeyHigh;
+ __le32 LeaseState;
+ __le32 LeaseFlags;
+ __le64 LeaseDuration;
+} __packed;
+
+struct lease_context_v2 {
+ __le64 LeaseKeyLow;
+ __le64 LeaseKeyHigh;
+ __le32 LeaseState;
+ __le32 LeaseFlags;
+ __le64 LeaseDuration;
+ __le64 ParentLeaseKeyLow;
+ __le64 ParentLeaseKeyHigh;
+ __le16 Epoch;
+ __le16 Reserved;
+} __packed;
+
+struct create_lease {
+ struct create_context ccontext;
+ __u8 Name[8];
+ struct lease_context lcontext;
+} __packed;
+
+struct create_lease_v2 {
+ struct create_context ccontext;
+ __u8 Name[8];
+ struct lease_context_v2 lcontext;
+ __u8 Pad[4];
+} __packed;
+
+/* Currently defined values for close flags */
+#define SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB cpu_to_le16(0x0001)
+struct smb2_close_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 24 */
+ __le16 Flags;
+ __le32 Reserved;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+} __packed;
+
+struct smb2_close_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* 60 */
+ __le16 Flags;
+ __le32 Reserved;
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le64 AllocationSize; /* Beginning of FILE_STANDARD_INFO equivalent */
+ __le64 EndOfFile;
+ __le32 Attributes;
+} __packed;
+
+struct smb2_flush_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 24 */
+ __le16 Reserved1;
+ __le32 Reserved2;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+} __packed;
+
+struct smb2_flush_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize;
+ __le16 Reserved;
+} __packed;
+
+struct smb2_buffer_desc_v1 {
+ __le64 offset;
+ __le32 token;
+ __le32 length;
+} __packed;
+
+#define SMB2_CHANNEL_NONE cpu_to_le32(0x00000000)
+#define SMB2_CHANNEL_RDMA_V1 cpu_to_le32(0x00000001)
+#define SMB2_CHANNEL_RDMA_V1_INVALIDATE cpu_to_le32(0x00000002)
+
+struct smb2_read_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 49 */
+ __u8 Padding; /* offset from start of SMB2 header to place read */
+ __u8 Reserved;
+ __le32 Length;
+ __le64 Offset;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le32 MinimumCount;
+ __le32 Channel; /* Reserved MBZ */
+ __le32 RemainingBytes;
+ __le16 ReadChannelInfoOffset; /* Reserved MBZ */
+ __le16 ReadChannelInfoLength; /* Reserved MBZ */
+ __u8 Buffer[1];
+} __packed;
+
+struct smb2_read_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 17 */
+ __u8 DataOffset;
+ __u8 Reserved;
+ __le32 DataLength;
+ __le32 DataRemaining;
+ __u32 Reserved2;
+ __u8 Buffer[1];
+} __packed;
+
+/* For write request Flags field below the following flag is defined: */
+#define SMB2_WRITEFLAG_WRITE_THROUGH 0x00000001
+
+struct smb2_write_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 49 */
+ __le16 DataOffset; /* offset from start of SMB2 header to write data */
+ __le32 Length;
+ __le64 Offset;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le32 Channel; /* Reserved MBZ */
+ __le32 RemainingBytes;
+ __le16 WriteChannelInfoOffset; /* Reserved MBZ */
+ __le16 WriteChannelInfoLength; /* Reserved MBZ */
+ __le32 Flags;
+ __u8 Buffer[1];
+} __packed;
+
+struct smb2_write_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 17 */
+ __u8 DataOffset;
+ __u8 Reserved;
+ __le32 DataLength;
+ __le32 DataRemaining;
+ __u32 Reserved2;
+ __u8 Buffer[1];
+} __packed;
+
+#define SMB2_0_IOCTL_IS_FSCTL 0x00000001
+
+struct duplicate_extents_to_file {
+ __u64 PersistentFileHandle; /* source file handle, opaque endianness */
+ __u64 VolatileFileHandle;
+ __le64 SourceFileOffset;
+ __le64 TargetFileOffset;
+ __le64 ByteCount; /* Bytes to be copied */
+} __packed;
+
+struct smb2_ioctl_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 57 */
+ __le16 Reserved; /* offset from start of SMB2 header to write data */
+ __le32 CntCode;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le32 InputOffset; /* Reserved MBZ */
+ __le32 InputCount;
+ __le32 MaxInputResponse;
+ __le32 OutputOffset;
+ __le32 OutputCount;
+ __le32 MaxOutputResponse;
+ __le32 Flags;
+ __le32 Reserved2;
+ __u8 Buffer[1];
+} __packed;
+
+struct smb2_ioctl_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 49 */
+ __le16 Reserved; /* offset from start of SMB2 header to write data */
+ __le32 CntCode;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le32 InputOffset; /* Reserved MBZ */
+ __le32 InputCount;
+ __le32 OutputOffset;
+ __le32 OutputCount;
+ __le32 Flags;
+ __le32 Reserved2;
+ __u8 Buffer[1];
+} __packed;
+
+struct validate_negotiate_info_req {
+ __le32 Capabilities;
+ __u8 Guid[SMB2_CLIENT_GUID_SIZE];
+ __le16 SecurityMode;
+ __le16 DialectCount;
+ __le16 Dialects[1]; /* dialect (someday maybe list) client asked for */
+} __packed;
+
+struct validate_negotiate_info_rsp {
+ __le32 Capabilities;
+ __u8 Guid[SMB2_CLIENT_GUID_SIZE];
+ __le16 SecurityMode;
+ __le16 Dialect; /* Dialect in use for the connection */
+} __packed;
+
+struct smb_sockaddr_in {
+ __be16 Port;
+ __be32 IPv4address;
+ __u8 Reserved[8];
+} __packed;
+
+struct smb_sockaddr_in6 {
+ __be16 Port;
+ __be32 FlowInfo;
+ __u8 IPv6address[16];
+ __be32 ScopeId;
+} __packed;
+
+#define INTERNETWORK 0x0002
+#define INTERNETWORKV6 0x0017
+
+struct sockaddr_storage_rsp {
+ __le16 Family;
+ union {
+ struct smb_sockaddr_in addr4;
+ struct smb_sockaddr_in6 addr6;
+ };
+} __packed;
+
+#define RSS_CAPABLE 0x00000001
+#define RDMA_CAPABLE 0x00000002
+
+struct network_interface_info_ioctl_rsp {
+ __le32 Next; /* next interface. zero if this is last one */
+ __le32 IfIndex;
+ __le32 Capability; /* RSS or RDMA Capable */
+ __le32 Reserved;
+ __le64 LinkSpeed;
+ char SockAddr_Storage[128];
+} __packed;
+
+struct file_object_buf_type1_ioctl_rsp {
+ __u8 ObjectId[16];
+ __u8 BirthVolumeId[16];
+ __u8 BirthObjectId[16];
+ __u8 DomainId[16];
+} __packed;
+
+struct resume_key_ioctl_rsp {
+ __le64 ResumeKey[3];
+ __le32 ContextLength;
+ __u8 Context[4]; /* ignored, Windows sets to 4 bytes of zero */
+} __packed;
+
+struct copychunk_ioctl_req {
+ __le64 ResumeKey[3];
+ __le32 ChunkCount;
+ __le32 Reserved;
+ __u8 Chunks[1]; /* array of srv_copychunk */
+} __packed;
+
+struct srv_copychunk {
+ __le64 SourceOffset;
+ __le64 TargetOffset;
+ __le32 Length;
+ __le32 Reserved;
+} __packed;
+
+struct copychunk_ioctl_rsp {
+ __le32 ChunksWritten;
+ __le32 ChunkBytesWritten;
+ __le32 TotalBytesWritten;
+} __packed;
+
+struct file_sparse {
+ __u8 SetSparse;
+} __packed;
+
+struct file_zero_data_information {
+ __le64 FileOffset;
+ __le64 BeyondFinalZero;
+} __packed;
+
+struct file_allocated_range_buffer {
+ __le64 file_offset;
+ __le64 length;
+} __packed;
+
+struct reparse_data_buffer {
+ __le32 ReparseTag;
+ __le16 ReparseDataLength;
+ __u16 Reserved;
+ __u8 DataBuffer[]; /* Variable Length */
+} __packed;
+
+/* Completion Filter flags for Notify */
+#define FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
+#define FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002
+#define FILE_NOTIFY_CHANGE_NAME 0x00000003
+#define FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004
+#define FILE_NOTIFY_CHANGE_SIZE 0x00000008
+#define FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
+#define FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020
+#define FILE_NOTIFY_CHANGE_CREATION 0x00000040
+#define FILE_NOTIFY_CHANGE_EA 0x00000080
+#define FILE_NOTIFY_CHANGE_SECURITY 0x00000100
+#define FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200
+#define FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
+#define FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
+
+/* Flags */
+#define SMB2_WATCH_TREE 0x0001
+
+struct smb2_notify_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 32 */
+ __le16 Flags;
+ __le32 OutputBufferLength;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __u32 CompletionFileter;
+ __u32 Reserved;
+} __packed;
+
+struct smb2_notify_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 9 */
+ __le16 OutputBufferOffset;
+ __le32 OutputBufferLength;
+ __u8 Buffer[1];
+} __packed;
+
+/* SMB2 Notify Action Flags */
+#define FILE_ACTION_ADDED 0x00000001
+#define FILE_ACTION_REMOVED 0x00000002
+#define FILE_ACTION_MODIFIED 0x00000003
+#define FILE_ACTION_RENAMED_OLD_NAME 0x00000004
+#define FILE_ACTION_RENAMED_NEW_NAME 0x00000005
+#define FILE_ACTION_ADDED_STREAM 0x00000006
+#define FILE_ACTION_REMOVED_STREAM 0x00000007
+#define FILE_ACTION_MODIFIED_STREAM 0x00000008
+#define FILE_ACTION_REMOVED_BY_DELETE 0x00000009
+
+#define SMB2_LOCKFLAG_SHARED 0x0001
+#define SMB2_LOCKFLAG_EXCLUSIVE 0x0002
+#define SMB2_LOCKFLAG_UNLOCK 0x0004
+#define SMB2_LOCKFLAG_FAIL_IMMEDIATELY 0x0010
+#define SMB2_LOCKFLAG_MASK 0x0007
+
+struct smb2_lock_element {
+ __le64 Offset;
+ __le64 Length;
+ __le32 Flags;
+ __le32 Reserved;
+} __packed;
+
+struct smb2_lock_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 48 */
+ __le16 LockCount;
+ __le32 Reserved;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ /* Followed by at least one */
+ struct smb2_lock_element locks[1];
+} __packed;
+
+struct smb2_lock_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __le16 Reserved;
+} __packed;
+
+struct smb2_echo_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __u16 Reserved;
+} __packed;
+
+struct smb2_echo_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 4 */
+ __u16 Reserved;
+} __packed;
+
+/* search (query_directory) Flags field */
+#define SMB2_RESTART_SCANS 0x01
+#define SMB2_RETURN_SINGLE_ENTRY 0x02
+#define SMB2_INDEX_SPECIFIED 0x04
+#define SMB2_REOPEN 0x10
+
+struct smb2_query_directory_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 33 */
+ __u8 FileInformationClass;
+ __u8 Flags;
+ __le32 FileIndex;
+ __le64 PersistentFileId;
+ __le64 VolatileFileId;
+ __le16 FileNameOffset;
+ __le16 FileNameLength;
+ __le32 OutputBufferLength;
+ __u8 Buffer[1];
+} __packed;
+
+struct smb2_query_directory_rsp {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 9 */
+ __le16 OutputBufferOffset;
+ __le32 OutputBufferLength;
+ __u8 Buffer[1];
+} __packed;
+
+/* Possible InfoType values */
+#define SMB2_O_INFO_FILE 0x01
+#define SMB2_O_INFO_FILESYSTEM 0x02
+#define SMB2_O_INFO_SECURITY 0x03
+#define SMB2_O_INFO_QUOTA 0x04
+
+/* Security info type additionalinfo flags. See MS-SMB2 (2.2.37) or MS-DTYP */
+#define OWNER_SECINFO 0x00000001
+#define GROUP_SECINFO 0x00000002
+#define DACL_SECINFO 0x00000004
+#define SACL_SECINFO 0x00000008
+#define LABEL_SECINFO 0x00000010
+#define ATTRIBUTE_SECINFO 0x00000020
+#define SCOPE_SECINFO 0x00000040
+#define BACKUP_SECINFO 0x00010000
+#define UNPROTECTED_SACL_SECINFO 0x10000000
+#define UNPROTECTED_DACL_SECINFO 0x20000000
+#define PROTECTED_SACL_SECINFO 0x40000000
+#define PROTECTED_DACL_SECINFO 0x80000000
+
+struct smb2_query_info_req {
+ struct smb2_hdr hdr;
+ __le16 StructureSize; /* Must be 41 */