File Coverage

blib/lib/POE/Component/Client/SOCKS.pm
Criterion Covered Total %
statement 50 188 26.6
branch 7 54 12.9
condition 0 38 0.0
subroutine 10 21 47.6
pod 5 5 100.0
total 72 306 23.5


line stmt bran cond sub pod time code
1             package POE::Component::Client::SOCKS;
2              
3 2     2   78883 use strict;
  2         4  
  2         79  
4 2     2   11 use warnings;
  2         3  
  2         66  
5 2     2   10 use Carp;
  2         6  
  2         170  
6 2     2   879 use Socket;
  2         3567  
  2         1521  
7 2     2   799 use POE qw(Wheel::SocketFactory Filter::Stream Wheel::ReadWrite);
  2         588379  
  2         18  
8 2     2   2649313 use vars qw($VERSION);
  2         6  
  2         4601  
9              
10             $VERSION = '1.00';
11              
12             sub spawn {
13 1     1 1 1136 my $package = shift;
14 1         4 return $package->_create( 'spawn', @_ );
15             }
16              
17             sub connect {
18 0     0 1 0 my $self;
19 0         0 eval {
20 0 0 0     0 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
21 0         0 $self = shift;
22             }
23             };
24 0 0       0 if ( $self ) {
25 0         0 $poe_kernel->post( $self->{session_id}, 'connect', @_ );
26 0         0 return 1;
27             }
28 0         0 my $package = shift;
29 0         0 return $package->_create( 'connect', @_ );
30             }
31              
32             sub bind {
33 0     0 1 0 my $self;
34 0         0 eval {
35 0 0 0     0 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
36 0         0 $self = shift;
37             }
38             };
39 0 0       0 if ( $self ) {
40 0         0 $poe_kernel->post( $self->{session_id}, 'bind', @_ );
41 0         0 return 1;
42             }
43 0         0 my $package = shift;
44 0         0 return $package->_create( 'bind', @_ );
45             }
46              
47             sub _create {
48 1     1   2 my $package = shift;
49 1         1 my $command = shift;
50 1         2 my %opts = @_;
51 1         4 $opts{lc $_} = delete $opts{$_} for keys %opts;
52 1         3 my $options = delete $opts{options};
53 1         3 my $self = bless { }, $package;
54 1 50       7 if ( $command =~ /^(bind|connect)$/ ) {
55 0 0 0     0 unless ( $opts{successevent} and $opts{failureevent} ) {
56 0         0 warn "You must specify 'SuccessEvent' and 'FailureEvent' for '$command'\n";
57 0         0 return;
58             }
59 0 0 0     0 unless ( $opts{remoteaddress} and $opts{remoteport} ) {
60 0         0 warn "You must specify 'RemoteAddress' and 'RemotePort'\n";
61 0         0 return;
62             }
63 0 0       0 unless ( $opts{socksproxy} ) {
64 0         0 warn "You must specify 'SocksProxy'\n";
65 0         0 return;
66             }
67 0 0 0     0 if ( $command eq 'bind' and $opts{remoteaddress} !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
68 0         0 warn "You specified 'bind' but 'RemoteAddress' is not an IP address\n";
69 0         0 return;
70             }
71             }
72 1 50       20 $self->{session_id} = POE::Session->create(
73             object_states => [
74             $self => { shutdown => '_shutdown',
75             connect => '_command',
76             bind => '_command',
77             },
78             $self => [qw(_start
79             _disconnect
80             _create_socket
81             _sock_failed
82             _sock_up
83             _conn_input
84             _conn_error) ],
85             ],
86             heap => $self,
87             args => [ $command, %opts ],
88             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
89             )->ID();
90 1         680 return $self;
91             }
92              
93             sub session_id {
94 0     0 1 0 return $_[0]->{session_id};
95             }
96              
97             sub shutdown {
98 0     0 1 0 my $self = shift;
99 0         0 $poe_kernel->post( $self->{session_id}, 'shutdown' );
100 0         0 return 1;
101             }
102              
103             sub _start {
104 1     1   236 my ($kernel,$self,$sender,$command,@args) = @_[KERNEL,OBJECT,SENDER,ARG0..$#_];
105 1         4 $self->{session_id} = $_[SESSION]->ID();
106 1         10 $self->{filter} = POE::Filter::Stream->new();
107 1 50       9 if ( $command eq 'spawn' ) {
108 1         1 my $opts = { @args };
109 1         2 $self->{$_} = $opts->{$_} for keys %{ $opts };
  1         3  
110 1 50       6 $kernel->alias_set($self->{alias}) if $self->{alias};
111 1 50       6 $kernel->refcount_increment($self->{session_id}, __PACKAGE__) unless $self->{alias};
112 1         33 return;
113             }
114 0 0       0 if ( $kernel == $sender ) {
115 0         0 croak "'connect' and 'bind' should be called from another POE Session\n";
116             }
117 0         0 $self->{sender_id} = $sender->ID();
118 0         0 $kernel->refcount_increment( $self->{sender_id}, __PACKAGE__ );
119 0         0 $kernel->yield( $command, @args );
120 0         0 return;
121             }
122              
123             sub _shutdown {
124 1     1   1365 my ($kernel,$self) = @_[KERNEL,OBJECT];
125 1 50       17 unless ( $self->{sender_id} ) {
126 1         7 $kernel->alias_remove($_) for $kernel->alias_list();
127 1 50       38 $kernel->refcount_decrement($self->{session_id}, __PACKAGE__) unless $self->{alias};
128             }
129 1         37 $kernel->refcount_decrement( $_->{sender_id}, __PACKAGE__ ) for values %{ $self->{socks} };
  1         5  
130 1         2 $kernel->refcount_decrement( $_->{sender_id}, __PACKAGE__ ) for values %{ $self->{conns} };
  1         4  
131 1         2 delete $self->{socks};
132 1         2 delete $self->{conns};
133 1         3 return;
134             }
135              
136             sub _command {
137 0     0     my ($kernel,$self,$state,$session,$sender) = @_[KERNEL,OBJECT,STATE,SESSION,SENDER];
138 0           my $args;
139 0 0         if ( ref $_[ARG0] eq 'HASH' ) {
140 0           $args = $_[ARG0];
141             }
142             else {
143 0           $args = { @_[ARG0..$#_] };
144             }
145 0           $args->{cmd} = $state;
146 0 0         if ( $session == $sender ) {
147 0           $args->{sender_id} = $self->{sender_id};
148             }
149             else {
150 0           $args->{lc $_} = delete $args->{$_} for keys %{ $args };
  0            
151 0           $args->{sender_id} = $sender->ID();
152 0 0 0       unless ( $args->{successevent} and $args->{failureevent} ) {
153 0           warn "You must specify 'SuccessEvent' and 'FailureEvent'\n";
154 0           return;
155             }
156 0 0 0       unless ( $args->{remoteaddress} and $args->{remoteport} ) {
157 0           warn "You must specify 'RemoteAddress' and 'RemotePort'\n";
158 0           return;
159             }
160 0 0         unless ( $args->{socksproxy} ) {
161 0           warn "You must specify 'SocksProxy'\n";
162 0           return;
163             }
164 0 0 0       if ( $state eq 'bind' and $args->{remoteaddress} !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
165 0           warn "You specified 'bind' but 'RemoteAddress' is not an IP address\n";
166 0           return;
167             }
168 0           $kernel->refcount_increment( $args->{sender_id}, __PACKAGE__ );
169             }
170 0 0         if ( $state eq 'connect' ) {
171 0 0         if ( $args->{remoteaddress} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
172             # SOCKS 4
173 0   0       $args->{packet} = pack ('CCn', 4, 1, $args->{remoteport}) .
174             inet_aton($args->{remoteaddress}) . ( $args->{socks_id} || '' ) . (pack 'x');
175             }
176             else {
177             # SOCKS 4a
178 0   0       $args->{packet} = pack ('CCn', 4, 1, $args->{remoteport}) .
179             inet_aton('0.0.0.1') . ( $args->{socks_id} || '' ) . (pack 'x') .
180             $args->{remoteaddress} . (pack 'x');
181             }
182             }
183             else {
184 0   0       $args->{packet} = pack ('CCn', 4, 2, $args->{remoteport}) .
185             inet_aton($args->{remoteaddress}) . ( $args->{socks_id} || '' ) . (pack 'x');
186             }
187 0           $kernel->yield( '_create_socket', $args );
188 0           return;
189             }
190              
191             sub _create_socket {
192 0     0     my ($kernel,$self,$args) = @_[KERNEL,OBJECT,ARG0];
193 0   0       my $factory = POE::Wheel::SocketFactory->new(
194             SocketDomain => AF_INET,
195             SocketType => SOCK_STREAM,
196             SocketProtocol => 'tcp',
197             RemoteAddress => $args->{socksproxy},
198             RemotePort => $args->{socksport} || 1080,
199             SuccessEvent => '_sock_up',
200             FailureEvent => '_sock_failed',
201             );
202 0           $args->{factory} = $factory;
203 0           $self->{socks}->{ $factory->ID } = $args;
204 0           return;
205             }
206              
207             sub _sock_failed {
208 0     0     my ($kernel,$self,$operation,$errnum,$errstr,$factory_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
209 0           my $args = delete $self->{socks}->{ $factory_id };
210 0           delete $args->{factory};
211 0           delete $args->{packet};
212 0           my $sender_id = delete $args->{sender_id};
213 0           $args->{sockerr} = [ $operation, $errnum, $errstr ];
214 0           $kernel->refcount_decrement( $sender_id, __PACKAGE__ );
215 0           $kernel->post( $sender_id, $args->{failureevent}, $args );
216 0           return;
217             }
218              
219             sub _sock_up {
220 0     0     my ($kernel,$self,$socket,$peeraddr,$peerport,$fact_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
221 0           $peeraddr = inet_aton( $peeraddr );
222 0           my $args = delete $self->{socks}->{ $fact_id };
223 0           delete $args->{factory};
224 0           $args->{socket} = $socket;
225 0           my $wheel = POE::Wheel::ReadWrite->new(
226             Handle => $socket,
227             Filter => $self->{filter},
228             InputEvent => '_conn_input',
229             ErrorEvent => '_conn_error',
230             );
231 0           $args->{wheel} = $wheel;
232 0           $self->{conns}->{ $wheel->ID } = $args;
233 0           $wheel->put( $args->{packet} );
234 0           return;
235             }
236              
237             sub _conn_input {
238 0     0     my ($kernel,$self,$input,$wheel_id) = @_[KERNEL,OBJECT,ARG0,ARG1];
239 0 0         if ( length $input != 8 ) {
240 0           $kernel->yield( '_disconnect', $wheel_id, 'Mangled response from SOCKS proxy' );
241 0           return;
242             }
243 0           my @resp = unpack "CCnN", $input;
244 0 0 0       unless ( scalar @resp == 4 and $resp[0] eq '0' and $resp[1] =~ /^(90|91|92|93)$/ ) {
      0        
245 0           $kernel->yield( '_disconnect', $wheel_id, 'Mangled response from SOCKS proxy' );
246 0           return;
247             }
248 0           my ($vn,$cd,$dstport,$dstip) = @resp;
249 0           my $args = delete $self->{conns}->{ $wheel_id };
250 0           delete $args->{wheel};
251 0           delete $args->{packet};
252 0           my $sender_id = delete $args->{sender_id};
253 0 0         unless ( $cd eq '90' ) {
254 0           delete $args->{socket};
255 0           $args->{socks_error} = $cd;
256 0           $kernel->post( $sender_id, $args->{failureevent}, $args );
257 0           $kernel->refcount_decrement( $sender_id, __PACKAGE__ );
258 0           return;
259             }
260 0           $args->{socks_response} = [ $cd, inet_ntoa( pack "N", $dstip ), $dstport ];
261 0           $kernel->post( $sender_id, $args->{successevent}, $args );
262 0           $kernel->refcount_decrement( $sender_id, __PACKAGE__ );
263 0           return;
264             }
265              
266             sub _conn_error {
267 0     0     my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
268 0           my $args = delete $self->{conns}->{ $wheel_id };
269 0           delete $args->{wheel};
270 0           delete $args->{socket};
271 0           delete $args->{packet};
272 0           my $sender_id = delete $args->{sender_id};
273 0           $args->{sockerr} = [ $operation, $errnum, $errstr ];
274 0           $kernel->post( $sender_id, $args->{failureevent}, $args );
275 0           $kernel->refcount_decrement( $sender_id, __PACKAGE__ );
276 0           return;
277             }
278              
279             sub _disconnect {
280 0     0     my ($kernel,$self,$wheel_id,$reason) = @_[KERNEL,OBJECT,ARG0,ARG1];
281 0           my $args = delete $self->{conns}->{ $wheel_id };
282 0           delete $args->{wheel};
283 0           delete $args->{socket};
284 0           delete $args->{packet};
285 0           my $sender_id = delete $args->{sender_id};
286 0           $args->{socks_unknown} = $reason;
287 0           $kernel->refcount_decrement( $sender_id, __PACKAGE__ );
288 0           $kernel->post( $sender_id, $args->{failureevent}, $args );
289 0           return;
290             }
291              
292             1;
293             __END__