File Coverage

lib/SMB/v2/Commands.pm
Criterion Covered Total %
statement 12 120 10.0
branch 0 44 0.0
condition 0 43 0.0
subroutine 4 6 66.6
pod 0 2 0.0
total 16 215 7.4


line stmt bran cond sub pod time code
1             # SMB-Perl library, Copyright (C) 2014-2018 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16             package SMB::v2::Commands;
17              
18 1     1   8 use strict;
  1         3  
  1         34  
19 1     1   6 use warnings;
  1         3  
  1         31  
20              
21 1     1   496 use SMB::v2::Header;
  1         3  
  1         638  
22              
23             our $header_stamp = "\xfeSMB";
24              
25             our @command_names = (
26             'Negotiate', # 0x00
27             'SessionSetup', # 0x01
28             'SessionLogoff', # 0x02
29             'TreeConnect', # 0x03
30             'TreeDisconnect', # 0x04
31             'Create', # 0x05
32             'Close', # 0x06
33             'Flush', # 0x07
34             'Read', # 0x08
35             'Write', # 0x09
36             'Lock', # 0x0A
37             'Ioctl', # 0x0B
38             'Cancel', # 0x0C
39             'KeepAlive', # 0x0D
40             'QueryDirectory', # 0x0E
41             'ChangeNotify', # 0x0F
42             'QueryInfo', # 0x10
43             'SetInfo', # 0x11
44             'Break', # 0x12
45             );
46              
47             our %command_codes = map { $command_names[$_] => $_ } 0 .. $#command_names;
48              
49             our %command_aliases = (
50             'Echo' => 'KeepAlive',
51             'Find' => 'QueryDirectory',
52             'Notify' => 'ChangeNotify',
53             'GetInfo' => 'QueryInfo',
54             );
55              
56             our @command_struct_sizes = (
57             [ 36, 65 ], # 0x00
58             [ 25, 9 ], # 0x01
59             [ 4, 4 ], # 0x02
60             [ 9, 16 ], # 0x03
61             [ 4, 4 ], # 0x04
62             [ 57, 89 ], # 0x05
63             [ 24, 60 ], # 0x06
64             [ 24, 4 ], # 0x07
65             [ 49, 17 ], # 0x08
66             [ 49, 17 ], # 0x09
67             [ 48, 4 ], # 0x0A
68             [ 57, 49 ], # 0x0B
69             [ 4, 0 ], # 0x0C
70             [ 4, 4 ], # 0x0D
71             [ 33, 9 ], # 0x0E
72             [ 32, 9 ], # 0x0F
73             [ 41, 9 ], # 0x10
74             [ 33, 2 ], # 0x11
75             [ 24, 24 ], # 0x12 # or [ 36, 44 ]
76             );
77              
78             our $MIN_MESSAGE_SIZE = 64;
79              
80             sub parse ($$) {
81 0     0 0   my $class = shift;
82 0   0       my $parser = shift || die;
83              
84 0           $parser->cut; # skip any previous data
85              
86 0 0         if ($parser->size < $MIN_MESSAGE_SIZE) {
87 0           warn sprintf "Too short message to parse (%d, should be at least %d)\n", $parser->size, $MIN_MESSAGE_SIZE;
88 0           return;
89             }
90              
91 0 0         if ($parser->bytes(4) ne $header_stamp) {
92 0           warn "Expected SMB1 stamp not found, stopping\n";
93 0           return;
94             }
95              
96             # parse header following the SMB2 stamp "\xfeSMB"
97 0           $parser->uint16; # skip reserved
98 0           my $credit_charge = $parser->uint16;
99 0           my $status = $parser->uint32;
100 0           my $code = $parser->uint16;
101 0           my $credits = $parser->uint16;
102 0           my $flags = $parser->uint32;
103 0           my $offset = $parser->uint32; # offset of the next chain command or 0
104 0           my $mid = $parser->uint64;
105 0           my $aid = 0;
106 0           my $tid = 0;
107 0 0         if ($flags & SMB::v2::Header::FLAGS_ASYNC_COMMAND) {
108 0           $aid = $parser->uint64;
109             } else {
110 0           $parser->uint32; # reserved (according to spec), not pid
111 0           $tid = $parser->uint32;
112             }
113 0           my $uid = $parser->uint64;
114 0           my $sign = $parser->bytes(16);
115 0           my $struct_size = $parser->uint16;
116              
117 0           my $header = SMB::v2::Header->new(
118             code => $code,
119             status => $status,
120             uid => $uid,
121             tid => $tid,
122             mid => $mid,
123             signature => $sign,
124             flags => $flags,
125             aid => $aid,
126             credits => $credits,
127             credit_charge => $credit_charge,
128             chain_offset => $offset,
129             struct_size => $struct_size,
130             );
131              
132 0           my $command_name = $command_names[$code];
133 0           my $command;
134              
135 0 0         if ($command_name) {
136 0           my $command_class = "SMB::v2::Command::$command_name";
137 0           my $command_filename = "SMB/v2/Command/$command_name.pm";
138 0 0 0       unless ($INC{$command_filename} || $::_INC{$command_filename}) {
139             # auto-load or auto-create requested sub-class
140 0 0         if (!eval { require $command_filename; 1; }) {
  0            
  0            
141 1     1   9 no strict 'refs';
  1         3  
  1         966  
142 0           @{"${command_class}::ISA"} = qw(SMB::v2::Command);
  0            
143 0           $::_INC{$command_filename} = 1;
144             }
145             }
146              
147 0           $command = $command_class->new($header);
148 0 0 0       return $command unless $command->is_success || $command->is('SessionSetup');
149              
150 0 0         $command = $command->parse($parser)
151             or warn sprintf "Failed to parse SMB2 command 0x%x ($command_name)\n", $code;
152             } else {
153 0           warn sprintf "Got unexisting SMB2 command 0x%x\n", $code;
154             }
155              
156 0 0         $parser->reset($offset) if $offset; # jump to the next chain command if any
157              
158 0           return $command;
159             }
160              
161             sub pack ($$$%) {
162 0     0 0   my $class = shift;
163 0           my $packer = shift;
164 0           my $command = shift;
165 0           my %options = @_;
166              
167 0           my $header = $command->header;
168 0           my $status = $command->status;
169              
170 0           my $is_response = $command->is_response;
171 0   0       my $struct_size = $options{struct_size} // $command_struct_sizes[$header->code][$is_response] // $header->struct_size;
      0        
172 0           my $is_chained = $options{is_chained};
173 0           my $is_first = $options{is_first};
174 0           my $is_last = $options{is_last};
175              
176 0           my $flags = $header->flags;
177 0 0         if ($is_response) {
178 0           $flags |= SMB::v2::Header::FLAGS_RESPONSE;
179             } else {
180 0           $flags &= ~SMB::v2::Header::FLAGS_RESPONSE;
181             }
182 0 0 0       if ($is_chained && !$is_first) {
183 0           $flags |= SMB::v2::Header::FLAGS_CHAINED;
184             } else {
185 0           $flags &= ~SMB::v2::Header::FLAGS_CHAINED;
186             }
187              
188             # skip NetBIOS header (length will be filled later)
189 0 0 0       if (!$is_chained || $is_first) {
190 0           $packer->mark('netbios-header');
191 0           $packer->skip(4);
192             }
193              
194             # pack SMB2 header
195 0           $packer->mark('smb-header');
196 0           $packer->bytes($header_stamp); # SMB2 magic signature
197 0           $packer->uint16(64); # header size
198 0           $packer->uint16($header->credit_charge);
199 0           $packer->mark('status');
200 0 0         $packer->uint32($is_response ? $status : 0);
201 0           $packer->uint16($header->code);
202 0   0       $packer->uint16($header->credits || 1);
203 0           $packer->uint32($flags);
204 0           $packer->mark('next-command');
205 0           $packer->uint32(0);
206 0           $packer->uint64($header->mid);
207             # aid or pid + tid
208 0 0         if ($flags & SMB::v2::Header::FLAGS_ASYNC_COMMAND) {
209 0           $packer->uint64($header->aid);
210             } else {
211 0           $packer->uint32(0); # no pid in SMB2 spec
212 0           $packer->uint32($header->tid);
213             }
214 0           $packer->uint64($header->uid);
215 0           $packer->bytes("\0" x 16); # no message signing for now
216              
217 0           $packer->mark('command-start');
218 0 0         $packer->uint16($command->is_success ? $struct_size : 9);
219              
220 0   0       my $is_error_packet = $command->is_error && !$command->is('SessionSetup');
221              
222 0 0         $command->pack($packer) if !$is_error_packet;
223 0   0       $is_error_packet ||= $command->is_error; # support abort_pack inside pack
224 0 0         $packer->skip(6 + 1) if $is_error_packet;
225              
226 0           my $payload_allowed = $struct_size % 2;
227 0 0 0       $payload_allowed = 1 if $command->is('Negotiate') && !$is_response;
228 0           my $size = $packer->diff('command-start');
229 0           my $size0 = $struct_size & ~1;
230 0 0 0       die "SMB2 command $command->{name} pack produced size $size, expected $size0\n"
      0        
231             if $size > $size0 && !$payload_allowed && !$is_error_packet;
232 0 0         $packer->zero($size0 - $size) if $size0 > $size;
233              
234 0           $packer->mark('end');
235 0 0 0       if ($is_chained && !$is_last) {
236 0           my $command_size = $packer->diff('header');
237 0           my $command_size_padded = ($command_size + 7) & ~7;
238 0           $packer->zero($command_size_padded - $command_size);
239 0           $packer->mark('end');
240 0           $packer->jump('next-command');
241 0           $packer->uint32($command_size_padded);
242             }
243 0 0 0       if (!$is_chained || $is_last) {
244 0           $packer->jump('netbios-header');
245 0           $packer->uint32_be(-$packer->diff('end') - 4);
246             }
247 0           $packer->jump('end');
248             }
249              
250             1;