2022-11-10 20:31:11 +03:00
# Unix SMB/CIFS implementation.
# Copyright Volker Lendecke <vl@samba.org> 2022
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from samba . samba3 import libsmb_samba_internal as libsmb
from samba import ( ntstatus , NTSTATUSError )
from samba . dcerpc import security as sec
from samba import reparse_symlink
import samba . tests . libsmb
class ReparsePoints ( samba . tests . libsmb . LibsmbTests ) :
def connection ( self ) :
2023-01-02 15:56:12 +03:00
share = samba . tests . env_get_var_value ( " SHARENAME " , allow_missing = True )
if not share :
share = " tmp "
2022-11-10 20:31:11 +03:00
smb1 = samba . tests . env_get_var_value ( " SMB1 " , allow_missing = True )
conn = libsmb . Conn (
self . server_ip ,
share ,
self . lp ,
self . creds ,
force_smb1 = smb1 )
return conn
def clean_file ( self , conn , filename ) :
try :
conn . unlink ( filename )
except NTSTATUSError as e :
err = e . args [ 0 ]
ok = ( err == ntstatus . NT_STATUS_OBJECT_NAME_NOT_FOUND )
ok | = ( err == ntstatus . NT_STATUS_OBJECT_PATH_NOT_FOUND )
2022-12-01 16:48:46 +03:00
ok | = ( err == ntstatus . NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED )
2022-11-10 20:31:11 +03:00
if not ok :
raise
def test_error_not_a_reparse_point ( self ) :
conn = self . connection ( )
filename = ' reparse '
self . clean_file ( conn , filename )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE ,
CreateDisposition = libsmb . FILE_CREATE )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_GET_REPARSE_POINT , b ' ' , 1024 )
2022-12-02 11:26:56 +03:00
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_NOT_A_REPARSE_POINT )
2022-11-10 20:31:11 +03:00
conn . close ( fd )
self . clean_file ( conn , filename )
def test_create_reparse ( self ) :
conn = self . connection ( )
filename = ' reparse '
self . clean_file ( conn , filename )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE ,
CreateDisposition = libsmb . FILE_CREATE )
2022-12-02 12:20:06 +03:00
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b ' ' , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_INVALID_BUFFER_SIZE )
2022-12-02 12:34:55 +03:00
for i in range ( 1 , 15 ) :
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , i * b ' 0 ' , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_DATA_INVALID )
# Create a syntactically valid [MS-FSCC] 2.1.2.2 REPARSE_DATA_BUFFER
2022-11-10 20:31:11 +03:00
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
2022-12-02 12:34:55 +03:00
# Show that SET_REPARSE_POINT does exact length checks
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b + b ' 0 ' , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_DATA_INVALID )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b [ : - 1 ] , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_DATA_INVALID )
2024-05-03 15:49:24 +03:00
# Exact length works
2022-11-10 20:31:11 +03:00
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
2024-05-03 15:49:46 +03:00
b = reparse_symlink . put ( 0x80000026 , 0 , b ' asdf ' )
# We can't overwrite an existing reparse point with a different tag
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_TAG_MISMATCH )
2022-11-10 20:31:11 +03:00
2022-12-01 17:14:03 +03:00
# Show that we can write to a reparse point when opened properly
def test_write_reparse ( self ) :
conn = self . connection ( )
filename = ' reparse '
self . clean_file ( conn , filename )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE ,
CreateDisposition = libsmb . FILE_CREATE )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
2023-08-17 02:09:31 +03:00
conn . close ( fd )
2022-12-01 17:14:03 +03:00
fd , cr , _ = conn . create_ex (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_DATA | sec . SEC_STD_DELETE ,
CreateOptions = libsmb . FILE_OPEN_REPARSE_POINT ,
CreateDisposition = libsmb . FILE_OPEN )
self . assertEqual (
cr [ ' file_attributes ' ] & libsmb . FILE_ATTRIBUTE_REPARSE_POINT ,
libsmb . FILE_ATTRIBUTE_REPARSE_POINT )
conn . write ( fd , b ' x ' , 1 )
conn . delete_on_close ( fd , 1 )
2023-08-17 02:09:31 +03:00
conn . close ( fd )
2022-12-01 17:14:03 +03:00
2022-11-10 20:31:11 +03:00
# Show that directories can carry reparse points
def test_create_reparse_directory ( self ) :
conn = self . connection ( )
dirname = " reparse_dir "
2022-12-01 16:49:37 +03:00
filename = f ' { dirname } \\ file.txt '
2022-11-10 20:31:11 +03:00
2022-12-01 16:49:37 +03:00
self . clean_file ( conn , filename )
2022-11-10 20:31:11 +03:00
self . clean_file ( conn , dirname )
2022-12-01 16:49:37 +03:00
dir_fd = conn . create (
2022-11-10 20:31:11 +03:00
dirname ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE |
sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_CREATE ,
CreateOptions = libsmb . FILE_DIRECTORY_FILE )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
2022-12-01 16:49:37 +03:00
conn . fsctl ( dir_fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
with self . assertRaises ( NTSTATUSError ) as e :
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_CREATE )
2022-12-02 11:26:56 +03:00
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED )
2022-12-01 16:49:37 +03:00
conn . delete_on_close ( dir_fd , 1 )
2023-08-17 02:09:31 +03:00
conn . close ( dir_fd )
2022-11-10 20:31:11 +03:00
# Only empty directories can carry reparse points
def test_create_reparse_nonempty_directory ( self ) :
conn = self . connection ( )
dirname = " reparse_dir "
filename = f ' { dirname } \\ file.txt '
self . clean_file ( conn , filename )
self . clean_file ( conn , dirname )
dir_fd = conn . create (
dirname ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE |
sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_CREATE ,
CreateOptions = libsmb . FILE_DIRECTORY_FILE )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE |
sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_CREATE )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdf ' )
try :
conn . fsctl ( dir_fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
except NTSTATUSError as e :
err = e . args [ 0 ]
ok = ( err == ntstatus . NT_STATUS_DIRECTORY_NOT_EMPTY )
if not ok :
raise
conn . delete_on_close ( fd , 1 )
conn . close ( fd )
conn . delete_on_close ( dir_fd , 1 )
conn . close ( dir_fd )
# Show that reparse point opens respect share modes
def test_reparse_share_modes ( self ) :
conn = self . connection ( )
filename = ' reparse '
self . clean_file ( conn , filename )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE ,
CreateDisposition = libsmb . FILE_CREATE )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
2023-08-17 02:09:31 +03:00
conn . close ( fd )
2022-11-10 20:31:11 +03:00
fd1 = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_READ_DATA | sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_OPEN ,
CreateOptions = libsmb . FILE_OPEN_REPARSE_POINT )
with self . assertRaises ( NTSTATUSError ) as e :
fd2 = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_READ_DATA ,
CreateDisposition = libsmb . FILE_OPEN ,
CreateOptions = libsmb . FILE_OPEN_REPARSE_POINT )
2022-12-02 11:26:56 +03:00
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_SHARING_VIOLATION )
2022-11-10 20:31:11 +03:00
2023-08-17 02:09:31 +03:00
conn . delete_on_close ( fd1 , 1 )
2022-11-10 20:31:11 +03:00
conn . close ( fd1 )
if __name__ == ' __main__ ' :
import unittest
unittest . main ( )