File Coverage

blib/lib/Net/ProxyMod.pm
Criterion Covered Total %
statement 18 149 12.0
branch 0 46 0.0
condition 0 6 0.0
subroutine 6 14 42.8
pod 2 2 100.0
total 26 217 11.9


line stmt bran cond sub pod time code
1             #
2             # Copyright (c) 2001,2003,2004 Stephanie Wehner <_@r4k.net>
3             # All rights reserved.
4             #
5             # Redistribution and use in source and binary forms, with or without
6             # modification, are permitted provided that the following conditions
7             # are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright
10             # notice, this list of conditions and the following disclaimer.
11             # 2. Redistributions in binary form must reproduce the above copyright
12             # notice, this list of conditions and the following disclaimer in the
13             # documentation and/or other materials provided with the distribution.
14             # 3. Neither the name of the company ITSX nor the names of its contributors
15             # may be used to endorse or promote products derived from this software
16             # without specific prior written permission.
17             #
18             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21             # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22             # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23             # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24             # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25             # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26             # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27             # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28             # SUCH DAMAGE.
29             #
30             # Small tcp proxy package for packet(payload) alteration/debugging.
31             #
32             # $Id: ProxyMod.pm,v 1.3 2003/09/23 15:07:32 _ Exp $
33             #
34              
35             package Net::ProxyMod;
36              
37 1     1   711 use strict;
  1         1  
  1         34  
38 1     1   6 use vars qw($VERSION);
  1         2  
  1         65  
39 1     1   890 use POSIX ":sys_wait_h";
  1         7805  
  1         7  
40 1     1   1282 use Carp;
  1         2  
  1         61  
41              
42 1     1   959 use IO::Socket;
  1         35236  
  1         6  
43 1     1   7447 use IO::Select;
  1         1565  
  1         1850  
