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 NTSTATUSError
,ntstatus
20 import samba
.tests
.libsmb
21 from samba
.dcerpc
import security
22 from samba
.common
import get_string
23 from samba
.dcerpc
import smb3posix
24 from samba
.ndr
import ndr_unpack
25 from samba
.dcerpc
.security
import dom_sid
26 from samba
import reparse_symlink
30 def posix_context(mode
):
31 return (libsmb
.SMB2_CREATE_TAG_POSIX
, mode
.to_bytes(4, 'little'))
33 class Smb3UnixTests(samba
.tests
.libsmb
.LibsmbTests
):
38 self
.samsid
= os
.environ
["SAMSID"]
39 prefix_abs
= os
.environ
["PREFIX_ABS"]
40 p
= subprocess
.run(['stat', '-f', '-c', '%T', prefix_abs
], capture_output
=True, text
=True)
41 self
.fstype
= p
.stdout
.strip().lower()
43 def connections(self
, share1
=None, posix1
=False, share2
=None, posix2
=True):
45 share1
= samba
.tests
.env_get_var_value(
46 "SHARE1", allow_missing
=True)
51 share2
= samba
.tests
.env_get_var_value(
52 "SHARE2", allow_missing
=True)
72 def test_negotiate_context_posix(self
):
79 self
.assertTrue(c
.have_posix())
81 def test_negotiate_context_posix_invalid_length(self
):
82 with self
.assertRaises(NTSTATUSError
) as cm
:
88 negotiate_contexts
=[(0x100, b
'01234')])
91 self
.assertEqual(e
.args
[0], ntstatus
.NT_STATUS_INVALID_PARAMETER
)
93 def test_negotiate_context_posix_invalid_blob(self
):
99 negotiate_contexts
=[(0x100, b
'0123456789012345')])
100 self
.assertFalse(c
.have_posix())
102 def test_posix_create_context(self
):
109 self
.assertTrue(c
.have_posix())
111 cc_in
=[(libsmb
.SMB2_CREATE_TAG_POSIX
,b
'0000')]
112 fnum
,_
,cc_out
= c
.create_ex("",CreateContexts
=cc_in
)
113 self
.assertEqual(cc_in
[0][0],cc_out
[0][0])
117 def test_posix_create_invalid_context_length(self
):
124 self
.assertTrue(c
.have_posix())
126 cc_in
=[(libsmb
.SMB2_CREATE_TAG_POSIX
,b
'00000')]
128 with self
.assertRaises(NTSTATUSError
) as cm
:
129 fnum
,_
,cc_out
= c
.create_ex("",CreateContexts
=cc_in
)
132 self
.assertEqual(e
.args
[0], ntstatus
.NT_STATUS_INVALID_PARAMETER
)
134 def delete_test_file(self
, c
, fname
, mode
=0):
135 f
,_
,cc_out
= c
.create_ex(fname
,
136 DesiredAccess
=security
.SEC_STD_ALL
,
137 CreateDisposition
=libsmb
.FILE_OPEN
,
138 CreateContexts
=[posix_context(mode
)])
139 c
.delete_on_close(f
, True)
142 def test_posix_query_dir(self
):
151 self
.assertTrue(c
.have_posix())
154 fname
= '\\test%d' % i
155 f
,_
,cc_out
= c
.create_ex(fname
,
156 CreateDisposition
=libsmb
.FILE_OPEN_IF
,
157 CreateContexts
=[posix_context(0o744)])
159 test_files
.append(fname
)
161 expected_count
= len(c
.list(''))
162 self
.assertNotEqual(expected_count
, 0, 'No files were found')
164 actual_count
= len(c
.list('',
165 info_level
=libsmb
.SMB2_FIND_POSIX_INFORMATION
))
166 self
.assertEqual(actual_count
-2, expected_count
,
167 'SMB2_FIND_POSIX_INFORMATION failed to list contents')
170 if len(test_files
) > 0:
171 for fname
in test_files
:
172 self
.delete_test_file(c
, fname
)
174 def test_posix_reserved_char(self
):
181 self
.assertTrue(c
.have_posix())
183 test_files
= ['a ', 'a ', '. ', '. ', 'a.',
184 '.a', ' \\ ', '>', '<' '?']
186 for fname
in test_files
:
188 f
,_
,cc_out
= c
.create_ex('\\%s' % fname
,
189 CreateDisposition
=libsmb
.FILE_CREATE
,
190 DesiredAccess
=security
.SEC_STD_DELETE
,
191 CreateContexts
=[posix_context(0o744)])
192 except NTSTATUSError
as e
:
194 c
.delete_on_close(f
, True)
197 def test_posix_delete_on_close(self
):
204 self
.assertTrue(c
.have_posix())
206 f
,_
,cc_out
= c
.create_ex('\\TESTING999',
207 DesiredAccess
=security
.SEC_STD_ALL
,
208 CreateDisposition
=libsmb
.FILE_CREATE
,
209 CreateContexts
=[posix_context(0o744)])
210 c
.delete_on_close(f
, True)
213 def test_posix_case_sensitive(self
):
221 self
.assertTrue(c
.have_posix())
223 f
,_
,cc_out
= c
.create_ex('\\xx',
224 DesiredAccess
=security
.SEC_STD_ALL
,
225 CreateDisposition
=libsmb
.FILE_CREATE
,
226 CreateContexts
=[posix_context(0o644)])
231 f
,_
,cc_out
= c
.create_ex('\\XX',
232 DesiredAccess
=security
.SEC_STD_ALL
,
233 CreateDisposition
=libsmb
.FILE_OPEN
,
234 CreateContexts
=[posix_context(0)])
235 except NTSTATUSError
:
241 self
.assertFalse(fail
, "Opening uppercase file didn't fail")
244 self
.delete_test_file(c
, '\\xx')
246 def test_posix_perm_files(self
):
255 self
.assertTrue(c
.have_posix())
257 for perm
in range(0o600, 0o7777+1):
258 # Owner write permission is required or cleanup will fail, and
259 # owner read is required to list the file if O_PATH is disabled
260 if perm
& 0o600 != 0o600:
263 # Don't create with setuid or setgid.
264 if perm
& 0o6000 != 0:
267 fname
= 'testfile%04o' % perm
268 test_files
[fname
] = perm
269 f
,_
,cc_out
= c
.create_ex('\\%s' % fname
,
270 DesiredAccess
=security
.SEC_FILE_ALL
,
271 CreateDisposition
=libsmb
.FILE_CREATE
,
272 CreateContexts
=[posix_context(perm
)])
273 if perm
& 0o200 == 0o200:
274 c
.write(f
, buffer=b
"data", offset
=0)
277 dname
= 'testdir%04o' % perm
278 test_files
[dname
] = perm
279 f
,_
,cc_out
= c
.create_ex('\\%s' % dname
,
280 DesiredAccess
=security
.SEC_STD_ALL
,
281 CreateDisposition
=libsmb
.FILE_CREATE
,
282 CreateOptions
=libsmb
.FILE_DIRECTORY_FILE
,
283 CreateContexts
=[posix_context(perm
)])
286 res
= c
.list("", info_level
=libsmb
.SMB2_FIND_POSIX_INFORMATION
)
288 found_files
= {get_string(i
['name']): i
for i
in res
}
289 for fname
,perm
in test_files
.items():
290 self
.assertIn(get_string(fname
), found_files
.keys(),
291 'Test file not found')
292 self
.assertEqual(test_files
[fname
], found_files
[fname
]['perms'],
293 'Requested %04o, Received %04o' % \
294 (test_files
[fname
], found_files
[fname
]['perms']))
296 self
.assertEqual(found_files
[fname
]['reparse_tag'],
297 libsmb
.IO_REPARSE_TAG_RESERVED_ZERO
)
298 self
.assertEqual(found_files
[fname
]['perms'], perm
)
299 self
.assertEqual(found_files
[fname
]['owner_sid'],
300 self
.samsid
+ "-1000")
301 self
.assertTrue(found_files
[fname
]['group_sid'].startswith("S-1-22-2-"))
303 if fname
.startswith("testfile"):
304 self
.assertEqual(found_files
[fname
]['nlink'], 1)
305 self
.assertEqual(found_files
[fname
]['size'], 4)
306 self
.assertEqual(found_files
[fname
]['allocaction_size'],
308 self
.assertEqual(found_files
[fname
]['attrib'],
309 libsmb
.FILE_ATTRIBUTE_ARCHIVE
)
311 # Note: btrfs always reports the link count of directories as one.
312 if self
.fstype
== "btrfs":
313 self
.assertEqual(found_files
[fname
]['nlink'], 1)
315 self
.assertEqual(found_files
[fname
]['nlink'], 2)
316 self
.assertEqual(found_files
[fname
]['attrib'],
317 libsmb
.FILE_ATTRIBUTE_DIRECTORY
)
320 if len(test_files
) > 0:
321 for fname
in test_files
.keys():
322 self
.delete_test_file(c
, '\\%s' % fname
)
324 def test_share_root_null_sids_fid(self
):
331 self
.assertTrue(c
.have_posix())
333 res
= c
.list("", info_level
=libsmb
.SMB2_FIND_POSIX_INFORMATION
)
334 found_files
= {get_string(i
['name']): i
for i
in res
}
335 dotdot
= found_files
['..']
336 self
.assertEqual('S-1-0-0', dotdot
['owner_sid'],
337 'The owner sid for .. was not NULL')
338 self
.assertEqual('S-1-0-0', dotdot
['group_sid'],
339 'The group sid for .. was not NULL')
340 self
.assertEqual(0, dotdot
['ino'], 'The ino for .. was not 0')
341 self
.assertEqual(0, dotdot
['dev'], 'The dev for .. was not 0')
343 def test_create_context_basic1(self
):
345 Check basic CreateContexts response
354 self
.assertTrue(c
.have_posix())
356 f
,_
,cc_out
= c
.create_ex('\\test_create_context_basic1_file',
357 DesiredAccess
=security
.SEC_STD_ALL
,
358 CreateDisposition
=libsmb
.FILE_CREATE
,
359 CreateContexts
=[posix_context(0o600)])
362 cc
= ndr_unpack(smb3posix
.smb3_posix_cc_info
, cc_out
[0][1])
364 self
.assertEqual(cc
.nlinks
, 1)
365 self
.assertEqual(cc
.reparse_tag
, libsmb
.IO_REPARSE_TAG_RESERVED_ZERO
)
366 self
.assertEqual(cc
.posix_perms
, 0o600)
367 self
.assertEqual(cc
.owner
, dom_sid(self
.samsid
+ "-1000"))
368 self
.assertTrue(str(cc
.group
).startswith("S-1-22-2-"))
370 f
,_
,cc_out
= c
.create_ex('\\test_create_context_basic1_dir',
371 DesiredAccess
=security
.SEC_STD_ALL
,
372 CreateDisposition
=libsmb
.FILE_CREATE
,
373 CreateOptions
=libsmb
.FILE_DIRECTORY_FILE
,
374 CreateContexts
=[posix_context(0o700)])
378 cc
= ndr_unpack(smb3posix
.smb3_posix_cc_info
, cc_out
[0][1])
380 # Note: btrfs always reports the link count of directories as one.
381 if self
.fstype
== "btrfs":
382 self
.assertEqual(cc
.nlinks
, 1)
384 self
.assertEqual(cc
.nlinks
, 2)
386 self
.assertEqual(cc
.reparse_tag
, libsmb
.IO_REPARSE_TAG_RESERVED_ZERO
)
387 self
.assertEqual(cc
.posix_perms
, 0o700)
388 self
.assertEqual(cc
.owner
, dom_sid(self
.samsid
+ "-1000"))
389 self
.assertTrue(str(cc
.group
).startswith("S-1-22-2-"))
392 self
.delete_test_file(c
, '\\test_create_context_basic1_file')
393 self
.delete_test_file(c
, '\\test_create_context_basic1_dir')
395 def test_create_context_reparse(self
):
397 Check reparse tag in posix create context response
406 self
.assertTrue(c
.have_posix())
410 f
,_
,cc_out
= c
.create_ex('\\reparse',
411 DesiredAccess
=security
.SEC_STD_ALL
,
412 CreateDisposition
=libsmb
.FILE_CREATE
,
413 CreateContexts
=[posix_context(0o600)])
415 cc
= ndr_unpack(smb3posix
.smb3_posix_cc_info
, cc_out
[0][1])
416 self
.assertEqual(cc
.reparse_tag
, libsmb
.IO_REPARSE_TAG_RESERVED_ZERO
)
418 b
= reparse_symlink
.put(tag
, 0, b
'asdf')
419 c
.fsctl(f
, libsmb
.FSCTL_SET_REPARSE_POINT
, b
, 0)
423 f
,_
,cc_out
= c
.create_ex('\\reparse',
424 DesiredAccess
=security
.SEC_STD_ALL
,
425 CreateDisposition
=libsmb
.FILE_OPEN
,
426 CreateContexts
=[posix_context(0o600)])
429 cc
= ndr_unpack(smb3posix
.smb3_posix_cc_info
, cc_out
[0][1])
430 self
.assertEqual(cc
.reparse_tag
, tag
)
433 self
.delete_test_file(c
, '\\reparse')
435 def test_delete_on_close(self
):
437 Test two opens with delete-on-close:
440 Closing handle 1 should unlink the file, a subsequent directory
441 listing shouldn't list the deleted file.
443 (winconn
,posixconn
) = self
.connections()
445 self
.clean_file(winconn
, 'test_delete_on_close')
447 fdw
= winconn
.create(
448 'test_delete_on_close',
449 DesiredAccess
=security
.SEC_FILE_WRITE_ATTRIBUTE | security
.SEC_STD_DELETE
,
451 CreateDisposition
=libsmb
.FILE_CREATE
)
452 self
.addCleanup(self
.clean_file
, winconn
, 'test_delete_on_close')
454 fdp
,_
,_
= posixconn
.create_ex(
455 'test_delete_on_close',
456 DesiredAccess
=security
.SEC_FILE_WRITE_ATTRIBUTE | security
.SEC_STD_DELETE
,
458 CreateDisposition
=libsmb
.FILE_OPEN
,
459 CreateContexts
=[posix_context(0o600)])
461 winconn
.delete_on_close(fdw
, 1)
462 posixconn
.delete_on_close(fdp
, 1)
466 # The file should now already be deleted
467 l
= winconn
.list('', mask
='test_delete_on_close')
468 found_files
= {get_string(f
['name']): f
for f
in l
}
469 self
.assertFalse('test_delete_on_close' in found_files
)