ruby_smb 3.3.20 → 3.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 370a9f235e5a6ca157a24cd68b3cea3d080b83267d3636a35e436c9637d12992
4
- data.tar.gz: b821e6d664809c263125e6a62faf9f594008a0451fd07c105567e04a4af81c2e
3
+ metadata.gz: 6a002233be5acb354928b1a3144a55f1e7224ca543ddd02ca8aed49f10d147bf
4
+ data.tar.gz: d410e410c764cec434426a75dab79b46f72700862750b470c068e3eee822d43c
5
5
  SHA512:
6
- metadata.gz: ee8ab909240175369bde0377f4917d24e3507b7d6a6cade9de0ed2e2e6cbdb3af03207965a922f4e4e2da15377d3cdf17c63fccf0f1b7d6b640f7983e6f345f3
7
- data.tar.gz: 2477f7b6bfd0567f41b1eae568a0d2d7ae463495a3614784d5663711d9e0a94ed36b127e2ed0fb19deb606115deb7dba7f51c47be7111fb409d7fde18c9e58e5
6
+ metadata.gz: a7e1295bb0a883cb00546a32ead1d647254bce82d8cd1bbad3217c3d8d68a8fb404a488f2b97b0a494e899fe33d39cd7f552b5e0515f202d97bb53525d869ca7
7
+ data.tar.gz: 698f56e64e2c419c4faddaaf18d0efa201e162a6cfabfe580a2472f98bb16d6f4729d2166aa546336357326847db6cdd520f37c26e5305a2019b5ec5397513eb
@@ -0,0 +1,31 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ module QueryFsInformationLevel
6
+ # Response data for SMB_QUERY_CIFS_UNIX_INFO (0x0200) from the
7
+ # CIFS UNIX Extensions. 12 bytes: major/minor version pair plus
8
+ # a 64-bit capability bitfield that the client echoes back in
9
+ # SMB_SET_CIFS_UNIX_INFO to enable UNIX extensions for the session.
10
+ #
11
+ # Outside of MS-CIFS; wire format defined by the CIFS UNIX
12
+ # Extensions draft maintained by the Samba team. The parent
13
+ # subcommand is documented at
14
+ # [MS-CIFS 2.2.6.4 TRANS2_QUERY_FS_INFORMATION (0x0003)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/a96c1c03-cade-4a4a-81a9-b00674d23d93).
15
+ # The on-disk layout matches Samba's server-side `unix_info`
16
+ # struct at
17
+ # [source3/smbd/globals.h:419-424](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/globals.h#L419-L424),
18
+ # populated by `call_trans2qfsinfo` at
19
+ # [source3/smbd/smb1_trans2.c:1633-1703](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1633-L1703).
20
+ class QueryFsCifsUnixInfo < BinData::Record
21
+ endian :little
22
+
23
+ uint16 :major_version, label: 'Major Version'
24
+ uint16 :minor_version, label: 'Minor Version'
25
+ uint64 :capabilities, label: 'Capabilities'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -28,11 +28,15 @@ module RubySMB
28
28
  # [NT LANMAN] Query file system attributes.
29
29
  SMB_QUERY_FS_ATTRIBUTE_INFO = 0x0105 # 261
30
30
 
31
+ # [CIFS UNIX Extensions] Query server UNIX extension capabilities.
32
+ SMB_QUERY_CIFS_UNIX_INFO = 0x0200 # 512
33
+
31
34
  def self.name(value)
32
35
  constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
33
36
  end
34
37
 
35
38
  require 'ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info'
39
+ require 'ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_cifs_unix_info'
36
40
  end
37
41
  end
38
42
  end
@@ -2,7 +2,9 @@ module RubySMB
2
2
  module SMB1
3
3
  module Packet
4
4
  module Trans2
5
- # The Trans2 Parameter Block for this particular Subcommand
5
+ # The Trans2 Parameter Block for a QUERY_FS_INFORMATION request as
6
+ # defined in
7
+ # [MS-CIFS 2.2.6.4.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cfa23a11-0e80-43bd-bbd4-e9cfb99b5dce).
6
8
  class QueryFsInformationRequestTrans2Parameters < BinData::Record
7
9
  endian :little
8
10
 
@@ -15,16 +17,22 @@ module RubySMB
15
17
  end
16
18
  end
17
19
 
18
- # The {RubySMB::SMB1::DataBlock} specific to this packet type.
20
+ # The {RubySMB::SMB1::DataBlock} specific to this packet type. See
21
+ # [MS-CIFS 2.2.6.4.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cfa23a11-0e80-43bd-bbd4-e9cfb99b5dce).
22
+ # The request carries no Trans2 data payload, but the generic
23
+ # DataBlock padding helpers require a :trans2_data accessor, so
24
+ # we expose a zero-length string.
19
25
  class QueryFsInformationRequestDataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
20
26
  uint8 :name, label: 'Name', initial_value: 0x00
21
27
  string :pad1, length: -> { pad1_length }
22
28
  query_fs_information_request_trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
23
- # trans2_data: No data is sent by this message.
29
+ string :trans2_data, length: 0, label: 'Trans2 Data'
24
30
  end
25
31
 
26
32
  # A Trans2 QUERY_FS_INFORMATION Request Packet as defined in
27
- # [2.2.6.4.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cfa23a11-0e80-43bd-bbd4-e9cfb99b5dce)
33
+ # [MS-CIFS 2.2.6.4.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cfa23a11-0e80-43bd-bbd4-e9cfb99b5dce).
34
+ # See also the subcommand overview at
35
+ # [MS-CIFS 2.2.6.4 TRANS2_QUERY_FS_INFORMATION (0x0003)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/a96c1c03-cade-4a4a-81a9-b00674d23d93).
28
36
  class QueryFsInformationRequest < RubySMB::GenericPacket
29
37
  COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
30
38
 
