File Coverage

blib/lib/POE/Component/Client/SOCKS.pm
Criterion Covered Total %
statement 47 185 25.4
branch 7 54 12.9
condition 0 38 0.0
subroutine 9 20 45.0
pod 5 5 100.0
total 68 302 22.5


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