44              
45             $VERSION = '0.04';
46              
47             my $do_debug = 0;
48              
49             my @parnames = qw/
50             -local_host
51             -local_port
52             -remote_host
53             -remote_port
54             -debug
55             /;
56              
57              
58             # create a new proxy object
59             sub new
60             {
61 0     0 1   my($caller) = shift;
62 0   0       my($class) = ref($caller) || $caller;
63              
64 0           my $self = {};
65              
66 0           bless($self, $class);
67              
68             # set defaults, for form
69 0           $self->{-mode} = "forking";
70              
71             # initialize the proxy object
72 0           $self->_init(@_);
73              
74 0           return($self);
75             }
76              
77              
78             # initialize
79             sub _init
80             {
81 0     0     my $self = shift;
82              
83             # care for unnamed and named params
84 0           my $i = 0;
85 0   0       while (($_[0] !~ /^-/) && ($i < 5)) {
86 0           $self->{$parnames[$i]} = shift;
87 0           $i++;
88             }
89 0           my %named = @_;
90 0           $self->{$_} = $named{$_} for keys(%named);
91              
92             # check if we need root
93 0 0         if($self->{-local_port} < 1024 ) {
94 0           croak "Need to be root to create a socket with port < 1024.";
95             }
96              
97 0           $do_debug = $self->{-debug};
98              
99             # setup the proxy socket
100 0 0         $self->{MAIN} = IO::Socket::INET->new(
101             LocalAddr => $self->{-local_host},
102             LocalPort => $self->{-local_port},
103             Listen => Socket::SOMAXCONN,
104             ReuseAddr => 1,
105             Proto => 'tcp',
106             ) or croak "Can't open socket: $!\n";
107              
108 0           _debug(
109             "Started server at ",
110             $self->{-local_host},
111             ":",
112             $self->{-local_port},
113             );
114              
115             # set autoflush
116 0           $self->{MAIN}->autoflush(1);
117              
118 0 0         if ( $self->{-mode} eq 'nonforking' ) {
119              
120 0           require Tie::RefHash;
121              
122             # setup a lookup hash
123 0 0         tie my %lookup, 'Tie::RefHash'
124             or croak "couldn't tie lookup hash";
125 0           $self->{LOOKUP} = \%lookup;
126              
127             # setup IO::Select object
128 0           $self->{ALLSOCKS} = IO::Select->new;
129 0           $self->{ALLSOCKS}->add($self->{MAIN});
130             }
131              
132 0           return;
133             }
134              
135              
136             # handle client connections (this is similar to fwdport
137             # in the perl coobook in some ways)
138             sub get_conn
139             {
140 0     0 1   my $self = shift;
141 0           my($infunc, $outfunc) = @_;
142              
143 0 0         my $func = $self->{-mode} eq 'nonforking' ?
144             \&_nonforking
145             :
146             \&_forking;
147              
148 0           $func->($self, $infunc, $outfunc);
149             }
150              
151              
152             # handle forked connections
153             sub _forking
154             {
155 0     0     my $self = shift;
156 0           my($infunc, $outfunc) = @_;
157              
158 0           my($client, $remote, $pid, $buf);
159              
160 0           _debug("Forking server started");
161              
162             # reap children
163 0           $SIG{CHLD} = \&_REAPER;
164              
165             # get connection
166 0           while($client = $self->{MAIN}->accept()) {
167              
168 0           _debug(
169             "Connect from ",
170             $client->peerhost(),
171             ':',
172             $client->peerport(),
173             );
174              
175             # connect to remote host
176 0           $remote = $self->_make_conn($client);
177 0 0         next unless $remote;
178 0           $remote->autoflush(1);
179 0           _debug(
180             "Remote connection to ",
181             $remote->peerhost(),
182             ":",
183             $remote->peerport()
184             );
185              
186 0           $pid = fork();
187 0 0         unless ( defined($pid) ) {
188 0           carp "Cannot fork: $!\n";
189 0           close($client);
190 0           close($remote);
191 0           next;
192             }
193              
194 0 0         if($pid) { # mum
195 0           close($client);
196 0           close($remote);
197 0           next;
198             }
199              
200             # child
201 0           close($self->{MAIN});
202              
203             # create a twin handling the other side
204 0           $pid = fork();
205 0 0         unless ( defined($pid) ) {
206 0           croak "Cannot fork: $!\n";
207             }
208              
209 0 0         if ( $pid ) { # mum # 2
210              
211 0           select($client);
212 0           $| = 1;
213              
214             # shovel data from remote to client
215 0           while($remote->sysread($buf, 1024, length($buf))) {
216 0           print $infunc->($buf);
217             }
218              
219 0           select(STDOUT);
220 0           _debug(
221             "Session closed from remote side ",
222             $remote->peerhost(),
223             ":",
224             $remote->peerport()
225             );
226              
227             # done, kill child
228 0           kill('TERM', $pid);
229              
230             } else {
231              
232 0           select($remote);
233             # turn off buffering
234 0           $| = 1;
235              
236             # shovel data from client to remote
237 0           while($client->sysread($buf, 1024, length($buf))) {
238 0           print $outfunc->($buf);
239             }
240              
241 0           select(STDOUT);
242 0           _debug(
243             "Session closed from client side ",
244             $client->peerhost(),
245             ":",
246             $client->peerport()
247             );
248              
249             # kill parent, since done
250 0           kill('TERM', getppid());
251             }
252 0           $remote->close();
253 0           $client->close();
254              
255             } # while
256              
257 0           return;
258             }
259              
260              
261             sub _nonforking
262             {
263 0     0     my $self = shift;
264 0           my($infunc, $outfunc) = @_;
265              
266 0           _debug("Nonforking server started");
267              
268 0           while (1) {
269              
270 0           my @readable = $self->{ALLSOCKS}->can_read(0.05);
271              
272 0           foreach my $sock ( @readable ) {
273              
274 0 0         if ( $sock == $self->{MAIN} ) {
    0          
275              
276             # accepting local connection
277 0           my $client = $sock->accept();
278 0           $client->autoflush(1);
279 0           _debug(
280             "Connect from ",
281             $client->peerhost(),
282             ":",
283             $client->peerport()
284             );
285              
286             # opening remote connection
287 0           my $remote = $self->_make_conn($client);
288 0 0         next unless $remote;
289 0           $remote->autoflush(1);
290 0           _debug(
291             "Remote connection to ",
292             $remote->peerhost(),
293             ":",
294             $remote->peerport()
295             );
296              
297             # adding both sockets to IO::Select object
298 0           $self->{ALLSOCKS}->add($client);
299 0           $self->{ALLSOCKS}->add($remote);
300              
301             # adding both sockets to socket hash
302             # pointing to each other
303 0           $self->{LOOKUP}{$client} = {
304             sock => $remote,
305             type => 'remote',
306             };
307 0           $self->{LOOKUP}{$remote} = {
308             sock => $client,
309             type => 'client',
310             }
311              
312             } elsif ( defined($self->{LOOKUP}{$sock}) ) {
313              
314 0           my $dest = $self->{LOOKUP}{$sock}{sock};
315 0           my $type = $self->{LOOKUP}{$sock}{type};
316 0 0         my $rtype = $type eq 'client' ? 'remote' : 'client';
317 0 0         my $func = $type eq 'client' ? $infunc : $outfunc;
318              
319 0           my $buf;
320 0           my $sel = IO::Select->new($sock);
321 0           while ( $sel->can_read(0.05) ) {
322 0 0         last unless $sock->sysread($buf, 1024, length($buf));
323             }
324            
325 0           my $err = 0;
326 0 0         if ( !$buf ) {
    0          
327 0           _debug(
328             "Session closed from $rtype side ",
329             $sock->peerhost(),
330             ":",
331             $sock->peerport()
332             );
333 0           $err = 1;
334             } elsif ( ! print $dest $func->($buf) ) {
335 0           _debug(
336             "Session closed from $type side ",
337             $sock->peerhost(),
338             ":",
339             $sock->peerport()
340             );
341 0           $err = 1;
342             }
343              
344             # remove sockets on error
345 0 0         if ( $err ) {
346 0           $self->{ALLSOCKS}->remove($sock, $dest);
347 0           delete($self->{LOOKUP}{$sock});
348 0           delete($self->{LOOKUP}{$dest});
349 0           $sock->close();
350 0           $dest->close();
351             }
352              
353             } else {
354              
355             # socked already closed?
356 0 0         next unless $sock->connected();
357              
358             # should never happen :-)
359 0           carp "unknown connection ", $sock->peerhost(),
360             ":", $sock->peerport();
361             }
362             }
363             }
364             }
365              
366              
367             # reap kids
368             sub _REAPER
369             {
370              
371 0     0     my($child);
372              
373 0           while (($child = waitpid(-1,WNOHANG)) > 0) {
374             }
375              
376 0           $SIG{CHLD} = \&_REAPER;
377             }
378              
379              
380             #
381             # Make a connection to the requested destination
382             #
383              
384             sub _make_conn
385             {
386 0     0     my $self = shift;
387 0           my($sock) = @_;
388              
389             # see if this should be transparent proxying or not
390              
391 0           my($dhost, $dport);
392 0 0         if($self->{-remote_host}) {
393 0           $dhost = $self->{-remote_host};
394 0           $dport = $self->{-remote_port};
395             } else {
396             # find the actual destination
397 0           $dport = $sock->sockport();
398 0           $dhost = $sock->sockhost();
399             }
400              
401 0           _debug("Connecting to ", $dhost, ":", $dport);
402              
403 0 0         my $newsock = IO::Socket::INET->new(
404             PeerAddr => $dhost,
405             PeerPort => $dport,
406             Proto => 'tcp',
407             ) or carp "Can't connect to $dhost:$dport: $!\n";
408              
409 0           return($newsock);
410             }
411              
412              
413             #
414             # print debug info if desired
415             #
416              
417             sub _debug
418             {
419 0     0     my(@strings) = @_;
420              
421 0 0         if ($do_debug) {
422 0           print @strings, "\n";
423             }
424             }
425              
426              
427             1;
428             __END__