@@ -0,0 +1,28 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # SET_FS Information Levels used in TRANS2_SET_FS_INFORMATION.
6
+ #
7
+ # MS-CIFS marks the parent subcommand
8
+ # [TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
9
+ # as "reserved but not implemented" — the info level codes below are
10
+ # defined by the CIFS UNIX Extensions draft maintained by the Samba
11
+ # team and implemented in
12
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
13
+ # They sit in the 0x0200–0x02FF range reserved by
14
+ # [MS-CIFS 2.2.2.3 Information Level Codes](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/03c10ab9-d723-4368-b9a6-c72de3244c77)
15
+ # for third-party extensions.
16
+ module SetFsInformationLevel
17
+ # Client advertises / negotiates CIFS UNIX Extensions support.
18
+ # Data block: major:u16, minor:u16, capabilities:u64.
19
+ SMB_SET_CIFS_UNIX_INFO = 0x0200
20
+
21
+ def self.name(value)
22
+ constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,90 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # The Trans2 Parameter Block for TRANS2_SET_FS_INFORMATION.
6
+ # Observed on the wire (and required by Samba) as a 4-byte block
7
+ # containing a placeholder file handle plus the information level.
8
+ #
9
+ # The parent subcommand
10
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
11
+ # is marked "reserved but not implemented"; the on-the-wire format
12
+ # used here is defined by the CIFS UNIX Extensions draft and
13
+ # implemented in
14
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
15
+ class SetFsInformationRequestTrans2Parameters < BinData::Record
16
+ endian :little
17
+
18
+ uint16 :fid, label: 'File ID'
19
+ uint16 :information_level, label: 'Information Level'
20
+
21
+ # Returns the length of the Trans2Parameters struct
22
+ # in number of bytes
23
+ def length
24
+ do_num_bytes
25
+ end
26
+ end
27
+
28
+ # The Trans2 Data Block for TRANS2_SET_FS_INFORMATION.
29
+ #
30
+ # The data layout depends on the Information Level being set, so the
31
+ # block carries an opaque byte buffer that the caller fills in for
32
+ # the target info level. SMB_SET_CIFS_UNIX_INFO (0x0200) for example
33
+ # carries a QueryFsCifsUnixInfo-shaped record (major/minor/caps).
34
+ #
35
+ # Not documented in
36
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3);
37
+ # per-level layouts are defined by the CIFS UNIX Extensions draft
38
+ # and implemented in
39
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
40
+ class SetFsInformationRequestTrans2Data < BinData::Record
41
+ string :buffer, read_length: -> { parent.buffer_read_length }
42
+
43
+ # Returns the length of the Trans2Data struct
44
+ # in number of bytes
45
+ def length
46
+ do_num_bytes
47
+ end
48
+ end
49
+
50
+ # The {RubySMB::SMB1::DataBlock} specific to this packet type. The
51
+ # parent subcommand
52
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
53
+ # is documented as "reserved but not implemented"; this data block
54
+ # is structured for the CIFS UNIX Extensions use in
55
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
56
+ class SetFsInformationRequestDataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
57
+ uint8 :name, label: 'Name', initial_value: 0x00
58
+ string :pad1, length: -> { pad1_length }
59
+ set_fs_information_request_trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
60
+ string :pad2, length: -> { pad2_length }
61
+ set_fs_information_request_trans2_data :trans2_data, label: 'Trans2 Data'
62
+ end
63
+
64
+ # A Trans2 SET_FS_INFORMATION Request Packet. The on-disk layout
65
+ # described by
66
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
67
+ # is marked "reserved but not implemented" and does not document the
68
+ # CIFS UNIX Extensions info levels; their wire format is defined by
69
+ # the CIFS UNIX Extensions draft and matched by Samba's
70
+ # `call_trans2setfsinfo` in
71
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
72
+ class SetFsInformationRequest < RubySMB::GenericPacket
73
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
74
+
75
+ class ParameterBlock < RubySMB::SMB1::Packet::Trans2::Request::ParameterBlock
76
+ end
77
+
78
+ smb_header :smb_header
79
+ parameter_block :parameter_block
80
+ set_fs_information_request_data_block :data_block
81
+
82
+ def initialize_instance
83
+ super
84
+ parameter_block.setup << RubySMB::SMB1::Packet::Trans2::Subcommands::SET_FS_INFORMATION
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,56 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # The Trans2 Parameter Block for a TRANS2_SET_FS_INFORMATION response.
6
+ # The field is intentionally empty — servers return only an NT status
7
+ # in the SMB header to acknowledge the SET.
8
+ #
9
+ # Parent subcommand:
10
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
11
+ # ("reserved but not implemented"). Response shape observed in the
12
+ # CIFS UNIX Extensions implementation in
13
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
14
+ class SetFsInformationResponseTrans2Parameters < BinData::Record
15
+ def length
16
+ do_num_bytes
17
+ end
18
+ end
19
+
20
+ # The {RubySMB::SMB1::DataBlock} specific to this packet type.
21
+ # Parent subcommand:
22
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
23
+ # ("reserved but not implemented"); actual shape used by the CIFS
24
+ # UNIX Extensions implementation in
25
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
26
+ class SetFsInformationResponseDataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
27
+ uint8 :name, label: 'Name', initial_value: 0x00
28
+ string :pad1, length: -> { pad1_length }
29
+ set_fs_information_response_trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
30
+ end
31
+
32
+ # A Trans2 SET_FS_INFORMATION Response Packet. Parent subcommand:
33
+ # [MS-CIFS 2.2.6.5 TRANS2_SET_FS_INFORMATION (0x0004)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/ac4b00db-6015-416a-89a1-bf5da2503bc3)
34
+ # ("reserved but not implemented"). Response shape is defined by the
35
+ # CIFS UNIX Extensions implementation in
36
+ # [source3/smbd/smb1_trans2.c:1706-1915 (`call_trans2setfsinfo`)](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L1706-L1915).
37
+ class SetFsInformationResponse < RubySMB::GenericPacket
38
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
39
+
40
+ class ParameterBlock < RubySMB::SMB1::Packet::Trans2::Response::ParameterBlock
41
+ end
42
+
43
+ smb_header :smb_header
44
+ parameter_block :parameter_block
45
+ set_fs_information_response_data_block :data_block
46
+
47
+ def initialize_instance
48
+ super
49
+ parameter_block.setup << RubySMB::SMB1::Packet::Trans2::Subcommands::SET_FS_INFORMATION
50
+ smb_header.flags.reply = 1
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # Information Level codes valid for Trans2 SET_PATH_INFORMATION and
6
+ # SET_FILE_INFORMATION requests. See
7
+ # [MS-CIFS 2.2.2.3.4 SET Information Level Codes](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/0321265e-312a-4721-90fa-cd40a443ed86).
8
+ #
9
+ # FSCC pass-through levels are defined in
10
+ # {RubySMB::Fscc::FileInformation} and require
11
+ # `SMB_INFO_PASSTHROUGH` to be added. The constants defined here are
12
+ # the CIFS UNIX Extensions info levels used by Samba servers that
13
+ # advertise UNIX extensions support in their negotiate response. These
14
+ # levels fall outside MS-CIFS; their wire format is defined by the
15
+ # CIFS UNIX Extensions draft and implemented by Samba's
16
+ # `smb_set_file_unix_link` handler at
17
+ # [source3/smbd/smb1_trans2.c:3643-3712](https://github.com/samba-team/samba/blob/33f516c06756e12a9d11f50e2bf309171cdf5c88/source3/smbd/smb1_trans2.c#L3643-L3712).
18
+ module SetInformationLevel
19
+ # Set the symbolic link target for a file. The Trans2 parameters
20
+ # block carries the path being created (the symlink itself); the
21
+ # Trans2 data block carries the target path as a null-terminated
22
+ # string.
23
+ SMB_SET_FILE_UNIX_LINK = 0x0201
24
+
25
+ # Create a hard link. Data block carries the existing file path.
26
+ SMB_SET_FILE_UNIX_HLINK = 0x0203
27
+
28
+ def self.name(value)
29
+ constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,75 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # The Trans2 Parameter Block for a SET_PATH_INFORMATION request as
6
+ # defined in
7
+ # [MS-CIFS 2.2.6.7.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/2ca0642a-1cd4-4e72-a16f-9e804a218262).
8
+ class SetPathInformationRequestTrans2Parameters < BinData::Record
9
+ endian :little
10
+
11
+ uint16 :information_level, label: 'Information Level'
12
+ uint32 :reserved, label: 'Reserved'
13
+ choice :filename, copy_on_change: true, selection: -> { parent.parent.smb_header.flags2.unicode } do
14
+ stringz16 1, label: 'FileName'
15
+ stringz 0, label: 'FileName'
16
+ end
17
+
18
+ # Returns the length of the Trans2Parameters struct
19
+ # in number of bytes
20
+ def length
21
+ do_num_bytes
22
+ end
23
+ end
24
+
25
+ # The Trans2 Data Block for a SET_PATH_INFORMATION request as defined
26
+ # in
27
+ # [MS-CIFS 2.2.6.7.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/2ca0642a-1cd4-4e72-a16f-9e804a218262).
28
+ #
29
+ # The data layout depends on the Information Level being set, so the
30
+ # block carries an opaque byte buffer that the caller fills in for the
31
+ # target info level. SMB_SET_FILE_UNIX_LINK (0x0201) for example
32
+ # carries the symlink target as a null-terminated string.
33
+ class SetPathInformationRequestTrans2Data < BinData::Record
34
+ string :buffer, read_length: -> { parent.buffer_read_length }
35
+
36
+ # Returns the length of the Trans2Data struct
37
+ # in number of bytes
38
+ def length
39
+ do_num_bytes
40
+ end
41
+ end
42
+
43
+ # The {RubySMB::SMB1::DataBlock} specific to this packet type. See
44
+ # [MS-CIFS 2.2.6.7.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/2ca0642a-1cd4-4e72-a16f-9e804a218262).
45
+ class SetPathInformationRequestDataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
46
+ uint8 :name, label: 'Name', initial_value: 0x00
47
+ string :pad1, length: -> { pad1_length }
48
+ set_path_information_request_trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
49
+ string :pad2, length: -> { pad2_length }
50
+ set_path_information_request_trans2_data :trans2_data, label: 'Trans2 Data'
51
+ end
52
+
53
+ # A Trans2 SET_PATH_INFORMATION Request Packet as defined in
54
+ # [MS-CIFS 2.2.6.7.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/2ca0642a-1cd4-4e72-a16f-9e804a218262).
55
+ # See also the subcommand overview at
56
+ # [MS-CIFS 2.2.6.7 TRANS2_SET_PATH_INFORMATION (0x0006)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/a23483d9-6543-4aaa-a996-e7c9506f8b94).
57
+ class SetPathInformationRequest < RubySMB::GenericPacket
58
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
59
+
60
+ class ParameterBlock < RubySMB::SMB1::Packet::Trans2::Request::ParameterBlock
61
+ end
62
+
63
+ smb_header :smb_header
64
+ parameter_block :parameter_block
65
+ set_path_information_request_data_block :data_block
66
+
67
+ def initialize_instance
68
+ super
69
+ parameter_block.setup << RubySMB::SMB1::Packet::Trans2::Subcommands::SET_PATH_INFORMATION
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # The Trans2 Parameter Block for a SET_PATH_INFORMATION response as
6
+ # defined in
7
+ # [MS-CIFS 2.2.6.7.2 Response](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cf1cd579-9687-465d-9274-08ebb5944cd3).
8
+ class SetPathInformationResponseTrans2Parameters < BinData::Record
9
+ endian :little
10
+
11
+ uint16 :ea_error_offset, label: 'Extended Attribute Error Offset'
12
+
13
+ # Returns the length of the Trans2Parameters struct
14
+ # in number of bytes
15
+ def length
16
+ do_num_bytes
17
+ end
18
+ end
19
+
20
+ # The {RubySMB::SMB1::DataBlock} specific to this packet type. See
21
+ # [MS-CIFS 2.2.6.7.2 Response](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cf1cd579-9687-465d-9274-08ebb5944cd3).
22
+ class SetPathInformationResponseDataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
23
+ string :pad1, length: -> { pad1_length }
24
+ set_path_information_response_trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
25
+ # trans2_data: No data is sent by this message.
26
+ end
27
+
28
+ # A Trans2 SET_PATH_INFORMATION Response Packet as defined in
29
+ # [MS-CIFS 2.2.6.7.2 Response](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/cf1cd579-9687-465d-9274-08ebb5944cd3).
30
+ # See also the subcommand overview at
31
+ # [MS-CIFS 2.2.6.7 TRANS2_SET_PATH_INFORMATION (0x0006)](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/a23483d9-6543-4aaa-a996-e7c9506f8b94).
32
+ class SetPathInformationResponse < RubySMB::GenericPacket
33
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
34
+
35
+ class ParameterBlock < RubySMB::SMB1::Packet::Trans2::Response::ParameterBlock
36
+ end
37
+
38
+ smb_header :smb_header
39
+ parameter_block :parameter_block
40
+ set_path_information_response_data_block :data_block
41
+
42
+ def initialize_instance
43
+ super
44
+ parameter_block.setup << RubySMB::SMB1::Packet::Trans2::Subcommands::SET_PATH_INFORMATION
45
+ smb_header.flags.reply = 1
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -7,6 +7,8 @@ module RubySMB
7
7
  require 'ruby_smb/smb1/packet/trans2/find_information_level'
