File Coverage

lib/SMB/Connection.pm
Criterion Covered Total %
statement 24 117 20.5
branch 0 34 0.0
condition 0 25 0.0
subroutine 8 29 27.5
pod 2 20 10.0
total 34 225 15.1


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::Connection;
17              
18 1     1   9 use strict;
  1         2  
  1         42  
19 1     1   9 use warnings;
  1         2  
  1         34  
20              
21 1     1   724 use bytes;
  1         18  
  1         9  
22              
23 1     1   38 use parent 'SMB';
  1         17  
  1         7  
24              
25 1     1   545 use SMB::Parser;
  1         4  
  1         40  
26 1     1   528 use SMB::Packer;
  1         4  
  1         48  
27 1     1   539 use SMB::v1::Commands;
  1         4  
  1         41  
28 1     1   529 use SMB::v2::Commands;
  1         3  
  1         1756  
29              
30 0     0 0   sub parse_uint8 { $_[0]->parser->uint8; }
31 0     0 0   sub parse_uint16 { $_[0]->parser->uint16; }
32 0     0 0   sub parse_uint32 { $_[0]->parser->uint32; }
33 0     0 0   sub parse_bytes { $_[0]->parser->bytes($_[1]); }
34 0     0 0   sub parse_smb1 { SMB::v1::Commands->parse($_[0]->parser) }
35 0     0 0   sub parse_smb2 { SMB::v2::Commands->parse($_[0]->parser) }
36              
37 0     0 0   sub pack_uint8 { $_[0]->packer->uint8($_[1]); }
38 0     0 0   sub pack_uint16 { $_[0]->packer->uint16($_[1]); }
39 0     0 0   sub pack_uint32 { $_[0]->packer->uint32($_[1]); }
40 0     0 0   sub pack_bytes { $_[0]->packer->bytes($_[1]); }
41 0     0 0   sub pack_smb1 { SMB::v1::Commands->pack(shift()->packer, shift, @_) }
42 0     0 0   sub pack_smb2 { SMB::v2::Commands->pack(shift()->packer, shift, @_) }
43              
44             sub new ($$$%) {
45 0     0 1   my $class = shift;
46 0   0       my $socket = shift || die "No socket";
47 0   0       my $id = shift || die "No id";
48 0           my %options = @_;
49              
50 0           my $self = $class->SUPER::new(
51             %options,
52             socket => $socket,
53             id => $id,
54             parser => SMB::Parser->new,
55             packer => SMB::Packer->new,
56             );
57              
58 0 0         unless ($self->log_level == SMB::LOG_LEVEL_NONE) {
59 0           my $addr_with_port = $self->get_socket_addr;
60 0 0         my ($id0, $str) = $id =~ /^-(.*)/ ? ($1, 'server') : ($id, 'client');
61 0           $self->{id_str} = "$str #$id0 [$addr_with_port]";
62             }
63              
64 0           $self->msg("Connected");
65              
66 0           return $self;
67             }
68              
69             sub DESTROY ($) {
70 0     0     my $self = shift;
71              
72 0           $self->close;
73             }
74              
75             sub close ($) {
76 0     0 0   my $self = shift;
77              
78 0           my $socket = $self->socket;
79 0 0 0       return unless $socket && $socket->opened;
80              
81 0           $self->msg("Disconnected");
82              
83 0           $socket->close;
84 0           $self->socket(undef);
85             }
86              
87             sub get_socket_addr ($;$) {
88 0     0 0   my $this = shift;
89 0   0       my $socket = shift || ref($this) && $this->socket || return;
90              
91 0           my $host = $socket->peerhost();
92 0           my $port = $socket->peerport();
93              
94 0 0         return wantarray ? ($host, $port) : "$host:$port";
95             }
96              
97             sub recv_nbss ($) {
98 0     0 0   my $self = shift;
99              
100 0           my $socket = $self->socket;
101 0           my $data1; # NBSS header
102             my $data2; # SMB packet
103 0           my $header_label = 'NetBIOS Session Service header';
104 0   0       my $len = $socket->read($data1, 4) //
105             return $self->err("Socket read failed: $!");
106 0 0         if ($len != 4) {
107 0           $self->err("Can't read $header_label (got $len bytes)");
108 0           return;
109             }
110 0           my ($packet_type, $packet_flags, $packet_len) = unpack('CCn', $data1);
111 0 0 0       if ($packet_type != 0 || $packet_flags > 1) {
112 0           $self->err("Only supported $header_label with type=0 flags=0|1");
113 0           return;
114             }
115 0 0         $packet_len += 1 << 16 if $packet_flags;
116 0   0       $len = $socket->read($data2, $packet_len) // 0;
117 0 0         if ($len != $packet_len) {
118 0           $self->err("Can't read full packet (expected $packet_len, got $len bytes)");
119 0           return;
120             }
121              
122 0           $self->parser->set($data1 . $data2, 4);
123             }
124              
125             sub recv_command ($) {
126 0     0 0   my $self = shift;
127              
128 0 0         $self->recv_nbss
129             or return;
130              
131 0           my $smb_stamp_start = $self->parser->offset;
132 0           my $smb_num = $self->parse_uint8;
133 0           my $smb_str = $self->parse_bytes(3);
134 0 0 0       if ($smb_str ne 'SMB' || $smb_num != 0xff && $smb_num != 0xfe) {
      0        
135 0           $self->err("Neither SMB1 nor SMB2 signature found, giving up");
136 0           $self->mem(chr($smb_num) . $smb_str, "Signature");
137 0           return;
138             }
139 0           my $is_smb1 = $smb_num == 0xff;
140 0           $self->mem($self->parser->data, "<- SMB Packet");
141 0           $self->parser->reset($smb_stamp_start);
142              
143 0           my @commands;
144              
145 0           while (1) {
146 0 0         my $command = $is_smb1
147             ? $self->parse_smb1
148             : $self->parse_smb2;
149              
150 0 0         if ($command) {
151 0           $self->dbg("%s", $command->to_string);
152 0           push @commands, $command;
153             } else {
154 0 0         $self->err("Failed to parse SMB%d packet", $is_smb1 ? 1 : 2);
155             }
156 0 0 0       last unless $command && $command->has_next_in_chain;
157             }
158              
159 0           return @commands;
160             }
161              
162             sub send_nbss ($$) {
163 0     0 0   my $self = shift;
164 0           my $data = shift;
165              
166 0           $self->mem($data, "-> NetBIOS Packet");
167              
168 0 0         if (!$self->socket->write($data, length($data))) {
169 0           $self->err("Can't write full packet");
170 0           return;
171             }
172             }
173              
174             sub send_command ($$) {
175 0     0 0   my $self = shift;
176 0           my $command = shift;
177              
178 0           $self->dbg("%s", $command->to_string);
179              
180 0           $self->packer->reset;
181              
182 0 0         $command->is_smb1
183             ? $self->pack_smb1($command, is_response => 1)
184             : $self->pack_smb2($command, is_response => 1);
185              
186 0           $self->send_nbss($self->packer->data);
187             }
188              
189             sub log ($$$) {
190 0     0 1   my $self = shift;
191 0           my $level = shift;
192 0           my $format = shift;
193 0 0         return if $level > $self->{log_level};
194              
195 0           $format =~ s/(:?$)/ - $self->{id_str}$1/;
196 0           $self->SUPER::log($level, $format, @_);
197             }
198              
199             1;