File Coverage

blib/lib/Net/SC.pm
Criterion Covered Total %
statement 33 544 6.0
branch 0 258 0.0
condition 0 136 0.0
subroutine 11 68 16.1
pod 8 52 15.3
total 52 1058 4.9


line stmt bran cond sub pod time code
1             #######################################################################
2             #
3             # $Id: SC.pm,v 1.22 2009-11-14 10:18:40 gosha Exp $
4             #
5             # Socks Chain ( TCP only )
6             #
7             # Copyright (C) Okunev Igor gosha@prv.mts-nn.ru 2002-2006
8             #
9             # All rights reserved. This program is free software;
10             # you can redistribute it and/or modify it under the
11             # same terms as Perl itself.
12             #
13             ########################################################################
14             package Net::SC;
15              
16 1     1   1334 use strict;
  1         2  
  1         57  
17 1     1   5 use vars qw( @ISA @EXPORT $VERSION );
  1         4  
  1         86  
18              
19 1     1   5 use Fcntl qw(:DEFAULT :flock);
  1         6  
  1         586  
20 1     1   1067 use Symbol;
  1         1010  
  1         85  
21 1     1   6 use Config;
  1         1  
  1         47  
22 1     1   5 use Exporter;
  1         1  
  1         33  
23 1     1   984 use IO::Socket;
  1         30375  
  1         6  
24 1     1   2201 use MIME::Base64;
  1         1173  
  1         150  
25              
26 1     1   1344 local $[ = 0;
  1         733  
  1         346  
27              
28             ($VERSION='$Revision: 1.21 $')=~s/^\S+\s+(\S+)\s+.*/$1/;
29              
30             @ISA = qw( Exporter );
31              
32             @EXPORT = qw( socks_error
33              
34             SOCKS_GENERAL_SOCKS_SERVER_FAILURE
35             SOCKS_CONNECTION_NOT_ALLOWED_BY_RUL
36             SOCKS_NETWORK_UNREACHABLE
37             SOCKS_HOST_UNREACHABLE
38             SOCKS_CONNECTION_REFUSED
39             SOCKS_TTL_EXPIRED
40             SOCKS_COMMAND_NOT_SUPPORTED
41             SOCKS_ADDRESS_TYPE_NOT_SUPPORTED
42             SOCKS_OKAY
43             SOCKS_FAILED
44             SOCKS_NO_IDENT
45             SOCKS_USER_MISMATCH
46             SOCKS_INCOMPLETE_AUTH
47             SOCKS_BAD_AUTH
48             SOCKS_SERVER_DENIES_AUTH_METHOD
49             SOCKS_MISSING_SOCKS_SERVER_NET_DATA
50             SOCKS_MISSING_PEER_NET_DATA
51             SOCKS_SOCKS_SERVER_UNAVAILABLE
52             SOCKS_TIMEOUT
53             SOCKS_UNSUPPORTED_PROTOCOL_VERSION
54             SOCKS_UNSUPPORTED_ADDRESS_TYPE
55             SOCKS_HOSTNAME_LOOKUP_FAILURE
56             );
57              
58             #
59             # Расширенные сообщения об ошибках
60             #
61 1         137 use constant SOCKS_MSG => {
62             1 => 'general SOCKS server failure', # SOCKS5
63             2 => 'connection not allowed by ruleset',
64             3 => 'network unreachable',
65             4 => 'host unreachable',
66             5 => 'connection refused',
67             6 => 'TTL expired',
68             7 => 'command not supported',
69             8 => 'address type not supported',
70             90 => 'okay', # SOCKS4
71             91 => 'failed',
72             92 => 'no ident',
73             93 => 'user mismatch',
74             100 => 'incomplete auth', # generic
75             101 => 'bad auth',
76             102 => 'server denies auth method',
77             202 => 'missing SOCKS server net data',
78             203 => 'missing peer net data',
79             204 => 'SOCKS server unavailable',
80             205 => 'timeout',
81             206 => 'unsupported protocol version',
82             207 => 'unsupported address type',
83             208 => 'hostname lookup failure'
84 1     1   8 };
  1         2  
85              
86             #
87             # Доствпные параметры конфигурации сокс серверов
88             #
89 1         12921 use constant SOCKS_PARAM => {
90             addr => 1,
91             port => 2,
92             user_id => 3,
93             user_pswd => 4,
94             protocol_version => 5,
95              
96             attempt_cnt => 6,
97             last_check_time => 7,
98              
99             cd => 8,
100             addr_type => 9,
101             listen_addr => 10,
102             listen_port => 11,
103             prev_user_id => 12
104 1     1   6 };
  1         3  
105              
106             #
107             # Коды возврвта сокс серверов.
108             #
109 0     0 0   sub SOCKS_GENERAL_SOCKS_SERVER_FAILURE { 1 };
110              
111 0     0 0   sub SOCKS_CONNECTION_NOT_ALLOWED_BY_RULESET { 2 };
112              
113 0     0 0   sub SOCKS_NETWORK_UNREACHABLE { 3 };
114              
115 0     0 0   sub SOCKS_HOST_UNREACHABLE { 4 };
116              
117 0     0 0   sub SOCKS_CONNECTION_REFUSED { 5 };
118              
119 0     0 0   sub SOCKS_TTL_EXPIRED { 6 };
120              
121 0     0 0   sub SOCKS_COMMAND_NOT_SUPPORTED { 7 };
122              
123 0     0 0   sub SOCKS_ADDRESS_TYPE_NOT_SUPPORTED { 8 };
124              
125 0     0 0   sub SOCKS_OKAY { 90 };
126              
127 0     0 0   sub SOCKS_FAILED { 91 };
128              
129 0     0 0   sub SOCKS_NO_IDENT { 92 };
130              
131 0     0 0   sub SOCKS_USER_MISMATCH { 93 };
132              
133 0     0 0   sub SOCKS_INCOMPLETE_AUTH { 100 };
134              
135 0     0 0   sub SOCKS_BAD_AUTH { 101 };
136              
137 0     0 0   sub SOCKS_SERVER_DENIES_AUTH_METHOD { 102 };
138              
139 0     0 0   sub SOCKS_MISSING_SOCKS_SERVER_NET_DATA { 202 };
140              
141 0     0 0   sub SOCKS_MISSING_PEER_NET_DATA { 203 };
142              
143 0     0 0   sub SOCKS_SOCKS_SERVER_UNAVAILABLE { 204 };
144              
145 0     0 0   sub SOCKS_TIMEOUT { 205 };
146              
147 0     0 0   sub SOCKS_UNSUPPORTED_PROTOCOL_VERSION { 206 };
148              
149 0     0 0   sub SOCKS_UNSUPPORTED_ADDRESS_TYPE { 207 };
150              
151 0     0 0   sub SOCKS_HOSTNAME_LOOKUP_FAILURE { 208 };
152              
153             #
154             # Конструктор...
155             #
156             # Возвращает ссылку на созданный обьект.
157             #
158             sub new {
159 0     0 1   my ( $class, %conf ) = @_;
160 0           my $self = bless {}, $class;
161 0           my $key;
162 0           local $_;
163              
164 0           my %def_conf = (
165             CHAIN_FILE => $ENV{HOME} . '/.sc.conf',
166             LOG_FILE => undef,
167             TIMEOUT => 180,
168              
169             CHAIN_FILE_DATA => undef,
170              
171             CHECK_DELAY => 24 * 3600,
172              
173             DEBUG => 0x09,
174             CHAIN_LEN => 10,
175             RANDOM_CHAIN => 0,
176              
177             RESTORE_TYPE => 0,
178             AUTO_SAVE => 0,
179             LOOP_CONNECT => 0x02, # 0x01 - проверка Socks 5
180             # 0x02 - проверка Socks 4
181             # 0x04 - проверка HTTP proxies
182             LOG_FH => undef,
183              
184             SYSLOG => 0,
185              
186             HTTP_CLIENT => 'Proxy chain client $Id: SC.pm,v 1.21 2009-11-14 10:18:40 gosha Exp $',
187              
188             LOG_SOCKS_FIELD => [ qw( addr port user_id protocol_version ) ]
189             );
190             #
191             # Инициализируем значения по умолчанию, и данные переданные в качестве
192             # параметров конфигурации...
193             #
194             # Внутри данные хранятся с префиксами CFG_
195             #
196 0           foreach $key ( keys %conf ) {
197 0           $_ = uc($key);
198 0 0         if ( exists $def_conf{$_} ) {
199 0           $self->{"CFG_$_"} = $conf{$key};
200             }
201             }
202 0           foreach $key ( keys %def_conf ) {
203 0 0         unless ( exists $self->{"CFG_$key"} ) {
204 0           $self->{"CFG_$key"} = $def_conf{$key};
205             }
206             }
207             #
208             # Готовим место для данных из файла конфигурации
209             #
210 0           undef $self->{CFG_CHAIN_DATA};
211              
212 0 0         unless ( defined $self->configure( 'TIMEOUT' ) ) {
213 0           $self->configure( TIMEOUT => 0 );
214             }
215             #
216             # Если установлена переменная SYSLOG то лог пишется через:
217             # *nix - syslogd,
218             # win32 - eventlog,
219             # иначе если возможно открываем LOG файл, так же можно напрямую передать
220             # дескриптор файла в LOG_FH, но тогда надо чтоб LOG_FILE был undef...
221             #
222 0 0         if ( $self->configure( 'SYSLOG' ) ) {
    0          
223 0 0         if ( $^O =~ /[Ww]in32/ ) {
224 0           require Win32::EventLog;
225              
226 0           $self->configure( 'LOG_FH' => Win32::EventLog->new( 'sc45', '' ) );
227              
228 0 0         unless ( defined $self->configure( 'LOG_FH' ) ) {
229 0           $self->configure( SYSLOG => 0 );
230 0           $self->log_error( "Cannot open EventLog:" . Win32::GetLastError() );
231             }
232             } else {
233 0           require Sys::Syslog;
234 0           import Sys::Syslog qw(:DEFAULT setlogsock );
235              
236 0 0 0       if ( $^O ne 'solaris' && $^O ne 'freebsd' &&
      0        
237 0           eval { &Sys::Syslog::_PATH_LOG() } ) {
238              
239 0 0         unless ( defined setlogsock( 'unix' ) ) {
240 0           $self->configure( SYSLOG => 0 );
241 0           $self->log_error("Can't `setlogsock' : $!");
242             }
243             }
244 0 0         if ( not defined openlog( 'sc45', 'cons,pid', 'daemon') ) {
245 0           $self->configure( SYSLOG => 0 );
246 0           $self->log_error("Can't `openlog' : $!");
247             }
248             }
249             } elsif ( defined $self->configure( 'LOG_FILE' ) ) {
250 0           $key = gensym;
251 0           $self->configure( LOG_FH => $key );
252 0 0         if ( open ( $key , '>>' . $self->configure( 'LOG_FILE' ) ) ) {
253 0           select((select($key), $| = 1)[0]);
254             } else {
255 0           $self->configure( LOG_FH => undef );
256 0           $self->log_error("Can't open file " . $self->configure('LOG_FILE') ." : $!");
257 0           ungensym $key;
258             }
259             }
260              
261 0           return $self;
262             }
263              
264             #
265             # Читает/устанавливает переменные из 'конфигурационного хеша'
266             # ( тот который CFG_... )
267             # Если задано 2 аргумента то устанавливается переменная
268             # с именем `первый аргумент' в значение `второй аргумент',
269             # и возвращает данное значение.
270             # Если задан один аргумент то возвращает переменную с
271             # именем `первый аргумент'...
272             #
273             sub configure {
274 0     0 1   my ( $self, $section, $var ) = @_;
275 0           local $_;
276              
277 0 0 0       unless ( exists $self->{ 'CFG_' . uc($section) } ) {
    0          
278 0           $self->log_error("Use unknown configuration variable : `$section'");
279 0           return undef;
280             } elsif ( uc($section) eq 'CHAIN_DATA' and (caller)[0] ne __PACKAGE__ ) {
281             #
282             # Маленькая кучка соломки, от изменения данных конфигурационного файла...
283             #
284 0           return $self->{ 'CFG_' . uc($section) };
285             } else {
286 0 0         if ( scalar @_ > 2 ) {
287 0           $self->{ 'CFG_' . uc($section) } = $var;
288             }
289 0           return $self->{ 'CFG_' . uc($section) };
290             }
291             }
292              
293             #
294             # Соединение с удаленной машиной через socks цепь.
295             #
296             # Ну это вроде как просто обертка для create_chain,
297             # плюс автоматическое чтение конфигов.
298             #
299             # Возвращает SOCKS_OK если все OK
300             #
301             sub connect {
302 0     0 1   my ( $self, $peer_host, $peer_port ) = @_;
303 0           my $rc;
304 0           local $_;
305              
306 0 0         unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
307 0 0         unless ( ( $rc = $self->read_chain_data ) == SOCKS_OKAY ) {
308 0           return $rc;
309             }
310 0 0         if ( $self->configure( 'AUTO_SAVE' ) ) {
311 0           $self->restore_cfg_data;
312             }
313             }
314              
315 0           $rc = $self->create_chain( $peer_host, $peer_port, 1 );
316              
317 0 0         if ( $self->configure( 'AUTO_SAVE' ) ) {
318 0           $self->dump_cfg_data;
319             }
320              
321 0           return $rc;
322             }
323              
324             #
325             # Установка связи для принятия соединений через socks цепь.
326             #
327             # Ну это вроде как просто обертка для create_chain,
328             # плюс автоматическое чтение конфигов.
329             #
330             # Возвращает SOCKS_OK если все OK
331             #
332             sub bind {
333 0     0 1   my ( $self, $peer_host, $peer_port ) = @_;
334 0           my $rc;
335 0           local $_;
336              
337 0 0         unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
338 0 0         unless ( ( $rc = $self->read_chain_data ) == SOCKS_OKAY ) {
339 0           return $rc;
340             }
341 0 0         if ( $self->configure( 'AUTO_SAVE' ) ) {
342 0           $self->restore_cfg_data;
343             }
344             }
345              
346 0           $rc = $self->create_chain( $peer_host, $peer_port, 2 );
347              
348 0 0         if ( $self->configure( 'AUTO_SAVE' ) ) {
349 0           $self->dump_cfg_data;
350             }
351              
352 0           return $rc;
353             }
354              
355             #
356             # Ждет соединение удаленной машины, через цепочку
357             # Socks серверов.
358             #
359             sub accept {
360 0     0 1   my $self = shift;
361 0           local $_;
362              
363 0 0         if ( $self->socks_param( 'protocol_version' ) == 4 ) {
    0          
364 0           return $self->get_resp4;
365             } elsif ( $self->socks_param('protocol_version') == 5 ) {
366 0           return $self->get_resp5;
367             } else {
368 0           return SOCKS_UNSUPPORTED_PROTOCOL_VERSION;
369             }
370             }
371              
372             #
373             # Возвращает сокет цепочки socks'ов
374             #
375             sub sh {
376 0     0 1   my $self = shift;
377              
378 0           return $self->{sock_h};
379             }
380              
381             #
382             # Закрывает соединение через socks цепь.
383             #
384             sub close {
385 0     0 1   my $self = shift;
386              
387 0           $self->sh->shutdown(2);
388              
389 0           $self->sh->close;
390              
391 0           undef $self->{sock_h};
392             }
393              
394             #
395             # Возвращает параметры сеанса работы последнего socks сервера
396             #
397             # Возможные параметры : listen_addr, listen_port, proxy_id, etc...
398             #
399             # при отсутствии $param возвращает ссылку на хеш со всеми
400             # имеющимися параметрами...
401             #
402             # Если не задан $id то берутся данные о последнем socks сервере цепочки...
403             #
404             # При установленном $value параметр param устанавливается в
405             # данное значение.
406             #
407             sub socks_param {
408 0     0 1   my ( $self, $param, $value, $id ) = @_;
409 0           local $_;
410              
411 0 0         unless ( defined $id ) {
412 0           $id = $self->{__last_socks};
413             }
414 0 0         unless ( defined $id ) {
    0          
    0          
    0          
    0          
415 0           return undef;
416             } elsif ( not defined $param ) {
417 0           return $self->configure( 'CHAIN_DATA' )->[ $id ];
418             } elsif ( not exists SOCKS_PARAM->{$param} ) {
419 0           $self->log_error("Use unknown socks parameter: `$param'");
420 0           return undef;
421             } elsif ( defined $value ) {
422 0           return $self->configure( 'CHAIN_DATA' )->[ $id ]->{$param} = $value;
423             } elsif ( not exists $self->configure( 'CHAIN_DATA' )->[ $id ]->{$param} ) {
424 0           return undef;
425             } else {
426 0           return $self->configure( 'CHAIN_DATA' )->[ $id ]->{$param};
427             }
428             }
429              
430             #
431             # Выводит текстовое сообщение в соответствующее коду возврата
432             # socks сервера.
433             #
434             sub socks_error {
435 0 0   0 0   if ( defined $_[0] ) {
436 0   0       return SOCKS_MSG->{$_[0]} || $_[0];
437             } else {
438 0           return undef;
439             }
440             }
441              
442             #
443             # Читает конфиг для модуля Net::SC. Формат:
444             #
445             # #host : port : uid : pswd : socks_proto
446             # 192.168.1.90 : 1080 : : : 5
447             #
448             # В качестве комментариев используется `#' в начале строки,
449             # пустые строки пропускаются. Данные записываются в массив
450             # CFG_CHAIN_DATA, который состоит из ссылок на хеш вида:
451             # 0 addr - имя socks сервера
452             # 1 port - порт socks сервера
453             # 2 user_id - пользователь socks
454             # 3 user_pswd - пароль пользователя socks
455             # 4 protocol_version - протокол socks сервера ( 4 или 5 или 0 (для http) )
456             # 5 last_check_time - время последней проверки сервера ( unixtime )
457             # 6 attempt_cnt - количество неудачных проверок ( 1 - все ок )
458             #
459             # Если все OK то возвращает SOCKS_OKAY
460             #
461             # С версии 1.17 также можно отказаться от чтения данных из конфигурационного
462             # файла ( new( CHAIN_FILE_DATA => [ 'str1', .., 'strN' ] ) )
463             # где strX - строка в формате описанном выше...
464             #
465             sub read_chain_data {
466 0     0 0   my $self = shift;
467 0           local $_;
468              
469 0           $self->configure( CHAIN_DATA => [] );
470              
471 0           my @data;
472              
473 0 0 0       if ( defined $self->configure( 'CHAIN_FILE_DATA' ) and
474             ref $self->configure( 'CHAIN_FILE_DATA' ) eq 'ARRAY' ) {
475              
476 0           @data = @{ $self->configure( 'CHAIN_FILE_DATA' ) };
  0            
477             } else {
478 0           my $sym = gensym;
479              
480 0 0         unless ( open($sym, '<' . $self->configure( 'CHAIN_FILE' ) ) ) {
481 0           $self->log_error("Can't open file " . $self->configure( 'CHAIN_FILE' ) ." : $!");
482 0           return SOCKS_FAILED;
483             }
484              
485 0           my_flock ( $sym, LOCK_SH );
486              
487 0           @data = <$sym>;
488              
489 0           CORE::close $sym;
490              
491 0           ungensym $sym;
492             }
493              
494 0           chomp @data;
495              
496 0           for my $line ( 0 .. $#data ) {
497              
498 0 0 0       next if $data[$line] =~ /^#/ || $data[$line] =~ /^\s*$/;
499              
500 0           my ( $socks_host, $socks_port,
501             $socks_user, $socks_pswd, $socks_proto ) = split( /\s*:\s*/, $data[$line] );
502              
503 0 0 0       unless ( defined $socks_host and length $socks_host ) {
504 0           $self->log_error( "Parse config: host name not defined [ $line ]" );
505 0           next;
506             }
507 0 0 0       unless ( defined $socks_port and $socks_port > 0 ) {
508 0           $self->log_error( "Parse config: bad number port [ $line ]" );
509 0           next;
510             }
511 0 0 0       unless ( defined $socks_proto and
      0        
      0        
512             length( $socks_proto ) and
513             ( $socks_proto == 4 or $socks_proto == 5 or $socks_proto == 0 ) ) {
514              
515 0           $socks_proto = 5;
516             }
517 0 0         unless ( defined $socks_user ) {
518 0           $socks_user = '';
519             }
520 0 0         unless ( defined $socks_pswd ) {
521 0           $socks_pswd = '';
522             }
523 0   0       push @{$self->configure( 'CHAIN_DATA' )}, {
  0   0        
524             addr => $socks_host,
525             port => $socks_port,
526             user_id => $socks_user || '',
527             user_pswd => $socks_pswd || '',
528             protocol_version => $socks_proto,
529             last_check_time => 0,
530             attempt_cnt => 0 };
531             }
532              
533 0 0         if ( scalar @{$self->configure( 'CHAIN_DATA' )} ) {
  0            
534 0           return SOCKS_OKAY;
535             } else {
536 0           $self->log_error('Configuration file is empty');
537 0           return SOCKS_FAILED;
538             }
539             }
540              
541             #
542             # Возвращает количество Socks серверов с `непросроченным'
543             # временем пользования, т.е. не `отдыхающих' по таймауту
544             # в связи с недоступностью
545             #
546             sub get_socks_count {
547 0     0 0   my $self = shift;
548 0           local $_;
549              
550 0 0         unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
551 0           return 0;
552             } else {
553 0           return scalar ( grep {
554 0           $self->socks_param( 'last_check_time', undef, $_ ) + ( $self->configure( 'CHECK_DELAY' ) * $self->socks_param( 'attempt_cnt', undef, $_ ) ) < time
555 0           } ( 0 .. $#{$self->configure( 'CHAIN_DATA' )} ) );
556             }
557             }
558              
559             #
560             # Помечает прокси с порядковым номером в конфиге $id как временно
561             # недоступный на CHECK_DELAY * КОЛИЧЕСТВО_НЕУДАЧНЫХ_КОННЕКТОВ секунд -
562             # если $status != SOCKS_OKAY, иначе очищает счетчик неудачных попыток...
563             #
564             # Если все нормально возвращает SOCKS_OKAY
565             #
566             sub mark_proxy {
567 0     0 0   my ( $self, $id, $status ) = @_;
568 0           local $_;
569              
570 0 0 0       unless ( defined $self->configure( 'CHAIN_DATA' ) and defined $id ) {
571 0 0         unless ( defined $id ) {
572 0           $self->log_error('Socks identifer not defined');
573             } else {
574 0           $self->log_error('Configuration data not defined...');
575             }
576 0           return SOCKS_FAILED;
577             }
578              
579 0 0         if ( $status == SOCKS_OKAY ) {
580 0           $self->socks_param( 'last_check_time', time - 1, $id );
581 0           $self->socks_param( 'attempt_cnt', 0, $id );
582             } else {
583 0           $self->socks_param( 'last_check_time', time, $id );
584 0   0       $self->socks_param( 'attempt_cnt', ( $self->socks_param( 'attempt_cnt' ) || 0 ) + 1, $id );
585             }
586              
587 0           return SOCKS_OKAY;
588             }
589              
590             #
591             # Сбрасывает текущее состояние данных о Socks серверах из конфига в
592             # хеш файл, на диске. Используется для последующего восстановления
593             # данных о `дохлых' серверах
594             #
595             # Если все нормально возвращает SOCKS_OKAY
596             #
597             sub dump_cfg_data {
598 0     0 0   my $self = shift;
599 0           my ( $sym, %hash, $id, $key );
600 0           local $_;
601              
602 0 0         unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
603 0           return SOCKS_OKAY;
604             }
605              
606 0 0         unless ( dbmopen ( %hash, $self->configure( 'CHAIN_FILE' ) . '-cache', 0600 ) ) {
607 0           $self->log_error("Can't create dump hash : $!");
608 0           return SOCKS_FAILED;
609             }
610 0           $sym = gensym;
611             #
612             # В качестве лок файла - используем текстовы конфигурационный файл
613             #
614 0 0         unless ( open( $sym, '<'. $self->configure( 'CHAIN_FILE' ) ) ) {
615 0           $self->log_error("Can't open file " . $self->configure( 'CHAIN_FILE' ) . " : $!");
616 0           dbmclose %hash;
617 0           return SOCKS_FAILED;
618             }
619 0           my_flock ( $sym, LOCK_EX );
620              
621 0           foreach $id ( 0 .. $#{$self->configure( 'CHAIN_DATA' )} ) {
  0            
622 0   0       $key = join( "\x00", $self->configure( 'CHAIN_DATA' )->[$id]->{addr},
      0        
623             $self->configure( 'CHAIN_DATA' )->[$id]->{port},
624             $self->configure( 'CHAIN_DATA' )->[$id]->{user_id} || '',
625             $self->configure( 'CHAIN_DATA' )->[$id]->{user_pswd} || '',
626             $self->configure( 'CHAIN_DATA' )->[$id]->{protocol_version}
627             );
628 0 0         unless ( defined $hash{$key} ) {
629 0           $hash{$key} = join( "\x00", $self->dump_cfg_filter( %{$self->configure( 'CHAIN_DATA' )->[$id]} ) );
  0            
630             }
631             }
632 0           dbmclose %hash;
633 0           CORE::close $sym;
634 0           ungensym $sym;
635              
636 0           return SOCKS_OKAY;
637             }
638              
639             #
640             # Читает данные записанные на диск процедурой dump_cfg_data
641             #
642             # Данные хранятся в .db файле с именем идентичным имени конфига + '-cache',
643             # но с добавленным расширением .db ( или .pag & .dir у кого как )
644             #
645             # Если все в порядке возвращает SOCKS_OKAY
646             #
647             sub restore_cfg_data {
648 0     0 0   my $self = shift;
649 0           my ( $sym, %hash, %hash2, $id, $key );
650 0           local $_;
651              
652 0 0         unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
653 0           return SOCKS_OKAY;
654             }
655              
656 0 0         unless ( dbmopen ( %hash, $self->configure( 'CHAIN_FILE' ) . '-cache', 0600 ) ) {
657 0           $self->log_error("Can't open damp hash : $!");
658 0           return SOCKS_FAILED;
659             }
660             #
661             # Пустой файл ( только что созданный )
662             #
663 0 0         if ( scalar keys %hash == 0 ) {
664 0           dbmclose %hash;
665 0           return SOCKS_OKAY;
666             }
667              
668 0           $sym = gensym;
669             #
670             # В качестве лок файла - используем текстовы конфигурационный файл
671             #
672 0 0         unless ( open( $sym, '<'. $self->configure( 'CHAIN_FILE' ) ) ) {
673 0           $self->log_error("Can't open file " . $self->configure( 'CHAIN_FILE' ) . " : $!");
674 0           dbmclose %hash;
675 0           return SOCKS_FAILED;
676             }
677 0           my_flock ( $sym, LOCK_SH );
678              
679             #
680             # Создаем ключи и соответствующие им индексы
681             #
682 0           foreach $id ( 0 .. $#{$self->configure( 'CHAIN_DATA' )} ) {
  0            
683 0   0       $key = join( "\x00", $self->configure( 'CHAIN_DATA' )->[$id]->{addr},
      0        
684             $self->configure( 'CHAIN_DATA' )->[$id]->{port},
685             $self->configure( 'CHAIN_DATA' )->[$id]->{user_id} || '',
686             $self->configure( 'CHAIN_DATA' )->[$id]->{user_pswd} || '',
687             $self->configure( 'CHAIN_DATA' )->[$id]->{protocol_version}
688             );
689             #
690             # Может быть несколько одинаковых серверов в конфиге...
691             #
692 0           push @{$hash2{ $key }}, $id;
  0            
693             }
694              
695             #
696             # Восстанавливаем значения из кэша
697             #
698 0           foreach $key ( keys %hash ) {
699 0 0 0       if ( not exists $hash2{$key} and $self->configure( 'RESTORE_TYPE' ) == 1 ) {
700 0           delete $hash{$key};
701             } else {
702 0           foreach $id ( @{$hash2{$key}} ) {
  0            
703 0           $self->configure( 'CHAIN_DATA' )->[$id] = { $self->dump_cfg_filter( split(/\x00/, $hash{$key}) ) };
704             }
705             }
706             }
707 0           dbmclose %hash;
708 0           CORE::close $sym;
709 0           ungensym $sym;
710              
711 0           return SOCKS_OKAY;
712             }
713              
714             #
715             # Проверяет данные подлежащие кешированию на наличие \x00 и \n,
716             # проверяет корректность используемых параметров для socks_param
717             #
718             # Разбор с помощью массива а не хеша сделан для возможности
719             # использования внешней сортировки ключей.
720             #
721             # Возвращает проверенный массив элементов
722             #
723             sub dump_cfg_filter {
724 0     0 0   my $self = shift;
725 0           my ( $key, $val, @param );
726 0           local $_;
727              
728 0           while ( defined ( $key = shift @_ ) ) {
729 0           $val = shift;
730              
731 0 0         next unless exists SOCKS_PARAM->{$key};
732              
733 0 0         unless ( defined $val ) {
734 0           push @param, $key, '';
735             } else {
736 0           $val =~ s#[\x00\n]##g;
737 0           push @param, $key, $val;
738             }
739             }
740 0           return @param;
741             }
742              
743             #
744             # Создает цепочку Socks серверов до/для хоста $peer_host и порта $peer_port
745             # $type - тип сервиса : 1 - connect
746             # : 2 - bind
747             #
748             # До использования данной процедуры должен быть прочитан конфигурационный
749             # файл.
750             #
751             # Если все Ok то возвращает SOCKS_OKAY
752             #
753             sub create_chain {
754 0     0 0   my ( $self, $peer_host, $peer_port, $type ) = @_;
755 0           my ( $host_ind, $rc );
756 0           my ( @hosts_id );
757 0           local $_;
758              
759 0 0 0       unless ( defined $self->configure( 'CHAIN_DATA' ) ) {
    0 0        
    0 0        
    0          
    0          
760 0           $self->log_error('Configuration data not defined...');
761 0           return SOCKS_FAILED;
762             } elsif ( not defined $peer_host or not defined $peer_port ) {
763 0           $self->log_error('Destination host or destination addr not defined...');
764 0           return SOCKS_MISSING_PEER_NET_DATA;
765             } elsif ( not defined $type or ( $type != 1 and $type != 2 ) ) {
766 0           return SOCKS_COMMAND_NOT_SUPPORTED;
767             } elsif ( $self->configure( 'CHAIN_LEN' ) < 1 ) {
768 0           $self->log_error('Length of chain very small...');
769 0           return SOCKS_FAILED;
770             } elsif ( $self->configure( 'RANDOM_CHAIN' ) > 0 ) {
771             #
772             # Random select proxies
773             #
774 0           @hosts_id = ( grep {
775 0           $self->socks_param( 'last_check_time', undef, $_ ) + ( $self->configure( 'CHECK_DELAY' ) * $self->socks_param( 'attempt_cnt', undef, $_ ) ) < time
776 0           } ( sort { rand(10) <=> rand(10) } ( 0 .. $#{$self->configure( 'CHAIN_DATA' )} ) ) );
  0            
777             } else {
778             #
779             # Select proxies in order definition
780             #
781 0           @hosts_id = ( grep {
782 0           $self->socks_param( 'last_check_time', undef, $_ ) + ( $self->configure( 'CHECK_DELAY' ) * $self->socks_param( 'attempt_cnt', undef, $_ ) ) < time
783 0           } ( 0 .. $#{$self->configure( 'CHAIN_DATA' )} ) );
784             }
785              
786             CHAIN:{
787 0 0         if ( defined $self->sh ) {
  0            
788 0           $self->close;
789             }
790              
791 0 0         if ( scalar @hosts_id < $self->configure( 'CHAIN_LEN' ) ) {
792 0           $self->log_error("Can't create socks chain, many servers not response...");
793 0           return SOCKS_FAILED;
794             }
795              
796             #
797             # Connect to first socks/HTTP proxy
798             #
799 0           $self->{__last_socks} = $hosts_id[0];
800 0 0         unless ( $self->first_connect == SOCKS_OKAY ) {
801 0           shift @hosts_id;
802 0           redo CHAIN;
803             }
804              
805 0           for ( $host_ind = 0; $host_ind <= $#hosts_id; $host_ind++ ) {
806             #
807             # Last proxy identifier...
808             #
809 0           $self->{__last_socks} = $hosts_id[$host_ind];
810              
811 0 0         last if $host_ind >= $self->configure( 'CHAIN_LEN' ) - 1;
812              
813             #
814             # Check proxies for connection ( create loop connection )
815             #
816 0 0 0       if ( $self->socks_param( 'protocol_version' ) == 5 and $self->configure( 'LOOP_CONNECT' ) & 0x01 ) {
    0 0        
    0 0        
817 0           $rc = $self->request_socks5( 1,
818             $self->socks_param('addr',undef,$hosts_id[$host_ind]),
819             $self->socks_param('port',undef,$hosts_id[$host_ind]));
820             } elsif ( $self->socks_param( 'protocol_version' ) == 0 and $self->configure( 'LOOP_CONNECT' ) & 0x04 ) {
821 0           $rc = $self->request_http( 1,
822             $self->socks_param('addr',undef,$hosts_id[$host_ind]),
823             $self->socks_param('port',undef,$hosts_id[$host_ind]));
824             } elsif ( $self->socks_param( 'protocol_version' ) == 4 and $self->configure( 'LOOP_CONNECT' ) & 0x02 ) {
825 0           $rc = $self->request_socks4( 1,
826             $self->socks_param('addr',undef,$hosts_id[$host_ind]),
827             $self->socks_param('port',undef,$hosts_id[$host_ind]));
828             } else {
829 0           $rc = SOCKS_OKAY;
830             }
831              
832             #
833             # LoopCheck failed
834             #
835 0 0         unless ( $rc == SOCKS_OKAY ) {
836 0           $self->mark_proxy( $self->{__last_socks}, $rc );
837              
838 0 0         if ( $self->configure( 'DEBUG' ) & 0x01 ) {
839 0           $self->debug( "Socks error[$rc]: " . $self->log_str( $self->{ __last_socks }) );
840             }
841 0 0         if ( $self->configure( 'DEBUG' ) & 0x08 ) {
842 0           $self->debug( ' [ ' . ( socks_error($rc) ) . ' ]' );
843             }
844 0           splice( @hosts_id, $host_ind, 1);
845              
846 0           redo CHAIN;
847             }
848              
849             #
850             # Create connection to next proxy server
851             #
852 0 0         if ( $self->socks_param( 'protocol_version' ) == 5 ) {
    0          
853 0           $rc = $self->request_socks5( 1,
854             $self->socks_param('addr',undef,$hosts_id[$host_ind+1]),
855             $self->socks_param('port',undef,$hosts_id[$host_ind+1]));
856             } elsif ( $self->socks_param( 'protocol_version' ) == 0 ) {
857 0           $rc = $self->request_http( 1,
858             $self->socks_param('addr',undef,$hosts_id[$host_ind+1]),
859             $self->socks_param('port',undef,$hosts_id[$host_ind+1]));
860             } else {
861 0           $rc = $self->request_socks4( 1,
862             $self->socks_param('addr',undef,$hosts_id[$host_ind+1]),
863             $self->socks_param('port',undef,$hosts_id[$host_ind+1]));
864             }
865              
866 0           $self->mark_proxy( $self->{__last_socks}, $rc );
867              
868 0 0         unless ( $rc == SOCKS_OKAY ) {
869 0 0         if ( $self->configure( 'DEBUG' ) & 0x01 ) {
870 0           $self->debug( "Socks error[$rc]: " . $self->log_str( $hosts_id[$host_ind+1] ) );
871             }
872 0 0         if ( $self->configure( 'DEBUG' ) & 0x08 ) {
873 0           $self->debug( ' [ ' . ( socks_error($rc) ) . ' ]' );
874             }
875 0           splice( @hosts_id, $host_ind+1, 1);
876 0           redo CHAIN;
877             }
878             }
879             }
880              
881 0 0         if ( $host_ind < $self->configure( 'CHAIN_LEN' ) - 1 ) {
882 0           $self->log_error("Can't create socks chain, many servers not response...");
883 0           return SOCKS_FAILED;
884             }
885              
886             #
887             # Create connectino to destination addr/port
888             #
889 0 0         if ( $self->socks_param( 'protocol_version' ) == 5 ) {
    0          
890 0           $rc = $self->request_socks5( $type, $peer_host, $peer_port );
891             } elsif ( $self->socks_param( 'protocol_version' ) == 0 ) {
892 0           $rc = $self->request_http( $type, $peer_host, $peer_port );
893             } else {
894 0           $rc = $self->request_socks4( $type, $peer_host, $peer_port );
895             }
896              
897 0           $self->mark_proxy( $self->{__last_socks}, $rc );
898              
899 0           return $rc;
900             }
901              
902             #
903             # Процедура блокировки файлов, с учетом проверки на возможности системы...
904             #
905             sub my_flock {
906 0     0 0   my ( $fh, $mode ) = @_;
907              
908 0 0         return 1 unless defined $Config::Config{d_flock};
909              
910 0           flock ( $fh, $mode );
911             }
912              
913             #
914             # Используется для отладки - при использовании SYSLOG'а сообщения пишутся
915             # в `debug', если syslog не пользуется то вызывается log_error...
916             #
917             sub debug {
918 0     0 0   my $self = shift;
919              
920             #
921             # syslogd
922             #
923 0 0 0       if ( ref $self and $self->configure( 'SYSLOG' ) and $^O !~ /[Ww]in32/ ) {
      0        
924 0           foreach ( @_ ) {
925 0 0         syslog( 'debug', '%s [ %d ]', $_, (caller)[-1] ) unless /^\s*$/;
926             }
927 0           return 1;
928             }
929             #
930             # Все остальное
931             #
932 0           return log_error( $self, @_);
933             }
934              
935             #
936             # Пишет сообщения об ошибках в log файл или передает syslogd.
937             # Можно было конечно Carp.pm пользовать, но привычка...
938             #
939              
940             sub log_error {
941 0     0 0   my $self = shift;
942 0           my $sym;
943 0           local $_;
944              
945 0 0 0       if ( ref $self and $self->configure( 'SYSLOG' ) and $^O !~ /[Ww]in32/ ) {
    0 0        
      0        
      0        
946             #
947             # syslogd
948             #
949 0           foreach ( @_ ) {
950 0 0         syslog( 'warning', '%s [ %d ]', $_, (caller)[-1] ) unless /^\s*$/;
951             }
952             } elsif ( ref $self and
953             $self->configure( 'SYSLOG' ) and
954             defined $self->configure( 'LOG_FH' ) ) {
955             #
956             # eventlog
957             #
958 0           $self->configure('LOG_FH')->Report( {
959             Category => 20,
960             EventType => Win32::EventLog::EVENTLOG_INFORMATION_TYPE(),
961             Strings => join( "\n", '', @_ ),
962             Data => '',
963             EventID => 0
964             } );
965             } else {
966             #
967             # write co STDERR
968             #
969 0 0         unless ( ref $self ) {
    0          
970 0           unshift @_, $self;
971 0           $sym = \*STDERR;
972             } elsif ( not defined ( $sym = $self->configure( 'LOG_FH' ) ) ) {
973 0           $sym = \*STDERR;
974             }
975 0           my_flock ( $sym, LOCK_EX );
976 0           foreach ( @_ ) {
977 0 0         printf $sym "%2.2d/%2.2d %2.2d:%2.2d:%2.2d [ %5.5d : %d ] : %s\n",(localtime(time))[3,4,2,1,0], $$, (caller)[-1], $_ unless /^\s*$/;
978             }
979 0           my_flock ( $sym, LOCK_UN );
980             }
981              
982 0           return 1;
983             }
984              
985             #
986             # Возвращает лог строку о соединении id...
987             #
988             sub log_str {
989 0     0 0   my ( $self, $id ) = @_;
990 0           my $str;
991 0           local $_;
992              
993 0           $str = '';
994              
995 0           foreach ( @{$self->configure('LOG_SOCKS_FIELD')} ) {
  0            
996 0 0         if ( defined $self->socks_param( $_, undef, $id ) ) {
997 0           $str .= ' : ' . $self->socks_param( $_, undef, $id );
998             } else {
999 0           $str .= ' : ';
1000             }
1001             }
1002              
1003 0           return substr $str, 3;
1004             }
1005              
1006             #
1007             # Производит коннект в `открытую' к первому socks серверу.
1008             #
1009             # Если все Ok возвращает SOCKS_OKAY
1010             #
1011             sub first_connect {
1012 0     0 0   my $self = shift;
1013 0           local $_;
1014              
1015 0           $self->{sock_h} = new IO::Socket::INET (
1016             PeerAddr => $self->socks_param( 'addr' ),
1017             PeerPort => $self->socks_param( 'port' ),
1018             Timeout => $self->configure( 'TimeOut' ),
1019             Proto => 'tcp'
1020             );
1021              
1022 0 0         unless ( defined $self->sh ) {
1023 0           $self->log_error( $@, "Can't create network socket... : $!" );
1024 0           return SOCKS_FAILED;
1025             }
1026              
1027 0           binmode $self->sh;
1028              
1029 0           $self->sh->autoflush(1);
1030              
1031 0           return SOCKS_OKAY;
1032             }
1033              
1034             #
1035             # Читает данные из сокета $fh1. $fh2 может быть как ссылкой на сокет
1036             # так и ссылкой на скаляр, для первого случая должно быть определено
1037             # значение $cnt. Если $fh2 ссылка на сокет ( файловый дескриптор )
1038             # то данные читаются до 'конца' из $fh1 и пишутся в $fh2
1039             #
1040             # Возвращает 0 - при таймауте
1041             # -1 - при чтении 0 байт
1042             # 1 - все Ok
1043             sub read_data {
1044 0     0 0   my ( $self, $fh1, $fh2, $cnt ) = @_;
1045 0           my ( $char, $rc, $rin );
1046 0           local $_ = 1;
1047              
1048 0 0         unless ( defined $cnt ) {
1049 0           $cnt = 0;
1050             }
1051              
1052 0           vec( $rin = '', fileno( $fh1 ), 1 ) = 1;
1053              
1054 0           $rc = eval {
1055 0     0     local $SIG{__DIE__} = sub { die @_ };
  0            
1056 0     0     local $SIG{PIPE} = sub { die "Pipe error\n" };
  0            
1057 0 0         if ( ref $fh2 eq 'SCALAR' ) {
1058 0           $$fh2 = '';
1059              
1060             #
1061             # HTTP proxies support
1062             #
1063 0           my $is_http_proxy = 0;
1064 0 0         $is_http_proxy = 1 if $self->socks_param('protocol_version') == 0;
1065              
1066             #
1067             # \r\n\r\n - for http proxies support
1068             #
1069 0 0 0       while( $_ && ($is_http_proxy ? $$fh2 !~ /\r\n\r\n$/ : $cnt-- ) ) {
1070 0 0         unless ( select( $rin, undef, undef, $self->configure( 'TimeOut' ) ) ) {
1071 0           die "Read data - timeout\n";
1072             }
1073 0           $_ = sysread( $fh1, $char, 1 );
1074 0           $$fh2 .= $char;
1075             }
1076             } else {
1077 0           while ( $_ ) {
1078 0 0         unless ( select( $rin, undef, undef, $self->configure( 'TimeOut' ) ) ) {
1079 0           die "Read data - timeout\n";
1080             }
1081 0           $_ = sysread( $fh1, $char, 1 );
1082 0           print $fh2 $char;
1083             }
1084             }
1085             };
1086              
1087 0 0         unless ( defined $rc ) {
1088 0 0         if ( $@ eq "Read data - timeout\n" ) {
1089 0           $self->log_error( 'Timeout...' );
1090 0           return 0;
1091             } else {
1092 0           $self->log_error( $@ );
1093 0           return 0;
1094             }
1095             }
1096              
1097 0 0 0       if ( $_ < 1 ) {
    0          
1098 0           return -1;
1099             } elsif ( ref $fh2 eq 'SCALAR' and $self->configure( 'DEBUG' ) & 0x02 ) {
1100 0           $self->debug('READ: ' . unpack('H*', $$fh2) );
1101             }
1102 0           return 1;
1103             }
1104              
1105             #
1106             # Пишет данные @data в сокет $fh ( сокет вроде как FH Socks сервера )
1107             #
1108             # Возвращает 1 - все Ok
1109             # 0 - какие то проблемы...
1110             #
1111             sub print_data {
1112 0     0 0   my ( $self, $fh, @data ) = @_;
1113 0           my $rc;
1114 0           local $_;
1115              
1116 0           $rc = eval {
1117 0     0     local $SIG{__DIE__} = sub { die @_ };
  0            
1118 0     0     local $SIG{PIPE} = sub { die "Pipe error\n" };
  0            
1119              
1120 0           print $fh @data;
1121             };
1122              
1123 0 0         unless ( defined $rc ) {
1124 0   0       $self->log_error( $@ || 'Print data error...' );
1125 0           return 0;
1126             } else {
1127 0 0         if ( $self->configure( 'DEBUG' ) & 0x04 ) {
1128 0           $self->debug('WRITE: ' . unpack('H*', join('', @data) ) );
1129             }
1130 0           return 1;
1131             }
1132             }
1133              
1134             #
1135             # Запрос к 4 соксу...
1136             #
1137             # req_num - тип запроса к socks серверу:
1138             # 1 - connect
1139             # 2 - bind
1140             #
1141             # Если все OK то возвращает SOCKS_OKAY
1142             #
1143             sub request_socks4 {
1144 0     0 0   my ( $self, $req_num, $peer_host, $peer_port ) = @_;
1145 0           local $_;
1146              
1147             #
1148             # Print debug message
1149             #
1150 0 0         if ( $self->configure( 'DEBUG' ) & 0x01 ) {
1151 0           $self->debug( 'Connect over socks4: ' . $self->log_str( $self->{__last_socks}) . ", to $peer_host:$peer_port" );
1152             }
1153              
1154 0           $self->print_data( $self->sh,
1155             pack ( 'CCn', 4, $req_num, $peer_port ),
1156             inet_aton( $peer_host ),
1157             $self->socks_param( 'user_id' ),
1158             pack 'x' );
1159              
1160 0           return $self->get_resp_socks4;
1161             }
1162              
1163             #
1164             # Запрос к 5 соксу...
1165             #
1166             # req_num - тип запроса к socks серверу:
1167             # 1 - connect
1168             # 2 - bind
1169             #
1170             # Если все OK то возвращает SOCKS_OKAY
1171             #
1172             sub request_socks5 {
1173 0     0 0   my ( $self, $req_num, $peer_host, $peer_port ) = @_;
1174 0           local $_;
1175              
1176             #
1177             # Print debug message
1178             #
1179 0 0         if ( $self->configure( 'DEBUG' ) & 0x01 ) {
1180 0           $self->debug( 'Connect over socks5: ' . $self->log_str( $self->{__last_socks}) . ", to $peer_host:$peer_port" );
1181             }
1182              
1183             #
1184             # Check socks5 auth
1185             #
1186 0 0         unless ( ( my $rc = $self->socks5_auth ) == SOCKS_OKAY ) {
1187 0           $self->close;
1188 0           return $rc;
1189             }
1190              
1191 0           my $addr_type;
1192              
1193 0 0         if ( $peer_host =~ /[a-zA-Z]/) { # FQDN?
1194 0           $addr_type = 3;
1195 0           $peer_host = pack('C',length( $peer_host )) . $peer_host;
1196             } else { # nope. Must be dotted-dec.
1197 0           $addr_type = 1;
1198 0           $peer_host = inet_aton( $peer_host );
1199             }
1200              
1201 0           $self->print_data( $self->sh,
1202             pack ( 'CCCC', 5, $req_num, 0, $addr_type ),
1203             $peer_host,
1204             pack( 'n', $peer_port ) );
1205              
1206 0           return $self->get_resp_socks5;
1207             }
1208              
1209             #
1210             # Request to HTTP proxies
1211             #
1212             # req_num - тип запроса к socks серверу:
1213             # 1 - connect
1214             #
1215             # Если все OK то возвращает SOCKS_OKAY
1216             #
1217             sub request_http {
1218 0     0 0   my ( $self, $req_num, $peer_host, $peer_port ) = @_;
1219 0           local $_;
1220              
1221             #
1222             # Print debug message
1223             #
1224 0 0         if ( $self->configure( 'DEBUG' ) & 0x01 ) {
1225 0           $self->debug( 'Connect over http: ' . $self->log_str( $self->{__last_socks}) . ", to $peer_host:$peer_port" );
1226             }
1227              
1228             #
1229             # bind command not support in http proxies...
1230             #
1231 0 0         if ( $req_num == 2 ) {
1232 0           return SOCKS_COMMAND_NOT_SUPPORTED;
1233             }
1234              
1235 0           my $CRLF = "\015\012";
1236              
1237 0           my @headers = ( "CONNECT $peer_host:$peer_port HTTP/1.1",
1238             'User-Agent: ' . $self->configure( 'Http_Client' ),
1239             'Proxy-Connection: keep-alive' );
1240              
1241             #
1242             # Basic authorization
1243             #
1244 0 0 0       if ( length ( $self->socks_param( 'user_id' ) ) > 0 and
1245             length ( $self->socks_param( 'user_pswd' ) ) > 0 ) {
1246              
1247 0           push @headers, 'Proxy-Authorization: ' .
1248             'Basic ' .
1249             MIME::Base64::encode(
1250             $self->socks_param( 'user_id' )
1251             . ':' .
1252             $self->socks_param( 'user_pswd' ), ''
1253             );
1254             }
1255              
1256 0           $self->print_data( $self->sh, join( $CRLF, @headers, '', '' ) );
1257              
1258 0           return $self->get_resp_http;
1259             }
1260              
1261             #
1262             # Аутентификация для 5 сокса...
1263             #
1264             # Если все OK то возвращает SOCKS_OKAY
1265             #
1266             sub socks5_auth {
1267 0     0 0   my ( $self ) = @_;
1268 0           my ( $status, $method, $received, $ver );
1269 0           local $_;
1270              
1271 0           $method = pack('C', 0);
1272 0           $status = 0;
1273 0 0 0       if ( length ( $self->socks_param( 'user_id' ) ) > 0 and
1274             length ( $self->socks_param( 'user_pswd' ) ) > 0 ) {
1275              
1276 0           $method .= pack('C', 2);
1277             }
1278              
1279 0           $self->print_data( $self->sh,
1280             pack ('CC', 5, length($method) ),
1281             $method );
1282              
1283 0           $received = '';
1284              
1285 0 0 0       if ( ! $self->read_data($self->sh, \$received, 2) or length($received) < 2 ) {
1286 0           return SOCKS_TIMEOUT;
1287             }
1288              
1289 0           ( $ver, $method ) = unpack 'CC', $received;
1290 0 0         if ( $ver != 5) {
1291 0           return SOCKS_UNSUPPORTED_PROTOCOL_VERSION
1292             }
1293 0 0         if ( $method == 255 ) {
1294 0           return SOCKS_SERVER_DENIES_AUTH_METHOD
1295             }
1296 0 0 0       if ( $method == 2 and (
    0 0        
1297             length ( $self->socks_param( 'user_id' ) ) == 0 or
1298             length ( $self->socks_param( 'user_pswd' ) ) == 0 ) ) {
1299              
1300 0           return SOCKS_INCOMPLETE_AUTH;
1301             } elsif ( $method == 2 ) {
1302 0           $self->print_data( $self->sh,
1303             pack ('CC', 1, length( $self->socks_param( 'user_id' ) ) ),
1304             $self->socks_param( 'user_id' ),
1305             pack ('C', length( $self->socks_param( 'user_pswd' ) )),
1306             $self->socks_param( 'user_pswd' ) );
1307              
1308 0 0 0       if ( ! $self->read_data($self->sh, \$received, 2) or length($received) < 2 ) {
1309 0           return SOCKS_TIMEOUT;
1310             }
1311 0           ( $ver, $status ) = unpack 'CC', $received;
1312             }
1313              
1314 0 0         if ( $status == 0 ) {
1315 0           return SOCKS_OKAY;
1316             } else {
1317 0           return SOCKS_BAD_AUTH;
1318             }
1319             }
1320              
1321             #
1322             # Ответ 4 сокса
1323             #
1324             # Если все OK то возвращает SOCKS_OKAY
1325             #
1326             sub get_resp_socks4 {
1327 0     0 0   my ( $self ) = @_;
1328 0           my $received;
1329 0           local $_;
1330              
1331 0           $received = '';
1332              
1333 0 0 0       if ( ! $self->read_data($self->sh, \$received, 8) or length($received) < 8 ) {
1334 0           return SOCKS_TIMEOUT;
1335             }
1336 0           ( $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{vn},
1337             $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{cd},
1338             $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{listen_port},
1339             ) = unpack 'CCn', $received;
1340              
1341 0           $self->socks_param( 'listen_addr', inet_ntoa( substr $received, 4 ) );
1342              
1343 0           return $self->socks_param( 'cd' );
1344             }
1345              
1346             #
1347             # Ответ 5 сокса
1348             #
1349             # Если все OK то возвращает SOCKS_OKAY
1350             #
1351             sub get_resp_socks5 {
1352 0     0 0   my ( $self ) = @_;
1353 0           my ( $received, $length );
1354 0           local $_;
1355              
1356 0           $received = '';
1357              
1358 0 0 0       if ( ! $self->read_data($self->sh, \$received, 4) or length($received) < 4 ) {
1359 0           return SOCKS_TIMEOUT;
1360             }
1361             (
1362 0           $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{vn},
1363             $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{cd},
1364             $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{socks_flag},
1365             $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ]->{addr_type}
1366             ) = unpack('CCCC', $received);
1367              
1368 0 0         if ( $self->socks_param( 'addr_type' ) == 3 ) { # FQDN
    0          
1369 0 0 0       if ( ! $self->read_data($self->sh, \$received, 1) or length($received) < 1 ) {
1370 0           return SOCKS_TIMEOUT;
1371             }
1372 0           $length = unpack('C', $received);
1373 0 0 0       if ( ! $self->read_data($self->sh, \$received, $length) or length($received) < $length ) {
1374 0           return SOCKS_TIMEOUT;
1375             }
1376 0 0         unless ( $received = gethostbyname( $received ) ) {
1377 0           return SOCKS_HOSTNAME_LOOKUP_FAILURE;
1378             }
1379             } elsif ( $self->socks_param( 'addr_type' ) == 1) { # IPv4 32 bit
1380 0 0 0       if ( ! $self->read_data($self->sh, \$received, 4) or length($received) < 4 ) {
1381 0           return SOCKS_TIMEOUT;
1382             }
1383             } else { # IPv6, others
1384 0           return SOCKS_UNSUPPORTED_ADDRESS_TYPE;
1385             }
1386              
1387 0           $self->socks_param( 'listen_addr', inet_ntoa( $received ) );
1388              
1389 0 0 0       if ( ! $self->read_data($self->sh, \$received, 2) or length($received) < 2 ) {
1390 0           return SOCKS_TIMEOUT;
1391             }
1392              
1393 0           $self->socks_param( 'listen_port', unpack('n', $received) );
1394              
1395 0 0         if ( $self->socks_param( 'cd' ) == 0 ) {
1396 0           $self->socks_param( 'cd', SOCKS_OKAY );
1397             }
1398              
1399 0           return $self->socks_param( 'cd' );
1400             }
1401              
1402             #
1403             # Ответ http прокси
1404             #
1405             # Если все OK то возвращает SOCKS_OKAY
1406             #
1407             sub get_resp_http {
1408 0     0 0   my ( $self ) = @_;
1409 0           local $_;
1410              
1411 0           my $received = '';
1412              
1413 0 0 0       if ( ! $self->read_data($self->sh, \$received, 0) or length($received) < 5 ) {
1414 0           return SOCKS_TIMEOUT;
1415             }
1416              
1417 0           $self->configure( 'CHAIN_DATA' )->[ $self->{__last_socks} ] = {};
1418              
1419 0 0         if ( $received =~ /HTTP\/\d+\.\d+\s+200/is ) {
    0          
1420 0           return SOCKS_OKAY;
1421             } elsif ( $received =~ /HTTP\/\d+\.\d+\s+(\d+)\s+([^\r\n]+)/is ) {
1422 0           SOCKS_MSG->{"-$1"} = $2;
1423 0           return "-$1";
1424             }
1425              
1426 0           return SOCKS_FAILED;
1427             }
1428              
1429             #
1430             # так..., почистим за собой...
1431             #
1432 0     0     sub DESTROY {};
1433              
1434             1;
1435              
1436             __END__