8
8
  require 'ruby_smb/smb1/packet/trans2/query_information_level'
9
9
  require 'ruby_smb/smb1/packet/trans2/query_fs_information_level'
10
+ require 'ruby_smb/smb1/packet/trans2/set_information_level'
11
+ require 'ruby_smb/smb1/packet/trans2/set_fs_information_level'
10
12
  require 'ruby_smb/smb1/packet/trans2/data_block'
11
13
  require 'ruby_smb/smb1/packet/trans2/win9x_framing'
12
14
  require 'ruby_smb/smb1/packet/trans2/subcommands'
@@ -23,6 +25,12 @@ module RubySMB
23
25
  require 'ruby_smb/smb1/packet/trans2/set_file_information_response'
24
26
  require 'ruby_smb/smb1/packet/trans2/query_path_information_request'
25
27
  require 'ruby_smb/smb1/packet/trans2/query_path_information_response'
28
+ require 'ruby_smb/smb1/packet/trans2/set_path_information_request'
29
+ require 'ruby_smb/smb1/packet/trans2/set_path_information_response'
30
+ require 'ruby_smb/smb1/packet/trans2/query_fs_information_request'
31
+ require 'ruby_smb/smb1/packet/trans2/query_fs_information_response'
32
+ require 'ruby_smb/smb1/packet/trans2/set_fs_information_request'
33
+ require 'ruby_smb/smb1/packet/trans2/set_fs_information_response'
26
34
  end
