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
2024-06-17 13:45:18 +03:00
import stat
2022-11-10 20:31:11 +03:00
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
2024-06-17 13:45:18 +03:00
def connection_posix ( self ) :
share = samba . tests . env_get_var_value ( " SHARENAME " , allow_missing = True )
if not share :
share = " posix_share "
conn = libsmb . Conn (
self . server_ip ,
share ,
self . lp ,
self . creds ,
force_smb1 = True )
conn . smb1_posix ( )
return conn
2022-11-10 20:31:11 +03:00
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 ,
2024-05-03 15:52:42 +03:00
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE | sec . SEC_STD_DELETE ,
2022-11-10 20:31:11 +03:00
CreateDisposition = libsmb . FILE_CREATE )
2022-12-02 12:20:06 +03:00
2024-05-03 15:52:42 +03:00
conn . delete_on_close ( fd , 1 )
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
2024-05-09 12:54:31 +03:00
def test_query_reparse_tag ( self ) :
conn = self . connection ( )
filename = ' reparse '
self . clean_file ( conn , filename )
fd = conn . create (
filename ,
DesiredAccess = sec . SEC_FILE_READ_ATTRIBUTE |
sec . SEC_FILE_WRITE_ATTRIBUTE |
sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_CREATE )
conn . delete_on_close ( fd , 1 )
info = conn . qfileinfo ( fd , libsmb . FSCC_FILE_ATTRIBUTE_TAG_INFORMATION ) ;
self . assertEqual ( info [ ' tag ' ] , 0 )
b = reparse_symlink . put ( 0x80000026 , 0 , b ' asdf ' )
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
info = conn . qfileinfo ( fd , libsmb . FSCC_FILE_ATTRIBUTE_TAG_INFORMATION ) ;
self . assertEqual ( info [ ' tag ' ] , 0x80000026 )
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
2023-01-02 18:01:28 +03:00
def test_query_dir_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 . symlink_put ( " y " , " y " , 0 , 0 )
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
conn . close ( fd )
dirents = conn . list ( " " , filename )
self . assertEqual (
dirents [ 0 ] [ " reparse_tag " ] ,
libsmb . IO_REPARSE_TAG_SYMLINK )
self . clean_file ( conn , filename )
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 )
2024-05-05 13:16:39 +03:00
2022-11-10 20:31:11 +03:00
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
2024-05-05 13:16:39 +03:00
try :
conn . fsctl ( dir_fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
except NTSTATUSError as e :
err = e . args [ 0 ]
if ( err != ntstatus . NT_STATUS_ACCESS_DENIED ) :
raise
if ( err == ntstatus . NT_STATUS_ACCESS_DENIED ) :
self . fail ( " Could not set reparse point on directory " )
conn . delete_on_close ( fd , 1 )
return
2022-12-01 16:49:37 +03:00
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 )
2024-05-05 13:16:39 +03:00
2022-11-10 20:31:11 +03:00
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 ]
conn . delete_on_close ( fd , 1 )
conn . close ( fd )
conn . delete_on_close ( dir_fd , 1 )
conn . close ( dir_fd )
2024-05-05 13:16:39 +03:00
ok = ( err == ntstatus . NT_STATUS_DIRECTORY_NOT_EMPTY )
if not ok :
self . fail ( f ' set_reparse on nonempty directory returned { err } ' )
2022-11-10 20:31:11 +03:00
# 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 )
2024-05-06 17:35:25 +03:00
def test_delete_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 )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' asdfasdfasdfasdfasdfasdf ' )
conn . fsctl ( fd , libsmb . FSCTL_SET_REPARSE_POINT , b , 0 )
conn . close ( fd )
( fd , cr , _ ) = conn . create_ex (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE | 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 )
b = reparse_symlink . put ( 0x80000026 , 0 , b ' ' )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_DELETE_REPARSE_POINT , b , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_TAG_MISMATCH )
b = reparse_symlink . put ( 0x80000026 , 0 , b ' ' )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_DELETE_REPARSE_POINT , b , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_DATA_INVALID )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' ' )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_DELETE_REPARSE_POINT , b , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_IO_REPARSE_DATA_INVALID )
b = reparse_symlink . put ( 0x80000025 , 0 , b ' ' )
conn . fsctl ( fd , libsmb . FSCTL_DELETE_REPARSE_POINT , b , 0 )
with self . assertRaises ( NTSTATUSError ) as e :
conn . fsctl ( fd , libsmb . FSCTL_DELETE_REPARSE_POINT , b , 0 )
self . assertEqual ( e . exception . args [ 0 ] ,
ntstatus . NT_STATUS_NOT_A_REPARSE_POINT )
conn . close ( fd )
( fd , cr , _ ) = conn . create_ex (
filename ,
DesiredAccess = sec . SEC_FILE_WRITE_ATTRIBUTE | sec . SEC_STD_DELETE ,
CreateDisposition = libsmb . FILE_OPEN )
self . assertEqual ( cr [ ' file_attributes ' ] &
libsmb . FILE_ATTRIBUTE_REPARSE_POINT ,
0 )
conn . delete_on_close ( fd , 1 )
conn . close ( fd )
2024-07-07 21:21:12 +03:00
def do_test_nfs_reparse ( self , filename , filetype , nfstype ) :
""" Test special file reparse tag """
2024-06-17 13:45:18 +03:00
smb2 = self . connection ( )
smb1 = self . connection_posix ( )
self . clean_file ( smb2 , filename )
2024-07-07 21:21:12 +03:00
smb1 . mknod ( filename , filetype | 0o755 )
2024-06-17 13:45:18 +03:00
fd = smb2 . create (
filename ,
DesiredAccess = sec . SEC_FILE_READ_ATTRIBUTE | sec . SEC_STD_DELETE ,
CreateOptions = libsmb . FILE_OPEN_REPARSE_POINT ,
CreateDisposition = libsmb . FILE_OPEN )
smb2 . delete_on_close ( fd , 1 )
info = smb2 . qfileinfo ( fd , libsmb . FSCC_FILE_ATTRIBUTE_TAG_INFORMATION ) ;
self . assertEqual ( info [ ' tag ' ] , libsmb . IO_REPARSE_TAG_NFS )
reparse = smb2 . fsctl ( fd , libsmb . FSCTL_GET_REPARSE_POINT , b ' ' , 1024 )
( tag , ) = reparse_symlink . get ( reparse )
2024-07-07 21:21:12 +03:00
self . assertEqual ( tag , nfstype )
def test_fifo_reparse ( self ) :
""" Test FIFO reparse tag """
self . do_test_nfs_reparse ( ' fifo ' , stat . S_IFIFO , ' NFS_SPECFILE_FIFO ' )
2024-06-17 13:45:18 +03:00
2024-07-07 21:28:59 +03:00
def test_sock_reparse ( self ) :
""" Test SOCK reparse tag """
self . do_test_nfs_reparse ( ' sock ' , stat . S_IFSOCK , ' NFS_SPECFILE_SOCK ' )
2022-11-10 20:31:11 +03:00
if __name__ == ' __main__ ' :
import unittest
unittest . main ( )