1 # Unix SMB/CIFS implementation.
2 # Copyright Volker Lendecke <vl@samba.org> 2022
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 from samba
.samba3
import libsmb_samba_internal
as libsmb
19 from samba
import reparse_symlink
20 from samba
import (ntstatus
,NTSTATUSError
)
21 from samba
.dcerpc
import security
as sec
22 import samba
.tests
.libsmb
24 class Smb2SymlinkTests(samba
.tests
.libsmb
.LibsmbTests
):
26 def connections(self
, smb1share
=None, smb2share
=None):
28 smb1share
= samba
.tests
.env_get_var_value(
29 "SMB1_SHARE", allow_missing
=True)
31 smb1share
= "nosymlinks_smb1allow"
40 except NTSTATUSError
as e
:
41 if e
.args
[0] != ntstatus
.NT_STATUS_CONNECTION_RESET
:
46 smb2share
= samba
.tests
.env_get_var_value(
47 "SMB2_SHARE", allow_missing
=True)
49 smb2share
= "nosymlinks"
58 def clean_file(self
, conn
, filename
):
61 except NTSTATUSError
as e
:
62 if e
.args
[0] == ntstatus
.NT_STATUS_FILE_IS_A_DIRECTORY
:
64 elif not (e
.args
[0] == ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
or
65 e
.args
[0] == ntstatus
.NT_STATUS_OBJECT_PATH_NOT_FOUND
):
68 def create_symlink(self
, conn
, target
, symlink
):
69 self
.clean_file(conn
, symlink
)
70 if (conn
.protocol() < libsmb
.PROTOCOL_SMB2_02
and conn
.have_posix()):
71 conn
.smb1_symlink(target
, symlink
)
73 flags
= 0 if target
[0]=='/' else 1
76 DesiredAccess
=sec
.SEC_FILE_READ_ATTRIBUTE|
77 sec
.SEC_FILE_WRITE_ATTRIBUTE|
79 FileAttributes
=libsmb
.FILE_ATTRIBUTE_NORMAL
,
80 CreateDisposition
=libsmb
.FILE_OPEN_IF
,
81 CreateOptions
=libsmb
.FILE_OPEN_REPARSE_POINT
)
82 b
= reparse_symlink
.symlink_put(target
, target
, 0, 1)
83 conn
.fsctl(syml
, libsmb
.FSCTL_SET_REPARSE_POINT
, b
, 0)
86 def assert_symlink_exception(self
, e
, expect
):
87 self
.assertEqual(e
.args
[0], ntstatus
.NT_STATUS_STOPPED_ON_SYMLINK
)
88 for k
,v
in expect
.items():
90 # Ignore symlink trust flags for now
91 expected
= v
& ~libsmb
.SYMLINK_TRUST_MASK
92 got
= e
.args
[2].get(k
) & ~libsmb
.SYMLINK_TRUST_MASK
93 self
.assertEqual((k
,got
), (k
,expected
))
95 self
.assertEqual((k
,e
.args
[2].get(k
)), (k
,v
))
97 def test_symlinkerror_directory(self
):
98 """Test a symlink in a nonterminal path component"""
99 (smb1
,smb2
) = self
.connections()
104 self
.create_symlink(smb1
, target
, symlink
)
106 with self
.assertRaises(NTSTATUSError
) as e
:
107 fd
= smb2
.create_ex(f
'{symlink}\\{suffix}')
109 self
.assert_symlink_exception(
111 { 'unparsed_path_length' : len(suffix
)+1,
112 'substitute_name' : target
,
113 'print_name' : target
,
114 'flags' : 0x20000001 })
116 self
.clean_file(smb1
, symlink
)
118 def test_symlinkerror_file(self
):
119 """Test a simple symlink in a terminal path"""
120 (smb1
,smb2
) = self
.connections()
124 self
.create_symlink(smb1
, target
, symlink
)
126 with self
.assertRaises(NTSTATUSError
) as e
:
127 fd
= smb2
.create_ex(f
'{symlink}')
129 self
.assert_symlink_exception(
131 { 'unparsed_path_length' : 0,
132 'substitute_name' : target
,
133 'print_name' : target
,
134 'flags' : 0x20000001 })
136 self
.clean_file(smb1
, symlink
)
138 def test_symlinkerror_absolute_outside_share(self
):
140 Test symlinks to outside of the share
141 We return the contents 1:1
143 (smb1
,smb2
) = self
.connections()
146 for target
in ["/etc", "//foo/bar", "/"]:
148 self
.create_symlink(smb1
, target
, symlink
)
150 with self
.assertRaises(NTSTATUSError
) as e
:
151 fd
= smb2
.create_ex(f
'{symlink}')
153 self
.assert_symlink_exception(
155 { 'unparsed_path_length' : 0,
156 'substitute_name' : target
,
157 'print_name' : target
,
160 self
.clean_file(smb1
, symlink
)
162 def test_symlinkerror_absolute_inshare(self
):
163 """Test an absolute symlink inside the share"""
164 (smb1
,smb2
) = self
.connections()
167 localpath
=samba
.tests
.env_get_var_value("LOCAL_PATH")
168 shareroot
=f
'{localpath}/nosymlinks'
170 target
=f
'{shareroot}/{rel_dest}'
172 self
.create_symlink(smb1
, target
, symlink
)
174 with self
.assertRaises(NTSTATUSError
) as e
:
175 fd
= smb2
.create_ex(f
'{symlink}')
177 self
.assert_symlink_exception(
179 { 'unparsed_path_length' : 0,
180 'substitute_name' : rel_dest
,
181 'print_name' : rel_dest
,
184 self
.clean_file(smb1
, symlink
)
186 def test_symlink_reparse_data_buffer_parse(self
):
187 """Test parsing a symlink reparse buffer coming from Windows"""
189 buf
= (b
'\x0c\x00\x00\xa0\x18\x00\x00\x00'
190 b
'\x06\x00\x06\x00\x00\x00\x06\x00'
191 b
'\x01\x00\x00\x00\x62\x00\x61\x00'
192 b
'\x72\x00\x62\x00\x61\x00\x72\x00')
195 syml
= reparse_symlink
.symlink_get(buf
);
197 self
.fail("Could not parse symlink buffer")
199 self
.assertEqual(syml
, ('bar', 'bar', 0, 1));
201 def test_bug15505(self
):
202 """Test an absolute intermediate symlink inside the share"""
203 (smb1
,smb2
) = self
.connections(smb1share
="tmp",smb2share
="tmp")
206 localpath
=samba
.tests
.env_get_var_value("LOCAL_PATH")
209 self
.addCleanup(self
.clean_file
, smb1
, "sub")
211 self
.create_symlink(smb1
, f
'{localpath}/sub1', "sub/lnk")
212 self
.addCleanup(self
.clean_file
, smb1
, "sub/lnk")
215 self
.addCleanup(self
.clean_file
, smb1
, "sub1")
217 fd
= smb1
.create("sub1/x", CreateDisposition
=libsmb
.FILE_CREATE
);
219 self
.addCleanup(self
.clean_file
, smb1
, "sub1/x")
221 fd
= smb2
.create("sub\\lnk\\x")
224 if __name__
== '__main__':