27
35
  end
28
36
  end
@@ -195,6 +195,66 @@ module RubySMB
195
195
  results
196
196
  end
197
197
 
198
+ # Create a UNIX symbolic link on the remote share using the CIFS UNIX
199
+ # Extensions SMB_SET_FILE_UNIX_LINK information level (0x0201) via a
200
+ # Trans2 SET_PATH_INFORMATION request.
201
+ #
202
+ # @example
203
+ # tree = client.tree_connect("\\\\samba\\writable")
204
+ # tree.set_unix_link(symlink: 'escape', target: '../../../../etc')
205
+ #
206
+ # @param symlink [String] the path, relative to the share, where the
207
+ # symbolic link file will be created
208
+ # @param target [String] the destination the symbolic link points to
209
+ # @return [WindowsError::ErrorCode] the NTStatus returned by the server
210
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a Trans2
211
+ # SET_PATH_INFORMATION response
212
+ # @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus
213
+ # is not STATUS_SUCCESS
214
+ def set_unix_link(symlink:, target:)
215
+ # Samba gates SMB_SET_FILE_UNIX_LINK behind a per-session CIFS UNIX
216
+ # Extensions handshake: query server capabilities, then echo them
217
+ # back via SMB_SET_CIFS_UNIX_INFO. Without this, Samba responds to
218
+ # the symlink request with STATUS_INVALID_LEVEL even when
219
+ # `unix extensions = yes` is set server-side.
220
+ enable_cifs_unix_extensions
221
+
222
+ request = RubySMB::SMB1::Packet::Trans2::SetPathInformationRequest.new
223
+ request = set_header_fields(request)
224
+ # CIFS UNIX Extensions paths are raw byte strings, not UTF-16. Clear
225
+ # flags2.unicode so the filename parameter and target data block are
226
+ # emitted as bytes — Samba rejects the UTF-16 variant with
227
+ # STATUS_INVALID_LEVEL.
228
+ request.smb_header.flags2.unicode = 0
229
+
230
+ t2_params = request.data_block.trans2_parameters
231
+ t2_params.information_level = RubySMB::SMB1::Packet::Trans2::SetInformationLevel::SMB_SET_FILE_UNIX_LINK
232
+ t2_params.filename = symlink
233
+
234
+ encoded_target = target.dup.force_encoding(Encoding::ASCII_8BIT)
235
+ encoded_target << "\x00".b unless encoded_target.end_with?("\x00".b)
236
+ request.data_block.trans2_data.buffer = encoded_target
237
+
238
+ request.parameter_block.total_parameter_count = request.parameter_block.parameter_count
239
+ request.parameter_block.total_data_count = request.parameter_block.data_count
240
+ request.parameter_block.max_parameter_count = request.parameter_block.parameter_count
241
+ request.parameter_block.max_data_count = 0
242
+
243
+ raw_response = client.send_recv(request)
244
+ response = RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse.read(raw_response)
245
+ unless response.valid?
246
+ raise RubySMB::Error::InvalidPacket.new(
247
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
248
+ expected_cmd: RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse::COMMAND,
249
+ packet: response
250
+ )
251
+ end
252
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
253
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
254
+ end
255
+ response.status_code
256
+ end
257
+
198
258
  # Sets a few preset header fields that will always be set the same
