File Coverage

blib/lib/POE/Component/Proxy/SOCKS.pm
Criterion Covered Total %
statement 54 415 13.0
branch 8 188 4.2
condition 0 53 0.0
subroutine 10 48 20.8
pod 12 12 100.0
total 84 716 11.7


line stmt bran cond sub pod time code
1             package POE::Component::Proxy::SOCKS;
2             $POE::Component::Proxy::SOCKS::VERSION = '1.04';
3             #ABSTRACT: A POE based SOCKS 4 proxy server.
4              
5 1     1   550 use strict;
  1         1  
  1         23  
6 1     1   3 use warnings;
  1         1  
  1         25  
7 1     1   3 use POE qw(Component::Client::Ident Component::Client::DNS Wheel::SocketFactory Wheel::ReadWrite Filter::Stream);
  1         2  
  1         7  
8 1     1   81792 use Socket;
  1         2  
  1         507  
9 1     1   426 use Net::Netmask;
  1         4077  
  1         3525  
10              
11             sub spawn {
12 1     1 1 874 my $package = shift;
13 1         3 my %opts = @_;
14 1         4 $opts{lc $_} = delete $opts{$_} for keys %opts;
15 1         2 my $options = delete $opts{options};
16 1         3 my $self = bless \%opts, $package;
17 1 50       15 $self->{session_id} = POE::Session->create(
18             object_states => [
19             $self => { shutdown => '_shutdown',
20             send_event => '__send_event',
21             ident_agent_reply => '_ident_agent_reply',
22             ident_agent_error => '_ident_agent_error',
23             },
24             $self => [ qw(
25             _start
26             register
27             unregister
28             _accept_client
29             _accept_failed
30             _conn_input
31             _conn_error
32             _conn_alarm
33             __send_event
34             _ident_done
35             _reject_client
36             _dns_response
37             _do_connect
38             _do_bind
39             _sock_connection
40             _sock_up
41             _sock_failed
42             _sock_input
43             _sock_down
44             _sock_alarm
45             _bind_request
46             )
47             ],
48             ],
49             heap => $self,
50             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
51             )->ID();
52 1         446 return $self;
53             }
54              
55             sub session_id {
56 1     1 1 2 return $_[0]->{session_id};
57             }
58              
59             sub _conn_exists {
60 0     0   0 my ($self,$wheel_id) = @_;
61 0 0 0     0 return 0 unless $wheel_id and defined $self->{clients}->{ $wheel_id };
62 0         0 return 1;
63             }
64              
65             sub _link_exists {
66 0     0   0 my ($self,$wheel_id) = @_;
67 0 0 0     0 return 0 unless $wheel_id and defined $self->{links}->{ $wheel_id };
68 0         0 return 1;
69             }
70              
71             sub _sock_exists {
72 0     0   0 my ($self,$wheel_id) = @_;
73 0 0 0     0 return 0 unless $wheel_id and defined $self->{sockets}->{ $wheel_id };
74 0         0 return 1;
75             }
76              
77             sub _bind_request {
78 0     0   0 my ($self,$id) = @_;
79 0 0       0 return unless $self->_conn_exists( $id );
80 0         0 my $client = $self->{clients}->{ $id };
81 0         0 my $match;
82 0         0 foreach my $cid ( keys %{ $self->{clients} } ) {
  0         0  
83 0 0       0 next if $cid eq $id;
84 0 0       0 next if $self->{clients}->{ $cid }->{dstip} ne $client->{dstip};
85 0 0       0 next if $self->{clients}->{ $cid }->{dstport} ne $client->{dstport};
86 0         0 $match = $cid;
87 0         0 last;
88             }
89 0         0 return $match;
90             }
91              
92             sub shutdown {
93 0     0 1 0 my $self = shift;
94 0         0 $poe_kernel->post( $self->{session_id}, 'shutdown' );
95             }
96              
97             sub _start {
98 1     1   276 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
99 1         4 $self->{session_id} = $_[SESSION]->ID();
100 1 50       6 if ( $self->{alias} ) {
101 0         0 $kernel->alias_set( $self->{alias} );
102             }
103             else {
104 1         6 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
105             }
106 1 50       1508 if ( $kernel != $sender ) {
107 1         3 my $sender_id = $sender->ID;
108 1         6 $self->{events}->{'socksd_all'}->{$sender_id} = $sender_id;
109 1         2 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
110 1         2 $kernel->refcount_increment($sender_id, __PACKAGE__);
111 1         21 $kernel->post( $sender, 'socksd_registered', $self );
112             }
113              
114 1         105 $self->{resolver} = POE::Component::Client::DNS->spawn( Alias => 'socksd' . $self->{session_id}, Timeout => 10 );
115              
116 1         604 $self->{filter} = POE::Filter::Stream->new();
117              
118             $self->{listener} = POE::Wheel::SocketFactory->new(
119             ( defined $self->{address} ? ( BindAddress => $self->{address} ) : () ),
120 1 50       16 ( defined $self->{port} ? ( BindPort => $self->{port} ) : ( BindPort => 1080 ) ),
    50          
121             SuccessEvent => '_accept_client',
122             FailureEvent => '_accept_failed',
123             SocketDomain => AF_INET, # Sets the socket() domain
124             SocketType => SOCK_STREAM, # Sets the socket() type
125             SocketProtocol => 'tcp', # Sets the socket() protocol
126             Reuse => 'on', # Lets the port be reused
127             );
128 1         346 return;
129             }
130              
131             sub _shutdown {
132 1     1   1331 my ($kernel,$self) = @_[KERNEL,OBJECT];
133 1         7 delete $self->{listener};
134 1         159 delete $self->{clients};
135 1         20 delete $self->{sockets};
136 1         4 $kernel->alarm_remove_all();
137 1         44 $kernel->alias_remove( $_ ) for $kernel->alias_list();
138 1 50       24 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
139 1         28 $self->_unregister_sessions();
140 1         19 $self->{resolver}->shutdown();
141 1         137 return;
142             }
143              
144             sub _accept_client {
145 0     0   0 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
146 0         0 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( getsockname $socket ) )[1] );
147 0         0 my $sockport = ( unpack_sockaddr_in ( getsockname $socket ) )[0];
148 0         0 $peeraddr = inet_ntoa( $peeraddr );
149              
150 0 0       0 if ( $self->denied( $peeraddr ) ) {
151 0         0 $self->_send_event( 'socksd_denied', $peeraddr, $peerport );
152 0         0 return;
153             }
154              
155             my $wheel = POE::Wheel::ReadWrite->new(
156             Handle => $socket,
157             Filter => $self->{filter},
158 0         0 InputEvent => '_conn_input',
159             ErrorEvent => '_conn_error',
160             FlushedEvent => '_conn_flushed',
161             );
162              
163 0 0       0 return unless $wheel;
164              
165 0         0 my $id = $wheel->ID();
166 0         0 $self->{clients}->{ $id } =
167             {
168             wheel => $wheel,
169             peeraddr => $peeraddr,
170             peerport => $peerport,
171             sockaddr => $sockaddr,
172             sockport => $sockport,
173             };
174 0         0 $self->_send_event( 'socksd_connection', $id, $peeraddr, $peerport, $sockaddr, $sockport );
175              
176 0   0     0 $self->{clients}->{ $id }->{alarm} = $kernel->delay_set( '_conn_alarm', $self->{time_out} || 120, $id );
177              
178 0 0       0 if ( $self->{ident} ) {
179 0         0 POE::Component::Client::Ident::Agent->spawn(
180             PeerAddr => $peeraddr,
181             PeerPort => $peerport,
182             SockAddr => $sockaddr,
183             SockPort => $sockport,
184             BuggyIdentd => 1,
185             TimeOut => 10,
186             Reference => $id );
187             }
188              
189 0         0 return;
190             }
191              
192             sub _accept_failed {
193 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
194 0         0 warn "Wheel $wheel_id generated $operation error $errnum: $errstr\n";
195 0         0 delete $self->{listener};
196 0         0 $self->_send_event( 'socksd_listener_failed', $operation, $errnum, $errstr );
197 0         0 return;
198             }
199              
200             sub _ident_agent_reply {
201 0     0   0 my ($kernel,$self,$ref,$opsys,$other) = @_[KERNEL,OBJECT,ARG0,ARG1,ARG2];
202 0         0 my $wheel_id = $ref->{Reference};
203 0 0       0 return unless $self->_conn_exists( $wheel_id );
204 0         0 my $ident = '';
205             #$ident = $other if uc ( $opsys ) ne 'OTHER';
206 0         0 $ident = $other;
207 0         0 $self->{clients}->{ $wheel_id }->{ident} = $ident;
208 0         0 $kernel->yield( '_ident_done' => $wheel_id );
209 0         0 return;
210             }
211              
212             sub _ident_agent_error {
213 0     0   0 my ($kernel,$self,$ref,$error) = @_[KERNEL,OBJECT,ARG0,ARG1];
214 0         0 my $wheel_id = $ref->{Reference};
215 0 0       0 return unless $self->_conn_exists( $wheel_id );
216 0         0 $self->{clients}->{ $wheel_id }->{ident} = '';
217 0         0 $kernel->yield( '_ident_done' => $wheel_id );
218 0         0 return;
219             }
220              
221             sub _ident_done {
222 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
223 0 0       0 return unless $self->_conn_exists( $id );
224 0 0       0 return unless defined $self->{clients}->{ $id }->{user_id};
225 0 0       0 return unless defined $self->{clients}->{ $id }->{ident};
226 0         0 my $client = $self->{clients}->{ $id };
227 0 0       0 unless ( $client->{ident} ) {
228 0         0 $kernel->yield( '_reject_client', $id, '92', 'No Ident Response' );
229 0         0 return;
230             }
231 0 0       0 unless ( $client->{ident} eq $client->{user_id} ) {
232 0         0 $kernel->yield( '_reject_client', $id, '93', 'Ident and user_id mismatch' );
233 0         0 return;
234             }
235 0 0       0 $kernel->yield( '_do_connect', $id ) if $client->{cd} eq '1';
236 0 0       0 $kernel->yield( '_do_bind', $id ) if $client->{cd} eq '2';
237 0         0 return;
238             }
239              
240             sub _reject_client {
241 0     0   0 my ($kernel,$self,$id,$reject_id,$reason) = @_[KERNEL,OBJECT,ARG0,ARG1,ARG2];
242 0 0       0 return unless $self->_conn_exists( $id );
243 0         0 my $client = $self->{clients}->{ $id };
244 0         0 $client->{reject} = $reject_id;
245 0         0 my $response = pack "CCnN", 0, $reject_id, $client->{dstport}, inet_aton( $client->{dstip} );
246 0         0 $client->{wheel}->put( $response );
247 0         0 $self->_send_event( 'socksd_rejected', $id, $reject_id, $reason );
248 0         0 return;
249             }
250              
251             sub _parse_input {
252 0   0 0   0 my $input = shift || return;
253 0         0 my $null_idx = index $input, "\0";
254 0 0       0 return if $null_idx == -1;
255 0         0 my $request = substr $input, 0, $null_idx;
256 0 0       0 return unless $request;
257 0         0 my $packet = substr $input, 0, 4;
258 0 0 0     0 return unless $packet or length $packet == 4;
259 0         0 my @results = unpack "CCn", $packet;
260 0 0       0 return unless scalar @results == 3;
261 0         0 my $dstip = substr $input, 4, 4;
262 0 0       0 return unless $dstip;
263 0         0 push @results, $dstip;
264 0         0 my $remainder = substr $input, 8;
265 0         0 $remainder =~ s/\0$//g;
266 0         0 my ($id,$host) = split /\0/, $remainder;
267 0 0       0 $id = '' unless $id;
268 0         0 push @results, $id, $host;
269 0         0 return @results;
270             }
271              
272             sub _conn_input {
273 0     0   0 my ($kernel,$self,$input,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
274 0 0       0 return unless $self->_conn_exists( $id );
275 0         0 my $client = $self->{clients}->{ $id };
276 0   0     0 $kernel->delay_adjust( $client->{alarm}, $self->{time_out} || 120 );
277 0 0       0 unless ( $client->{link_id} ) {
278             # No uplink the client must be negotiating
279 0         0 my @args = _parse_input( $input );
280 0 0       0 unless ( @args ) {
281 0         0 delete $self->{clients}->{ $id };
282 0         0 return;
283             }
284 0         0 my ($vn,$cd,$dstport,$dstip,$userid,$host) = @args;
285 0         0 $dstip = inet_ntoa( $dstip );
286 0 0       0 unless ( $dstip ) {
287 0         0 delete $self->{clients}->{ $id };
288 0         0 return;
289             }
290 0         0 $client->{dstip} = $dstip;
291 0         0 $client->{dstport} = $dstport;
292 0         0 $client->{user_id} = $userid;
293 0 0 0     0 if ( $vn ne '4' or $cd !~ /^(1|2)$/ ) {
294 0         0 $kernel->yield( '_reject_client', $id, '91', 'Invalid request' );
295 0         0 return;
296             }
297 0 0 0     0 if ( $dstip =~ /^0\.0\.0\./ and $cd ne '2' ) {
298             # SOCKS 4a request
299 0 0       0 unless ( $host ) {
300 0         0 $kernel->yield( '_reject_client', $id, '91', 'SOCKS4a request. No host' );
301 0         0 return;
302             }
303             my $response = $self->{resolver}->resolve(
304 0         0 event => '_dns_response',
305             host => $host,
306             context => { id => $id },
307             );
308 0 0       0 if ( $response ) {
309 0         0 $kernel->yield( _dns_response => $response );
310             }
311 0         0 return;
312             }
313 0 0       0 if ( $cd eq '2' ) {
314 0         0 my $cid = $self->_bind_request( $id );
315 0 0       0 unless ( $cid ) {
316 0         0 $kernel->yield( '_reject_client', $id, '91', 'Invalid request' );
317 0         0 return;
318             }
319 0         0 $client->{primary} = $cid;
320 0 0       0 $kernel->yield( '_ident_done', $id ) if $self->{ident};
321 0 0       0 $kernel->yield( '_do_bind', $id ) unless $self->{ident};
322 0         0 return;
323             }
324 0 0       0 $kernel->yield( '_ident_done', $id ) if $self->{ident};
325 0 0       0 $kernel->yield( '_do_connect', $id ) unless $self->{ident};
326 0         0 return;
327             }
328 0 0       0 return unless $self->_link_exists( $client->{link_id} );
329 0         0 $self->{links}->{ $client->{link_id} }->{wheel}->put( $input );
330 0         0 return;
331             }
332              
333             sub _dns_response {
334 0     0   0 my ($kernel,$self,$arg) = @_[KERNEL,OBJECT,ARG0];
335 0         0 my $net_dns_packet = $arg->{response};
336 0         0 my $net_dns_errorstring = $arg->{error};
337 0         0 my $id = $arg->{context}->{id};
338 0 0       0 return unless $self->_conn_exists( $id );
339 0 0       0 unless( defined $net_dns_packet ) {
340 0         0 $kernel->yield( '_reject_client', $id, '91', 'DNS failed' );
341 0         0 return;
342             }
343 0         0 my @net_dns_answers = $net_dns_packet->answer;
344 0 0       0 unless ( @net_dns_answers ) {
345 0         0 $kernel->yield( '_reject_client', $id, '91', 'No DNS answers' );
346 0         0 return;
347             }
348 0         0 foreach my $net_dns_answer (@net_dns_answers) {
349 0 0       0 next unless $net_dns_answer->type eq 'A';
350 0         0 $self->{clients}->{ $id }->{dstip} = $net_dns_answer->rdatastr;
351 0         0 $self->_send_event( 'socksd_dns_lookup', $id, $arg->{host}, $self->{clients}->{ $id }->{dstip} );
352 0 0       0 $kernel->yield( '_ident_done', $id ) if $self->{ident};
353 0 0       0 $kernel->yield( '_do_connect', $id ) unless $self->{ident};
354 0         0 return;
355             }
356 0         0 $kernel->yield( '_reject_client', $id, '91', 'No DNS records found' );
357 0         0 return;
358             }
359              
360             sub _conn_error {
361 0     0   0 my ($self,$errstr,$id) = @_[OBJECT,ARG2,ARG3];
362 0 0       0 return unless $self->_conn_exists( $id );
363 0         0 $self->_delete_client( $id );
364 0         0 $self->_send_event( 'socksd_disconnected', $id, $errstr );
365 0         0 return;
366             }
367              
368             sub _conn_flushed {
369 0     0   0 my ($self,$id) = @_[OBJECT,ARG0];
370 0 0       0 return unless $self->_conn_exists( $id );
371 0 0       0 return if $self->{clients}->{ $id }->{link_id};
372 0 0       0 return unless $self->{clients}->{ $id }->{reject};
373 0         0 $self->_delete_client( $id );
374 0         0 return;
375             }
376              
377             sub _conn_alarm {
378 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
379 0 0       0 return unless $self->_conn_exists( $id );
380 0         0 $self->_delete_client( $id );
381 0         0 $self->_send_event( 'socksd_disconnected', $id );
382 0         0 return;
383             }
384              
385             sub _delete_client {
386 0     0   0 my ($self,$id) = @_;
387 0 0       0 return unless $self->_conn_exists( $id );
388 0         0 my $client = delete $self->{clients}->{ $id };
389 0 0 0     0 if ( $client->{link_id} and $self->_link_exists( $client->{link_id} ) ) {
390 0         0 delete $self->{links}->{ $client->{link_id} };
391             }
392 0 0 0     0 if ( $client->{factory} and $self->_sock_exists( $client->{factory} ) ) {
393 0         0 delete $self->{sockets}->{ $client->{factory} };
394             }
395 0         0 return 1;
396             }
397              
398             sub _do_connect {
399 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
400 0 0       0 return unless $self->_conn_exists( $id );
401 0         0 my $client = $self->{clients}->{ $id };
402             my $factory = POE::Wheel::SocketFactory->new(
403             SocketDomain => AF_INET,
404             SocketType => SOCK_STREAM,
405             SocketProtocol => 'tcp',
406             RemoteAddress => $client->{dstip},
407             RemotePort => $client->{dstport},
408 0         0 SuccessEvent => '_sock_up',
409             FailureEvent => '_sock_failed',
410             );
411 0         0 my $fact_id = $factory->ID();
412 0         0 $client->{factory} = $fact_id;
413 0         0 $self->{sockets}->{ $fact_id } = { client => $id, factory => $factory };
414 0         0 return;
415             }
416              
417             sub _do_bind {
418 0     0   0 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
419 0 0       0 return unless $self->_conn_exists( $id );
420 0         0 my $client = $self->{clients}->{ $id };
421 0         0 my $primary = $client->{primary};
422 0 0       0 return unless $self->_conn_exists( $primary );
423 0         0 my $link_id = $self->{clients}->{ $primary }->{link_id};
424 0 0 0     0 return unless $link_id or $self->_link_exists( $link_id );
425 0         0 my $bindaddr = $self->{links}->{ $link_id }->{sockaddr};
426 0         0 my $factory = POE::Wheel::SocketFactory->new(
427             SocketDomain => AF_INET,
428             SocketType => SOCK_STREAM,
429             SocketProtocol => 'tcp',
430             BindAddress => $bindaddr,
431             BindPort => 0,
432             Reuse => 'yes',
433             SuccessEvent => '_sock_connection',
434             FailureEvent => '_sock_failed',
435             );
436 0         0 my $sockname = $factory->getsockname();
437 0 0       0 unless ( $sockname ) {
438 0         0 $kernel->yield( '_reject_client', $id, '91', 'Socket failed' );
439 0         0 return;
440             }
441 0         0 my ($port, $myaddr) = sockaddr_in( $sockname );
442 0         0 my $fact_id = $factory->ID();
443 0         0 $client->{factory} = $fact_id;
444 0         0 $self->{sockets}->{ $fact_id } = { client => $id, factory => $factory };
445 0   0     0 $self->{sockets}->{ $fact_id }->{alarm} = $kernel->delay_set( '_sock_alarm', $self->{time_out} || 120, $fact_id, $id );
446 0         0 my $response = pack "CCnN", 0, 90, $port, $myaddr;
447 0         0 $client->{wheel}->put( $response );
448 0         0 $self->_send_event( 'socksd_bind_up', $id, $fact_id, inet_ntoa( $myaddr ), $port );
449 0         0 return;
450             }
451              
452             sub _sock_failed {
453 0     0   0 my ($kernel,$self,$op,$errno,$errstr,$fact_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
454 0         0 my $factory = delete $self->{sockets}->{ $fact_id };
455 0         0 $kernel->alarm_remove( $factory->{alarm} );
456 0         0 my $client_id = $factory->{client};
457 0 0       0 delete $self->{clients}->{ $client_id }->{factory} if $self->_conn_exists( $client_id );
458 0         0 $kernel->yield( '_reject_client', $client_id, '91', 'Socket failed', $op, $errno, $errstr );
459 0         0 return;
460             }
461              
462             sub _sock_up {
463 0     0   0 my ($kernel,$self,$socket,$fact_id) = @_[KERNEL,OBJECT,ARG0,ARG3];
464 0         0 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( getsockname $socket ) )[1] );
465 0         0 my $sockport = ( unpack_sockaddr_in ( getsockname $socket ) )[0];
466 0         0 my $factory = delete $self->{sockets}->{ $fact_id };
467 0         0 my $client_id = $factory->{client};
468 0 0       0 return unless $self->_conn_exists( $client_id );
469 0         0 my $wheel = POE::Wheel::ReadWrite->new(
470             Handle => $socket,
471             Filter => POE::Filter::Stream->new(),
472             InputEvent => '_sock_input',
473             ErrorEvent => '_sock_down',
474             );
475 0         0 my $link_id = $wheel->ID();
476 0         0 $self->{clients}->{ $client_id }->{link_id} = $link_id;
477 0         0 $self->{links}->{ $link_id } = { client => $client_id, wheel => $wheel, sockaddr => $sockaddr, sockport => $sockport };
478 0         0 my $client = $self->{clients}->{ $client_id };
479 0         0 my $response = pack "CCnN", 0, 90, $client->{dstport}, unpack("N", inet_aton( $client->{dstip}) );
480 0         0 $client->{wheel}->put( $response );
481 0         0 $self->_send_event( 'socksd_sock_up', $client_id, $link_id, $client->{dstip}, $client->{dstport} );
482 0         0 return;
483             }
484              
485             sub _sock_connection {
486 0     0   0 my ($kernel,$self,$socket,$peeraddr,$fact_id) = @_[KERNEL,OBJECT,ARG0,ARG1,ARG3];
487 0         0 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( getsockname $socket ) )[1] );
488 0         0 my $sockport = ( unpack_sockaddr_in ( getsockname $socket ) )[0];
489 0         0 $peeraddr = inet_ntoa( $peeraddr );
490 0         0 my $factory = delete $self->{sockets}->{ $fact_id };
491 0         0 $kernel->alarm_remove( $factory->{alarm} );
492 0         0 my $client_id = $factory->{client};
493 0 0       0 return unless $self->_conn_exists( $client_id );
494 0         0 my $client = $self->{clients}->{ $client_id };
495 0 0       0 unless ( $peeraddr eq $client->{dstip} ) {
496 0         0 $kernel->yield( '_reject_client', $client_id, '91', 'dstip and connecting ip differ' );
497 0         0 return;
498             }
499 0         0 my $wheel = POE::Wheel::ReadWrite->new(
500             Handle => $socket,
501             Filter => POE::Filter::Stream->new(),
502             InputEvent => '_sock_input',
503             ErrorEvent => '_sock_down',
504             );
505 0         0 my $link_id = $wheel->ID();
506 0         0 $client->{link_id} = $link_id;
507 0         0 $self->{links}->{ $link_id } = { client => $client_id, wheel => $wheel, sockaddr => $sockaddr, sockport => $sockport };
508 0         0 my $response = pack "CCnN", 0, 90, $sockport, inet_aton( $sockaddr );
509 0         0 $client->{wheel}->put( $response );
510 0         0 $self->_send_event( 'socksd_sock_up', $client_id, $link_id, $sockaddr, $sockport );
511 0         0 return;
512             }
513              
514             sub _sock_alarm {
515 0     0   0 my ($kernel,$self,$fact_id,$client_id) = @_[KERNEL,OBJECT,ARG0..ARG1];
516 0         0 delete $self->{sockets}->{ $fact_id };
517 0         0 delete $self->{clients}->{ $client_id };
518 0         0 return;
519             }
520              
521             sub _sock_input {
522 0     0   0 my ($kernel,$self,$input,$link_id) = @_[KERNEL,OBJECT,ARG0,ARG1];
523 0 0       0 return unless $self->_link_exists( $link_id );
524 0         0 my $client_id = $self->{links}->{ $link_id }->{client};
525 0 0       0 return unless $self->_conn_exists( $client_id );
526 0         0 $self->{clients}->{ $client_id }->{wheel}->put( $input );
527 0         0 return;
528             }
529              
530             sub _sock_down {
531 0     0   0 my ($self,$errstr,$link_id) = @_[OBJECT,ARG2,ARG3];
532 0 0       0 return unless $self->_link_exists( $link_id );
533 0         0 my $client_id = $self->{links}->{ $link_id }->{client};
534 0         0 $self->{clients}->{$client_id}->{wheel}->flush;
535 0         0 my $link = delete $self->{links}->{ $link_id };
536 0 0 0     0 if ( $link->{client} and $self->_conn_exists( $link->{client} ) ) {
537 0         0 delete $self->{clients}->{ $link->{client} };
538             }
539 0         0 $self->_send_event( 'socksd_sock_down', $link->{client}, $link_id, $errstr );
540 0         0 $self->_send_event( 'socksd_disconnected', $link->{client} );
541 0         0 return;
542             }
543              
544             sub register {
545 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
546             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
547              
548 0 0       0 unless (@events) {
549 0         0 warn "register: Not enough arguments";
550 0         0 return;
551             }
552              
553 0         0 my $sender_id = $sender->ID();
554              
555 0         0 foreach (@events) {
556 0 0       0 $_ = "socksd_" . $_ unless /^_/;
557 0         0 $self->{events}->{$_}->{$sender_id} = $sender_id;
558 0         0 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
559 0 0 0     0 unless ($self->{sessions}->{$sender_id}->{refcnt}++ or $session == $sender) {
560 0         0 $kernel->refcount_increment($sender_id, __PACKAGE__);
561             }
562             }
563              
564 0         0 $kernel->post( $sender, 'socksd_registered', $self );
565 0         0 return;
566             }
567              
568             sub unregister {
569 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
570             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
571              
572 0 0       0 unless (@events) {
573 0         0 warn "unregister: Not enough arguments";
574 0         0 return;
575             }
576              
577 0         0 $self->_unregister($session,$sender,@events);
578 0         0 undef;
579             }
580              
581             sub _unregister {
582 0     0   0 my ($self,$session,$sender) = splice @_,0,3;
583 0         0 my $sender_id = $sender->ID();
584              
585 0         0 foreach (@_) {
586 0 0       0 $_ = "socksd_" . $_ unless /^_/;
587 0         0 my $blah = delete $self->{events}->{$_}->{$sender_id};
588 0 0       0 unless ( $blah ) {
589 0         0 warn "$sender_id hasn't registered for '$_' events\n";
590 0         0 next;
591             }
592 0 0       0 if (--$self->{sessions}->{$sender_id}->{refcnt} <= 0) {
593 0         0 delete $self->{sessions}->{$sender_id};
594 0 0       0 unless ($session == $sender) {
595 0         0 $poe_kernel->refcount_decrement($sender_id, __PACKAGE__);
596             }
597             }
598             }
599 0         0 undef;
600             }
601              
602             sub _unregister_sessions {
603 1     1   1 my $self = shift;
604 1         4 my $socksd_id = $self->session_id();
605 1         1 foreach my $session_id ( keys %{ $self->{sessions} } ) {
  1         2  
606 1 50       5 if (--$self->{sessions}->{$session_id}->{refcnt} <= 0) {
607 1         2 delete $self->{sessions}->{$session_id};
608 1 50       3 $poe_kernel->refcount_decrement($session_id, __PACKAGE__)
609             unless ( $session_id eq $socksd_id );
610             }
611             }
612             }
613              
614             sub __send_event {
615 0     0     my( $self, $event, @args ) = @_[ OBJECT, ARG0, ARG1 .. $#_ ];
616 0           $self->_send_event( $event, @args );
617 0           return;
618             }
619              
620             sub send_event {
621 0     0 1   my $self = shift;
622 0           $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
623             }
624              
625             sub _send_event {
626 0     0     my $self = shift;
627 0           my ($event, @args) = @_;
628 0           my $kernel = $POE::Kernel::poe_kernel;
629 0           my $session = $kernel->get_active_session()->ID();
630 0           my %sessions;
631              
632 0           $sessions{$_} = $_ for (values %{$self->{events}->{'socksd_all'}}, values %{$self->{events}->{$event}});
  0            
  0            
633              
634 0           $kernel->post( $_ => $event => @args ) for values %sessions;
635 0           undef;
636             }
637              
638             ##################
639             # Access Control #
640             ##################
641              
642             sub add_denial {
643 0     0 1   my $self = shift;
644 0   0       my $netmask = shift || return;
645 0 0         return unless $netmask->isa('Net::Netmask');
646 0           $self->{denials}->{ $netmask } = $netmask;
647 0           return 1;
648             }
649              
650             sub del_denial {
651 0     0 1   my $self = shift;
652 0   0       my $netmask = shift || return;
653 0 0         return unless $netmask->isa('Net::Netmask');
654 0 0         return unless $self->{denials}->{ $netmask };
655 0           delete $self->{denials}->{ $netmask };
656 0           return 1;
657             }
658              
659             sub add_exemption {
660 0     0 1   my $self = shift;
661 0   0       my $netmask = shift || return;
662 0 0         return unless $netmask->isa('Net::Netmask');
663 0 0         $self->{exemptions}->{ $netmask } = $netmask unless $self->{exemptions}->{ $netmask };
664 0           return 1;
665             }
666              
667             sub del_exemption {
668 0     0 1   my $self = shift;
669 0   0       my $netmask = shift || return;
670 0 0         return unless $netmask->isa('Net::Netmask');
671 0 0         return unless $self->{exemptions}->{ $netmask };
672 0           delete $self->{exemptions}->{ $netmask };
673 0           return 1;
674             }
675              
676             sub denied {
677 0     0 1   my $self = shift;
678 0   0       my $ipaddr = shift || return;
679 0 0         return 0 if $self->exempted( $ipaddr );
680 0           foreach my $mask ( keys %{ $self->{denials} } ) {
  0            
681 0 0         return 1 if $self->{denials}->{ $mask }->match($ipaddr);
682             }
683 0           return 0;
684             }
685              
686             sub exempted {
687 0     0 1   my $self = shift;
688 0   0       my $ipaddr = shift || return;
689 0           foreach my $mask ( keys %{ $self->{exemptions} } ) {
  0            
690 0 0         return 1 if $self->{exemptions}->{ $mask }->match($ipaddr);
691             }
692 0           return 0;
693             }
694              
695             qq[SOCKS it to me];
696              
697             __END__