File Coverage

lib/SMB/v2/Commands.pm
Criterion Covered Total %
statement 12 115 10.4
branch 0 40 0.0
condition 0 40 0.0
subroutine 4 6 66.6
pod 0 2 0.0
total 16 203 7.8


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   5 use strict;
  1         1  
  1         21  
19 1     1   4 use warnings;
  1         1  
  1         17  
20              
21 1     1   298 use SMB::v2::Header;
  1         2  
  1         337  
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 0         if ($parser->size < $MIN_MESSAGE_SIZE) {
85 0           warn sprintf "Too short message to parse (%d, should be at least %d)\n", $parser->size, $MIN_MESSAGE_SIZE;
86 0           return;
87             }
88              
89             # parse header following the SMB2 stamp "\xfeSMB"
90 0           $parser->uint16; # skip reserved
91 0           my $credit_charge = $parser->uint16;
92 0           my $status = $parser->uint32;
93 0           my $code = $parser->uint16;
94 0           my $credits = $parser->uint16;
95 0           my $flags = $parser->uint32;
96 0           my $offset = $parser->uint32; # offset of the next chain command or 0
97 0           my $mid = $parser->uint64;
98 0           my $aid = 0;
99 0           my $tid = 0;
100 0 0         if ($flags & SMB::v2::Header::FLAGS_ASYNC_COMMAND) {
101 0           $aid = $parser->uint64;
102             } else {
103 0           $parser->uint32; # reserved (according to spec), not pid
104 0           $tid = $parser->uint32;
105             }
106 0           my $uid = $parser->uint64;
107 0           my $sign = $parser->bytes(16);
108 0           my $struct_size = $parser->uint16;
109              
110 0           my $header = SMB::v2::Header->new(
111             code => $code,
112             status => $status,
113             uid => $uid,
114             tid => $tid,
115             mid => $mid,
116             signature => $sign,
117             flags => $flags,
118             aid => $aid,
119             credits => $credits,
120             credit_charge => $credit_charge,
121             struct_size => $struct_size,
122             );
123              
124 0           my $command_name = $command_names[$code];
125 0           my $command;
126              
127 0 0         if ($command_name) {
128 0           my $command_class = "SMB::v2::Command::$command_name";
129 0           my $command_filename = "SMB/v2/Command/$command_name.pm";
130 0 0 0       unless ($INC{$command_filename} || $::_INC{$command_filename}) {
131             # auto-load or auto-create requested sub-class
132 0 0         if (!eval { require $command_filename; 1; }) {
  0            
  0            
133 1     1   6 no strict 'refs';
  1         1  
  1         526  
134 0           @{"${command_class}::ISA"} = qw(SMB::v2::Command);
  0            
135 0           $::_INC{$command_filename} = 1;
136             }
137             }
138              
139 0           $command = $command_class->new($header);
140 0 0 0       return $command unless $command->is_success || $command->is('SessionSetup');
141              
142 0 0         $command = $command->parse($parser)
143             or warn sprintf "Failed to parse SMB2 command 0x%x ($command_name)\n", $code;
144             } else {
145 0           warn sprintf "Got unexisting SMB2 command 0x%x\n", $code;
146             }
147              
148 0           return $command;
149             }
150              
151             sub pack ($$$%) {
152 0     0 0   my $class = shift;
153 0           my $packer = shift;
154 0           my $command = shift;
155 0           my %options = @_;
156              
157 0           my $header = $command->header;
158 0           my $status = $command->status;
159              
160 0           my $is_response = $command->is_response;
161 0   0       my $struct_size = $options{struct_size} // $command_struct_sizes[$header->code][$is_response] // $header->struct_size;
      0        
162 0           my $is_chained = $options{is_chained};
163 0           my $is_first = $options{is_first};
164 0           my $is_last = $options{is_last};
165              
166 0           my $flags = $header->flags;
167 0 0         if ($is_response) {
168 0           $flags |= SMB::v2::Header::FLAGS_RESPONSE;
169             } else {
170 0           $flags &= ~SMB::v2::Header::FLAGS_RESPONSE;
171             }
172 0 0 0       if ($is_chained && !$is_first) {
173 0           $flags |= SMB::v2::Header::FLAGS_CHAINED;
174             } else {
175 0           $flags &= ~SMB::v2::Header::FLAGS_CHAINED;
176             }
177              
178             # skip NetBIOS header (length will be filled later)
179 0 0 0       if (!$is_chained || $is_first) {
180 0           $packer->mark('netbios-header');
181 0           $packer->skip(4);
182             }
183              
184             # pack SMB2 header
185 0           $packer->mark('smb-header');
186 0           $packer->bytes($header_stamp); # SMB2 magic signature
187 0           $packer->uint16(64); # header size
188 0           $packer->uint16($header->credit_charge);
189 0           $packer->mark('status');
190 0 0         $packer->uint32($is_response ? $status : 0);
191 0           $packer->uint16($header->code);
192 0   0       $packer->uint16($header->credits || 1);
193 0           $packer->uint32($flags);
194 0           $packer->mark('next-command');
195 0           $packer->uint32(0);
196 0           $packer->uint64($header->mid);
197             # aid or pid + tid
198 0 0         if ($flags & SMB::v2::Header::FLAGS_ASYNC_COMMAND) {
199 0           $packer->uint64($header->aid);
200             } else {
201 0           $packer->uint32(0); # no pid in SMB2 spec
202 0           $packer->uint32($header->tid);
203             }
204 0           $packer->uint64($header->uid);
205 0           $packer->bytes("\0" x 16); # no message signing for now
206              
207 0           $packer->mark('header-end');
208 0 0         $packer->uint16($command->is_success ? $struct_size : 9);
209 0           $packer->mark('command-start');
210              
211 0   0       my $is_error_packet = $command->is_error && !$command->is('SessionSetup');
212              
213 0 0         $command->pack($packer) if !$is_error_packet;
214 0 0         $packer->zero(6 + 1) if $is_error_packet;
215              
216 0           my $payload_allowed = $struct_size % 2;
217 0 0 0       $payload_allowed = 1 if $command->is('Negotiate') && !$is_response;
218 0           my $size = $packer->diff('header-end');
219 0           my $size0 = $struct_size & ~1;
220 0 0 0       die "SMB2 command $command->{name} pack produced size $size, expected $size0\n"
      0        
221             if $size > $size0 && !$payload_allowed && !$is_error_packet;
222 0 0         $packer->zero($size0 - $size) if $size0 > $size;
223              
224 0           $packer->mark('end');
225 0 0 0       if ($is_chained && !$is_last) {
226 0           my $command_size = $packer->diff('header');
227 0           my $command_size_padded = ($command_size + 7) & ~7;
228 0           $packer->zero($command_size_padded - $command_size);
229 0           $packer->mark('end');
230 0           $packer->jump('next-command');
231 0           $packer->uint32($command_size_padded);
232             }
233 0 0 0       if (!$is_chained || $is_last) {
234 0           $packer->jump('netbios-header');
235 0           $packer->uint32_be(-$packer->diff('end') - 4);
236             }
237 0           $packer->jump('end');
238             }
239              
240             1;