199
259
  # way for Tree operations. This is, the TreeID and Extended Attributes.
200
260
  #
@@ -206,8 +266,72 @@ module RubySMB
206
266
  request
207
267
  end
208
268
 
269
+ # Perform the CIFS UNIX Extensions handshake on this tree: query the
270
+ # server's supported extensions (SMB_QUERY_CIFS_UNIX_INFO) and then
271
+ # echo the version and capability bits back via SMB_SET_CIFS_UNIX_INFO.
272
+ # Samba requires this round-trip to have completed on the current
273
+ # session before it will honor UNIX-extension info levels such as
274
+ # SMB_SET_FILE_UNIX_LINK.
275
+ #
276
+ # @return [WindowsError::ErrorCode] NTStatus of the SET reply
277
+ # @raise [RubySMB::Error::UnexpectedStatusCode] if either leg fails
278
+ def enable_cifs_unix_extensions
279
+ major, minor, capabilities = query_cifs_unix_info
280
+ set_cifs_unix_info(major: major, minor: minor, capabilities: capabilities)
281
+ end
282
+
209
283
  private
210
284
 
285
+ def query_cifs_unix_info
286
+ request = RubySMB::SMB1::Packet::Trans2::QueryFsInformationRequest.new
287
+ request = set_header_fields(request)
288
+ request.smb_header.flags2.unicode = 0
289
+ request.data_block.trans2_parameters.information_level =
290
+ RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::SMB_QUERY_CIFS_UNIX_INFO
291
+ request.parameter_block.total_parameter_count = request.parameter_block.parameter_count
292
+ request.parameter_block.total_data_count = 0
293
+ request.parameter_block.max_parameter_count = 0
294
+ request.parameter_block.max_data_count = 560
295
+
296
+ raw_response = client.send_recv(request)
297
+ response = RubySMB::SMB1::Packet::Trans2::QueryFsInformationResponse.read(raw_response)
298
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
299
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
300
+ end
301
+ info = RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::QueryFsCifsUnixInfo.read(
302
+ response.data_block.trans2_data.buffer
303
+ )
304
+ [info.major_version.to_i, info.minor_version.to_i, info.capabilities.to_i]
305
+ end
306
+
307
+ def set_cifs_unix_info(major:, minor:, capabilities:)
308
+ request = RubySMB::SMB1::Packet::Trans2::SetFsInformationRequest.new
309
+ request = set_header_fields(request)
310
+ request.smb_header.flags2.unicode = 0
311
+ request.data_block.trans2_parameters.fid = 0
312
+ request.data_block.trans2_parameters.information_level =
313
+ RubySMB::SMB1::Packet::Trans2::SetFsInformationLevel::SMB_SET_CIFS_UNIX_INFO
314
+
315
+ info = RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::QueryFsCifsUnixInfo.new
316
+ info.major_version = major
317
+ info.minor_version = minor
318
+ info.capabilities = capabilities
319
+ request.data_block.trans2_data.buffer = info.to_binary_s
320
+
321
+ request.parameter_block.total_parameter_count = request.parameter_block.parameter_count
322
+ request.parameter_block.total_data_count = request.parameter_block.data_count
323
+ request.parameter_block.max_parameter_count = request.parameter_block.parameter_count
324
+ request.parameter_block.max_data_count = 0
325
+
326
+ raw_response = client.send_recv(request)
327
+ response = RubySMB::SMB1::Packet::Trans2::SetFsInformationResponse.read(raw_response)
328
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
329
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
330
+ end
331
+ response.status_code
332
+ end
333
+
334
+
211
335
  def _open(filename:, flags: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
212
336
  impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
213
337
  unless client.server_supports_nt_smbs
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '3.3.20'.freeze
2
+ VERSION = '3.3.21'.freeze
3
3
  end
@@ -40,7 +40,12 @@ RSpec.describe RubySMB::SMB1::Packet::Trans2::QueryFsInformationRequest do
40
40
 
41
41
  it { is_expected.to respond_to :name }
42
42
  it { is_expected.to respond_to :trans2_parameters }
43
- it { is_expected.to_not respond_to :trans2_data }
43
+ it { is_expected.to respond_to :trans2_data }
44
+
45
+ it '#trans2_data is a zero-length placeholder (this request carries no data payload)' do
46
+ expect(data_block.trans2_data).to be_a BinData::String
47
+ expect(data_block.trans2_data.num_bytes).to eq 0
48
+ end
44
49
 
45
50
  it 'should keep #trans2_parameters 4-byte aligned' do
46
51
  expect(data_block.trans2_parameters.abs_offset % 4).to eq 0
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB1::Packet::Trans2::SetFsInformationRequest do
4
+ subject(:packet) { described_class.new }
5
+
6
+ describe '#smb_header' do
7
+ subject(:header) { packet.smb_header }
8
+
9
+ it { is_expected.to be_a RubySMB::SMB1::SMBHeader }
10
+
11
+ it 'has the command set to SMB_COM_TRANSACTION2' do
12
+ expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
13
+ end
14
+ end
15
+
16
+ describe '#parameter_block' do
17
+ subject(:parameter_block) { packet.parameter_block }
18
+
19
+ it { is_expected.to be_a RubySMB::SMB1::Packet::Trans2::Request::ParameterBlock }
20
+
21
+ it 'uses the SET_FS_INFORMATION subcommand' do
22
+ expect(parameter_block.setup[0]).to eq RubySMB::SMB1::Packet::Trans2::Subcommands::SET_FS_INFORMATION
23
+ end
24
+ end
25
+
26
+ describe '#data_block' do
27
+ subject(:data_block) { packet.data_block }
28
+
29
+ it { is_expected.to respond_to :trans2_parameters }
30
+ it { is_expected.to respond_to :trans2_data }
31
+
32
+ describe '#trans2_parameters' do
33
+ subject(:parameters) { data_block.trans2_parameters }
34
+
35
+ it { is_expected.to respond_to :fid }
36
+ it { is_expected.to respond_to :information_level }
37
+
38
+ it 'is 4 bytes on the wire (fid + information_level)' do
39
+ parameters.fid = 0
40
+ parameters.information_level = RubySMB::SMB1::Packet::Trans2::SetFsInformationLevel::SMB_SET_CIFS_UNIX_INFO
41
+ expect(parameters.to_binary_s.bytesize).to eq 4
42
+ end
43
+ end
44
+
45
+ describe '#trans2_data' do
46
+ it 'carries an opaque byte buffer whose contents depend on the information level' do
47
+ data_block.trans2_data.buffer = "\x01\x00\x00\x00".b + "\x00".b * 8
48
+ expect(data_block.trans2_data.buffer.bytesize).to eq 12
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB1::Packet::Trans2::SetFsInformationResponse do
4
+ subject(:packet) { described_class.new }
5
+
6
+ describe '#smb_header' do
7
+ subject(:header) { packet.smb_header }
8
+
9
+ it { is_expected.to be_a RubySMB::SMB1::SMBHeader }
10
+
11
+ it 'has the command set to SMB_COM_TRANSACTION2' do
12
+ expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
13
+ end
14
+
15
+ it 'sets the reply flag' do
16
+ expect(header.flags.reply).to eq 1
17
+ end
18
+ end
19
+
20
+ describe '#parameter_block' do
21
+ subject(:parameter_block) { packet.parameter_block }
22
+
23
+ it { is_expected.to be_a RubySMB::SMB1::Packet::Trans2::Response::ParameterBlock }
24
+
25
+ it 'uses the SET_FS_INFORMATION subcommand' do
26
+ expect(parameter_block.setup[0]).to eq RubySMB::SMB1::Packet::Trans2::Subcommands::SET_FS_INFORMATION
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,114 @@
1
+ RSpec.describe RubySMB::SMB1::Packet::Trans2::SetPathInformationRequest do
2
+ subject(:packet) { described_class.new }
3
+
4
+ describe '#smb_header' do
5
+ subject(:header) { packet.smb_header }
6
+
7
+ it 'is a standard SMB Header' do
8
+ expect(header).to be_a RubySMB::SMB1::SMBHeader
9
+ end
10
+
11
+ it 'should have the command set to SMB_COM_TRANSACTION2' do
12
+ expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
13
+ end
14
+
15
+ it 'should not have the response flag set' do
16
+ expect(header.flags.reply).to eq 0
17
+ end
18
+ end
19
+
20
+ describe '#parameter_block' do
21
+ subject(:parameter_block) { packet.parameter_block }
22
+
23
+ it 'is a standard ParameterBlock' do
24
+ expect(parameter_block).to be_a RubySMB::SMB1::Packet::Trans2::Request::ParameterBlock
25
+ end
26
+
27
+ it 'should have the setup set to the SET_PATH_INFORMATION subcommand' do
28
+ expect(parameter_block.setup).to include RubySMB::SMB1::Packet::Trans2::Subcommands::SET_PATH_INFORMATION
29
+ end
30
+ end
31
+
32
+ describe '#data_block' do
33
+ subject(:data_block) { packet.data_block }
34
+
35
+ it 'is a standard DataBlock' do
36
+ expect(data_block).to be_a RubySMB::SMB1::DataBlock
37
+ end
38
+
39
+ it { is_expected.to respond_to :name }
40
+ it { is_expected.to respond_to :trans2_parameters }
41
+ it { is_expected.to respond_to :trans2_data }
42
+
43
+ it 'should keep #trans2_parameters 4-byte aligned' do
44
+ expect(data_block.trans2_parameters.abs_offset % 4).to eq 0
45
+ end
46
+
47
+ describe '#trans2_parameters' do
48
+ subject(:parameters) { data_block.trans2_parameters }
49
+
50
+ it { is_expected.to respond_to :information_level }
51
+ it { is_expected.to respond_to :reserved }
52
+ it { is_expected.to respond_to :filename }
53
+
54
+ describe '#information_level' do
55
+ it 'is a 16-bit field' do
56
+ expect(parameters.information_level).to be_a BinData::Uint16le
57
+ end
58
+ end
59
+
60
+ describe '#reserved' do
61
+ it 'is a 32-bit field' do
62
+ expect(parameters.reserved).to be_a BinData::Uint32le
63
+ end
64
+ end
65
+
66
+ describe '#filename' do
67
+ it 'is a BinData::Choice' do
68
+ expect(parameters.filename).to be_a BinData::Choice
69
+ end
70
+
71
+ it 'encodes the filename as OEM when the unicode flag is cleared' do
72
+ packet.smb_header.flags2.unicode = 0
73
+ parameters.filename = 'link'
74
+ expect(parameters.filename.to_binary_s).to eq "link\x00".b
75
+ end
76
+
77
+ it 'encodes the filename as UTF-16LE when the unicode flag is set' do
78
+ packet.smb_header.flags2.unicode = 1
79
+ parameters.filename = 'link'
80
+ expect(parameters.filename.to_binary_s).to eq "link\x00".encode('UTF-16LE').b
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#trans2_data' do
86
+ subject(:data) { data_block.trans2_data }
87
+
88
+ it { is_expected.to respond_to :buffer }
89
+
90
+ it 'carries an opaque byte buffer for the info-level-specific payload' do
91
+ data.buffer = "target\x00"
92
+ expect(data.buffer.to_binary_s).to eq "target\x00".b
93
+ end
94
+ end
95
+ end
96
+
97
+ describe 'encoded bytes for an SMB_SET_FILE_UNIX_LINK request' do
98
+ it 'encodes the information level, filename and target in the expected positions' do
99
+ packet.smb_header.flags2.unicode = 1
100
+ packet.data_block.trans2_parameters.information_level =
101
+ RubySMB::SMB1::Packet::Trans2::SetInformationLevel::SMB_SET_FILE_UNIX_LINK
102
+ packet.data_block.trans2_parameters.filename = 'foo'
103
+ packet.data_block.trans2_data.buffer = "bar\x00".encode('UTF-16LE')
104
+
105
+ raw = packet.to_binary_s
106
+ # Information level (little-endian 0x0201)
107
+ expect(raw).to include("\x01\x02".b)
108
+ # Filename in UTF-16LE with null terminator
109
+ expect(raw).to include("foo\x00".encode('UTF-16LE').b)
110
+ # Target in UTF-16LE with null terminator
111
+ expect(raw).to include("bar\x00".encode('UTF-16LE').b)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,54 @@
1
+ RSpec.describe RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse do
2
+ subject(:packet) { described_class.new }
3
+
4
+ describe '#smb_header' do
5
+ subject(:header) { packet.smb_header }
6
+
7
+ it 'is a standard SMB Header' do
8
+ expect(header).to be_a RubySMB::SMB1::SMBHeader
9
+ end
10
+
11
+ it 'should have the command set to SMB_COM_TRANSACTION2' do
12
+ expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
13
+ end
14
+
15
+ it 'should have the response flag set' do
16
+ expect(header.flags.reply).to eq 1
17
+ end
18
+ end
19
+
20
+ describe '#parameter_block' do
21
+ subject(:parameter_block) { packet.parameter_block }
22
+
23
+ it 'should have the setup set to the SET_PATH_INFORMATION subcommand' do
24
+ expect(parameter_block.setup).to include RubySMB::SMB1::Packet::Trans2::Subcommands::SET_PATH_INFORMATION
25
+ end
26
+ end
27
+
28
+ describe '#data_block' do
29
+ subject(:data_block) { packet.data_block }
30
+
31
+ it 'is a standard DataBlock' do
32
+ expect(data_block).to be_a RubySMB::SMB1::DataBlock
33
+ end
34
+
35
+ it { is_expected.to respond_to :trans2_parameters }
36
+ it { is_expected.to_not respond_to :trans2_data }
37
+
38
+ it 'should keep #trans2_parameters 4-byte aligned' do
39
+ expect(data_block.trans2_parameters.abs_offset % 4).to eq 0
40
+ end
41
+
42
+ describe '#trans2_parameters' do
43
+ subject(:parameters) { data_block.trans2_parameters }
44
+
45
+ it { is_expected.to respond_to :ea_error_offset }
46
+
47
+ describe '#ea_error_offset' do
48
+ it 'is a 16-bit field' do
49
+ expect(parameters.ea_error_offset).to be_a BinData::Uint16le
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -582,6 +582,130 @@ RSpec.describe RubySMB::SMB1::Tree do
582
582
  end
583
583
  end
584
584
 
585
+ describe '#set_unix_link' do
586
+ let(:set_path_response) { RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse.new }
587
+
588
+ before :each do
589
+ # Stub out the CIFS UNIX Extensions handshake — covered separately below.
590
+ allow(tree).to receive(:enable_cifs_unix_extensions)
591
+ allow(client).to receive(:send_recv)
592
+ allow(RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse).to receive(:read).and_return(set_path_response)
593
+ end
594
+
595
+ it 'performs the CIFS UNIX Extensions handshake before issuing the symlink request' do
596
+ call_order = []
597
+ allow(tree).to receive(:enable_cifs_unix_extensions) { call_order << :handshake }
598
+ allow(client).to receive(:send_recv) { call_order << :set_path; '' }
599
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
600
+ expect(call_order).to eq([:handshake, :set_path])
601
+ end
602
+
603
+ it 'sends a Trans2 SetPathInformationRequest with the UNIX_LINK info level' do
604
+ allow(client).to receive(:send_recv) do |request|
605
+ expect(request).to be_a(RubySMB::SMB1::Packet::Trans2::SetPathInformationRequest)
606
+ expect(request.data_block.trans2_parameters.information_level).to(
607
+ eq(RubySMB::SMB1::Packet::Trans2::SetInformationLevel::SMB_SET_FILE_UNIX_LINK)
608
+ )
609
+ end
610
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
611
+ end
612
+
613
+ it 'sets the Tree ID on the request' do
614
+ allow(client).to receive(:send_recv) do |request|
615
+ expect(request.smb_header.tid).to eq(tree.id)
616
+ end
617
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
618
+ end
619
+
620
+ it 'encodes the symlink path and target as raw byte strings (non-unicode)' do
621
+ allow(client).to receive(:send_recv) do |request|
622
+ raw = request.to_binary_s
623
+ expect(request.smb_header.flags2.unicode).to eq(0)
624
+ expect(raw).to include('escape'.b)
625
+ expect(raw).to include('../../etc'.b)
626
+ expect(raw).not_to include('escape'.encode('UTF-16LE').b)
627
+ end
628
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
629
+ end
630
+
631
+ it 'returns STATUS_SUCCESS on a successful response' do
632
+ expect(tree.set_unix_link(symlink: 'escape', target: '../../etc'))
633
+ .to eq(WindowsError::NTStatus::STATUS_SUCCESS)
634
+ end
635
+
636
+ context 'when the server returns a non-Trans2 response packet' do
637
+ it 'raises InvalidPacket' do
638
+ allow(set_path_response).to receive(:valid?).and_return(false)
639
+ expect {
640
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
641
+ }.to raise_error(RubySMB::Error::InvalidPacket)
642
+ end
643
+ end
644
+
645
+ context 'when the response has a non-success status code' do
646
+ it 'raises UnexpectedStatusCode' do
647
+ set_path_response.smb_header.nt_status =
648
+ WindowsError::NTStatus::STATUS_ACCESS_DENIED.value
649
+ expect {
650
+ tree.set_unix_link(symlink: 'escape', target: '../../etc')
651
+ }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
652
+ end
653
+ end
654
+ end
655
+
656
+ describe '#enable_cifs_unix_extensions' do
657
+ let(:query_response) { RubySMB::SMB1::Packet::Trans2::QueryFsInformationResponse.new }
658
+ let(:set_response) { RubySMB::SMB1::Packet::Trans2::SetFsInformationResponse.new }
659
+
660
+ before :each do
661
+ # Server advertises major=1, minor=0, caps=0x0000_0000_0000_017B
662
+ info = RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::QueryFsCifsUnixInfo.new
663
+ info.major_version = 1
664
+ info.minor_version = 0
665
+ info.capabilities = 0x17B
666
+ query_response.data_block.trans2_data.buffer = info.to_binary_s
667
+ allow(RubySMB::SMB1::Packet::Trans2::QueryFsInformationResponse).to receive(:read).and_return(query_response)
668
+ allow(RubySMB::SMB1::Packet::Trans2::SetFsInformationResponse).to receive(:read).and_return(set_response)
669
+ end
670
+
671
+ it 'queries the server for CIFS UNIX info and then echoes the capability bits back via SET_CIFS_UNIX_INFO' do
672
+ sent = []
673
+ allow(client).to receive(:send_recv) do |req|
674
+ sent << req
675
+ ''
676
+ end
677
+ tree.enable_cifs_unix_extensions
678
+
679
+ expect(sent[0]).to be_a(RubySMB::SMB1::Packet::Trans2::QueryFsInformationRequest)
680
+ expect(sent[0].data_block.trans2_parameters.information_level).to(
681
+ eq(RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::SMB_QUERY_CIFS_UNIX_INFO)
682
+ )
683
+
684
+ expect(sent[1]).to be_a(RubySMB::SMB1::Packet::Trans2::SetFsInformationRequest)
685
+ expect(sent[1].data_block.trans2_parameters.information_level).to(
686
+ eq(RubySMB::SMB1::Packet::Trans2::SetFsInformationLevel::SMB_SET_CIFS_UNIX_INFO)
687
+ )
688
+ echoed = RubySMB::SMB1::Packet::Trans2::QueryFsInformationLevel::QueryFsCifsUnixInfo.read(
689
+ sent[1].data_block.trans2_data.buffer
690
+ )
691
+ expect(echoed.major_version).to eq(1)
692
+ expect(echoed.minor_version).to eq(0)
693
+ expect(echoed.capabilities).to eq(0x17B)
694
+ end
695
+
696
+ it 'raises UnexpectedStatusCode when the QUERY leg fails' do
697
+ query_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_ACCESS_DENIED.value
698
+ allow(client).to receive(:send_recv).and_return('')
699
+ expect { tree.enable_cifs_unix_extensions }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
700
+ end
701
+
702
+ it 'raises UnexpectedStatusCode when the SET leg fails' do
703
+ set_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER.value
704
+ allow(client).to receive(:send_recv).and_return('')
705
+ expect { tree.enable_cifs_unix_extensions }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
706
+ end
707
+ end
708
+
585
709
  describe '#set_header_fields' do
586
710
  let(:modified_request) { tree.set_header_fields(disco_req) }
587
711
  it 'adds the TreeID to the header' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_smb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.20
4
+ version: 3.3.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Metasploit Hackers
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2026-05-21 00:00:00.000000000 Z
16
+ date: 2026-06-08 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: redcarpet
@@ -571,6 +571,7 @@ files:
571
571
  - lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb
572
572
  - lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb
573
573
  - lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb
574
+ - lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_cifs_unix_info.rb
574
575
  - lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb
575
576
  - lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb
576
577
  - lib/ruby_smb/smb1/packet/trans2/query_information_level.rb
@@ -583,6 +584,12 @@ files:
583
584
  - lib/ruby_smb/smb1/packet/trans2/response.rb
584
585
  - lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb
585
586
  - lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb
587
+ - lib/ruby_smb/smb1/packet/trans2/set_fs_information_level.rb
588
+ - lib/ruby_smb/smb1/packet/trans2/set_fs_information_request.rb
589
+ - lib/ruby_smb/smb1/packet/trans2/set_fs_information_response.rb
590
+ - lib/ruby_smb/smb1/packet/trans2/set_information_level.rb
591
+ - lib/ruby_smb/smb1/packet/trans2/set_path_information_request.rb
592
+ - lib/ruby_smb/smb1/packet/trans2/set_path_information_response.rb
586
593
  - lib/ruby_smb/smb1/packet/trans2/subcommands.rb
587
594
  - lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb
588
595
  - lib/ruby_smb/smb1/packet/tree_connect_request.rb
@@ -922,6 +929,10 @@ files:
922
929
  - spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb
923
930
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb
924
931
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb
932
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_fs_information_request_spec.rb
933
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_fs_information_response_spec.rb
934
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_path_information_request_spec.rb
935
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_path_information_response_spec.rb
925
936
  - spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb
926
937
  - spec/lib/ruby_smb/smb1/packet/tree_connect_request_spec.rb
927
938
  - spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb
@@ -1275,6 +1286,10 @@ test_files:
1275
1286
  - spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb
1276
1287
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb
1277
1288
  - spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb
1289
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_fs_information_request_spec.rb
1290
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_fs_information_response_spec.rb
1291
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_path_information_request_spec.rb
1292
+ - spec/lib/ruby_smb/smb1/packet/trans2/set_path_information_response_spec.rb
1278
1293
  - spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb
1279
1294
  - spec/lib/ruby_smb/smb1/packet/tree_connect_request_spec.rb
1280
1295
  - spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb