File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 176 226 77.8
branch 78 174 44.8
condition 15 24 62.5
subroutine 21 29 72.4
pod 9 10 90.0
total 299 463 64.5


line stmt bran cond sub pod time code
1             package Net::Ident;
2              
3 11     11   1254997 use strict;
  11         21  
  11         380  
4 11     11   90 use warnings;
  11         23  
  11         673  
5              
6 11     11   5216 use Socket;
  11         41819  
  11         5491  
7 11     11   4910 use FileHandle;
  11         110966  
  11         63  
8 11     11   3673 use Carp;
  11         22  
  11         713  
9 11     11   4228 use Errno;
  11         30674  
  11         6477  
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(ident_lookup lookup lookupFromInAddr);
14             our @EXPORT_FAIL;
15             our %EXPORT_TAGS;
16              
17             # EXPORT_HOOKS is a sortof Exporter extension. Whenever one of the keys
18             # of this hash is imported as a "tag", the corresponding function is called
19             our %EXPORT_HOOKS = (
20             'fh' => \&_add_fh_method,
21             'apache' => \&_add_apache_method,
22             'debug' => \&_set_debug,
23             );
24              
25             # provide import magic
26             sub _export_hooks () {
27 11     11   30 my ( $tag, $hook );
28 11         62 while ( ( $tag, $hook ) = each %EXPORT_HOOKS ) {
29 33         57 my $hookname = "_export_hook_$tag"; # pseudo-function name
30 33         73 $EXPORT_TAGS{$tag} = [$hookname];
31 33         56 push @EXPORT_OK, $hookname;
32 33         100 push @EXPORT_FAIL, $hookname;
33             }
34             }
35              
36             # put the export hooks in the standard Exporter structures
37             _export_hooks();
38              
39             # for compatibility mode, uncomment the next line @@ s/^#\s*// @@
40             # our @EXPORT = qw(_export_hook_fh);
41              
42             our $VERSION = "1.26";
43              
44             our $DEBUG = 0;
45             *STDDBG = *STDERR;
46              
47             sub _set_debug {
48 0     0   0 $DEBUG++;
49 0         0 print STDDBG "Debugging turned to level $DEBUG\n";
50             }
51              
52             # protocol number for tcp.
53             my $tcpproto = ( getprotobyname('tcp') )[2] || 6;
54              
55             # get identd port (default to 113).
56             my $identport = ( getservbyname( 'ident', 'tcp' ) )[2] || 113;
57              
58             # turn a filehandle passed as a string, or glob, into a ref
59             # private subroutine
60             sub _passfh ($) {
61 1     1   4 my ($fh) = @_;
62              
63             # test if $fh is a reference. if it's not, we need to process...
64 1 50       55 if ( !ref $fh ) {
65 0 0       0 print STDDBG "passed fh: $fh is not a reference\n" if $DEBUG;
66              
67             # check for fully qualified name
68 0 0       0 if ( $fh !~ /'|::/ ) {
69 0 0       0 print STDDBG "$fh is not fully qualified\n" if $DEBUG;
70              
71             # get our current package
72 0         0 my $mypkg = (caller)[0];
73 0 0       0 print STDDBG "We are package $mypkg\n" if $DEBUG;
74              
75             # search for calling package
76 0         0 my $depth = 1;
77 0         0 my $otherpkg;
78 0         0 $depth++ while ( ( $otherpkg = caller($depth) ) eq $mypkg );
79 0 0       0 print STDDBG "We are called from package $otherpkg\n" if $DEBUG;
80 0         0 $fh = "${otherpkg}::$fh";
81 0 0       0 print STDDBG "passed fh now fully qualified: $fh\n" if $DEBUG;
82             }
83              
84             # turn $fh into a reference to a $fh. we need to disable strict refs
85 11     11   164 no strict 'refs';
  11         20  
  11         13457  
86 0         0 $fh = \*{$fh};
  0         0  
87             }
88 1         5 $fh;
89             }
90              
91             # create a Net::Ident object, and perform a non-blocking connect()
92             # to the remote identd port.
93             # class method, constructor
94             sub new {
95 1     1 1 240937 my ( $class, $fh, $timeout ) = @_;
96 1         13 my ( $localaddr, $remoteaddr );
97              
98 1 50       62 print STDDBG "Net::Ident::new fh=$fh, timeout=" . ( defined $timeout ? $timeout : "" ) . "\n"
    50          
99             if $DEBUG > 1;
100              
101             # "try"
102 1         15 eval {
103 1 50       25 defined $fh or die "= fh undef\n";
104 1         39 $fh = _passfh($fh);
105              
106             # get information about this (the local) end of the connection. We
107             # assume that $fh is a connected socket of type SOCK_STREAM. If
108             # it isn't, you'll find out soon enough because one of these functions
109             # will return undef real fast.
110 1 50       36 $localaddr = getsockname($fh) or die "= getsockname failed: $!\n";
111              
112             # get information about remote end of connection
113 0 0       0 $remoteaddr = getpeername($fh) or die "= getpeername failed: $!\n";
114             };
115 1 50       45 if ( $@ =~ /^= (.*)/ ) {
    0          
116              
117             # here's the catch of the throw
118             # return false, try to preserve errno
119 1         20 local ($!);
120              
121             # we make a "fake" $self
122 1         74 my $self = {
123             'state' => 'error',
124             'error' => "Net::Ident::new: $1\n",
125             };
126 1 50       18 print STDDBG $self->{error} if $DEBUG;
127              
128             # return our blessed $self
129 1         24 return bless $self, $class;
130             }
131             elsif ($@) {
132              
133             # something else went wrong. barf up completely.
134 0         0 confess($@);
135             }
136              
137             # continue with the NewFromInAddr constructor
138 0         0 $class->newFromInAddr( $localaddr, $remoteaddr, $timeout );
139             }
140              
141             sub newFromInAddr {
142 1     1 1 2218 my ( $class, $localaddr, $remoteaddr, $timeout ) = @_;
143 1         2 my $self = {};
144              
145 0     0   0 print STDDBG "Net::Ident::newFromInAddr localaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
146 0     0   0 ->( sockaddr_in($localaddr) ), ", remoteaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
147 1 0       3 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    50          
148             if $DEBUG > 1;
149              
150 1         2 eval {
151             # unpack addresses and store in
152 1         2 my ( $localip, $remoteip );
153 1         3 ( $self->{localport}, $localip ) = sockaddr_in($localaddr);
154 1         8 ( $self->{remoteport}, $remoteip ) = sockaddr_in($remoteaddr);
155              
156             # create a local binding port. We cannot bind to INADDR_ANY, it has
157             # to be bind (bound?) to the same IP address as the connection we're
158             # interested in on machines with multiple IP addresses
159 1         6 my $localbind = sockaddr_in( 0, $localip );
160              
161             # store max time
162 1 50       6 $self->{maxtime} = defined($timeout) ? time + $timeout : undef;
163              
164             # create a remote connect point
165 1         3 my $identbind = sockaddr_in( $identport, $remoteip );
166              
167             # create a new FileHandle
168 1         12 $self->{fh} = FileHandle->new;
169              
170             # create a stream socket.
171 1 50       101 socket( $self->{fh}, PF_INET, SOCK_STREAM, $tcpproto )
172             or die "= socket failed: $!\n";
173              
174             # bind it to the same IP number as the local end of THESOCK
175 1 50       24 bind( $self->{fh}, $localbind ) or die "= bind failed: $!\n";
176              
177             # make it a non-blocking socket
178 0 0       0 if ( $^O ne 'MSWin32' ) {
179 0 0       0 $self->{fh}->blocking(0) or die "= set non-blocking failed: $!\n";
180             }
181              
182             # connect it to the remote identd port, this can return EINPROGRESS.
183             # for some reason, reading $! twice doesn't work as it should
184             connect( $self->{fh}, $identbind )
185             or $!{EINPROGRESS}
186 0 0 0     0 or die "= connect failed: $!\n";
187 0 0       0 $self->{fh}->blocking(0) if $^O eq 'MSWin32';
188             };
189 1 50       7 if ( $@ =~ /^= (.*)/ ) {
    0          
190              
191             # here's the catch of the throw
192             # return false, try to preserve errno
193 1         3 local ($!);
194 1         5 $self->{error} = "Net::Ident::new: $1\n";
195 1 50       2 print STDDBG $self->{error} if $DEBUG;
196              
197             # this deletes the FileHandle, which gets closed,
198             # so that might change errno
199 1         25 delete $self->{fh};
200              
201             # do NOT return, so the constructor always succeeds
202             }
203             elsif ($@) {
204              
205             # something else went wrong. barf up completely.
206 0         0 confess($@);
207             }
208              
209             # clear errno in case it contains EINPROGRESS
210 1         2 $! = 0;
211              
212             # mark the state of the connection, consistent with new()
213 1 50       4 $self->{state} = $self->{error} ? 'error' : 'connect';
214              
215             # return a blessed reference
216 1         32 bless $self, $class;
217             }
218              
219             # send the query to the remote daemon.
220             # object method
221             sub query {
222 37     37 1 1229931 my ($self) = @_;
223 37         117 my ( $wmask, $timeout, $emask, $fileno, $err, $query );
224              
225 37 50       196 print STDDBG "Net::Ident::query\n" if $DEBUG > 1;
226              
227             # bomb out if no fh
228 37 100       264 return undef unless $self->{fh};
229              
230             # "try"
231 29         120 eval {
232 29 100       224 $self->{state} eq 'connect' or die "= calling in the wrong order\n";
233 24         76 $fileno = fileno $self->{fh};
234              
235             # calculate the time left, abort if necessary. Note that $timeout
236             # is simply left undef if $self->{maxtime} is not defined
237 24 50 66     373 if ( defined( $self->{maxtime} )
238             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
239 0         0 die "= Connection timed out\n";
240             }
241              
242             # wait until the socket becomes writable.
243 24         90 $wmask = '';
244 24         168 vec( $wmask, $fileno, 1 ) = 1;
245 24 50       345 scalar select( undef, $wmask, $emask = $wmask, $timeout )
246             or die "= Connection timed out\n";
247              
248             # Check for errors via select (you never know)
249 24 50       79 vec( $emask, $fileno, 1 ) and die "= connection error: $!\n";
250              
251             # fh must be writable now
252 24 50       63 vec( $wmask, $fileno, 1 ) or die "= connection timed out or error: $!\n";
253              
254             # check for errors via getsockopt(SO_ERROR)
255 24         366 $err = getsockopt( $self->{fh}, SOL_SOCKET, SO_ERROR );
256 24 50 33     367 if ( !defined($err) || ( $! = unpack( 'L', $err ) ) ) {
257 0         0 die "= connect: $!\n";
258             }
259              
260             # create the query, based on the remote port and the local port
261 24         147 $query = "$self->{remoteport},$self->{localport}\r\n";
262              
263             # write the query. Ignore the chance that such a short
264             # write will be fragmented.
265 24 50       582 syswrite( $self->{fh}, $query, length $query ) == length $query
266             or die "= fragmented write on socket: $!\n";
267             };
268 29 100       157 if ( $@ =~ /^= (.*)/ ) {
    50          
269              
270             # here's the catch of the throw
271             # return false, try to preserve errno
272 5         56 local ($!);
273 5         38 $self->{error} = "Net::Ident::query: $1\n";
274 5 50       25 print STDDBG $self->{error} if $DEBUG;
275              
276             # this deletes the FileHandle, which gets closed,
277             # so that might change errno
278 5         117 delete $self->{fh};
279 5         38 return undef;
280             }
281             elsif ($@) {
282              
283             # something else went wrong. barf up completely.
284 0         0 confess($@);
285             }
286              
287             # initialise empty answer to prevent uninitialised value warning
288 24         154 $self->{answer} = '';
289              
290             # mark the state of the connection
291 24         112 $self->{state} = 'query';
292              
293             # return the same object on success
294 24         151 $self;
295             }
296              
297             # read data, if any, and check if it's enough.
298             # object method
299             sub ready {
300 38     38 1 1985201 my ( $self, $blocking ) = @_;
301 38         157 my ( $timeout, $rmask, $emask, $answer, $ret, $fileno );
302              
303 38 0       277 print STDDBG "Net::Ident::ready blocking=" . ( $blocking ? "true\n" : "false\n" ) if $DEBUG > 1;
    50          
304              
305             # exit immediately if ready returned 1 before.
306 38 100       246 if ( $self->{state} eq 'ready' ) {
307 7         54 return 1;
308             }
309              
310             # perform the query if not already done.
311 31 100       199 if ( $self->{state} ne 'query' ) {
312 3 100       305 $self->query or return undef;
313             }
314              
315             # bomb out if no fh
316 30 50       135 return undef unless $self->{fh};
317              
318             # "try"
319 30         211 $ret = eval {
320 30         181 $fileno = fileno $self->{fh};
321              
322             # while $blocking, but at least once...
323 30         65 do {
324             # calculate the time left, abort if necessary.
325 33 50 66     509 if ( defined( $self->{maxtime} )
326             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
327 0         0 die "= Timeout\n";
328             }
329              
330             # zero timeout for non-blocking
331 33 100       135 $timeout = 0 unless $blocking;
332              
333             # wait for something
334 33         92 $rmask = '';
335 33         247 vec( $rmask, $fileno, 1 ) = 1;
336 33 100       201409 if ( select( $rmask, undef, $emask = $rmask, $timeout ) ) {
337              
338             # something came in
339 30 50       129 vec( $emask, $fileno, 1 ) and die "= error while reading: $!\n";
340              
341             # check for incoming data
342 30 50       186 if ( vec( $rmask, $fileno, 1 ) ) {
343              
344             # try to read as much data as possible.
345 30         98 $answer = '';
346 30         837 my $nread = sysread( $self->{fh}, $answer, 1000 );
347 30 50       136 defined $nread
348             or die "= read returned error: $!\n";
349 30 100       131 $nread
350             or die "= remote end closed connection\n";
351              
352             # append incoming data to total received
353 26         175 $self->{answer} .= $answer;
354              
355             # check for max length
356 26 100       120 length( $self->{answer} ) <= 1000
357             or die "= remote daemon babbling too much\n";
358              
359             # if data contains a CR or LF, we are ready receiving.
360             # strip everything after and including the CR or LF and
361             # return success
362 25 100       545 if ( $self->{answer} =~ /[\n\r]/ ) {
363 19         168 $self->{answer} =~ s/[\n\r].*//s;
364 19 50       107 print STDDBG "Net::Ident::ready received: $self->{answer}\n"
365             if $DEBUG;
366              
367             # close the socket to the remote identd
368 19         502 close( $self->{fh} );
369 19         106 $self->{state} = 'ready';
370 19         80 return 1;
371             }
372             }
373             }
374             } while $blocking;
375              
376             # we don't block, but we didn't receive everything yet... return false.
377 6         24 0;
378             };
379 30 100       196 if ( $@ =~ /^= (.*)/ ) {
    50          
380              
381             # here's the catch of the throw
382             # return undef, try to preserve errno
383 5         37 local ($!);
384 5         41 $self->{error} = "Net::Ident::ready: $1\n";
385 5 50       20 print STDDBG $self->{error} if $DEBUG;
386              
387             # this deletes the FileHandle, which gets closed,
388             # so that might change errno
389 5         158 delete $self->{fh};
390 5         45 return undef;
391             }
392             elsif ($@) {
393              
394             # something else went wrong. barf up completely.
395 0         0 confess($@);
396             }
397              
398             # return the return value from the eval{}
399 25         170 $ret;
400             }
401              
402             # return the username from the rfc931 query return.
403             # object method
404             sub username {
405 26     26 1 248632 my ($self) = @_;
406             my (
407 26         55 $remoteport, $localport, $port1, $port2, $replytype, $reply, $opsys,
408             $userid, $error
409             );
410              
411 26 50       72 print STDDBG "Net::Ident::username\n" if $DEBUG > 1;
412              
413             # wait for data, if necessary.
414 26 50       67 return wantarray ? ( undef, undef, $self->{error} ) : undef
    100          
415             unless $self->ready(1);
416              
417             # parse the received string, split it into parts.
418 25         362 ( $port1, $port2, $replytype, $reply ) = ( $self->{answer} =~ /^\s*(\d+)\s*,\s*(\d+)\s*:\s*(ERROR|USERID)\s*:\s*(.*)$/ );
419              
420             # make sure the answer parsed properly, and that the ports are the same.
421 25 100 100     271 if ( !defined($reply)
      100        
422             || ( $self->{remoteport} != $port1 )
423             || ( $self->{localport} != $port2 ) ) {
424 4         10 $self->{error} = "Net::Ident::username couldn't parse reply or port mismatch\n";
425 4 50       7 print STDDBG $self->{error} if $DEBUG;
426 4 50       21 return wantarray ? ( undef, undef, $self->{error} ) : undef;
427             }
428              
429             # check for error return type
430 21 100       59 if ( $replytype eq "ERROR" ) {
431 8 50       28 print STDDBG "Net::Ident::username: lookup returned ERROR\n" if $DEBUG;
432 8         16 $userid = undef;
433 8         15 $opsys = "ERROR";
434 8         28 ( $error = $reply ) =~ s/\s+$//;
435             }
436             else {
437             # a normal reply, parse the opsys and userid. Note that the opsys may
438             # contain \ escaped colons, which is why the hairy regexp is necessary.
439 13 50       169 unless ( ( $opsys, $userid ) = ( $reply =~ /\s*((?:[^\\:]+|\\.)*):(.*)$/ ) ) {
440              
441             # didn't parse properly, abort.
442 0         0 $self->{error} = "Net::Ident::username: couldn't parse userid\n";
443 0 0       0 print STDDBG $self->{error} if $DEBUG;
444 0 0       0 return wantarray ? ( undef, undef, $self->{error} ) : undef;
445             }
446              
447             # remove trailing whitespace, except backwhacked whitespaces from opsys
448 13         140 $opsys =~ s/([^\\])\s+$/$1/;
449              
450             # un-backwhack opsys.
451 13         44 $opsys =~ s/\\(.)/$1/g;
452              
453             # in all cases is leading whitespace removed from the username, even
454             # though rfc1413 mentions that it shouldn't be done, current
455             # implementation practice dictates otherwise. What insane OS would
456             # use leading whitespace in usernames anyway...
457 13         53 $userid =~ s/^\s+//;
458              
459             # Test if opsys is "special": if it contains a charset definition,
460             # or if it is "OTHER". This means that it is rfc1413-like, instead
461             # of rfc931-like. (Why can't they make these RFCs non-conflicting??? ;)
462             # Note that while rfc1413 (the one that superseded rfc931) indicates
463             # that _any_ characters following the final colon are part of the
464             # username, current implementation practice inserts a space there,
465             # even "modern" identd daemons.
466             # Also, rfc931 specifically mentions escaping characters, while
467             # rfc1413 does not mention it (it isn't really necessary). Anyway,
468             # I'm going to remove trailing whitespace from userids, and I'm
469             # going to un-backwhack them, unless the opsys is "special".
470 13 100 100     96 unless ( $opsys =~ /,/ || $opsys eq 'OTHER' ) {
471              
472             # remove trailing whitespace, except backwhacked whitespaces.
473 11         50 $userid =~ s/([^\\])\s+$/$1/;
474              
475             # un-backwhack
476 11         24 $userid =~ s/\\(.)/$1/g;
477             }
478 13         19 $error = undef;
479             }
480              
481             # return the requested information, depending on whether in array context.
482 21 50       64 if ( $DEBUG > 1 ) {
483 0         0 print STDDBG "Net::Ident::username returns:\n";
484 0 0       0 print STDDBG "userid = ", defined $userid ? $userid : "", "\n";
485 0 0       0 print STDDBG "opsys = ", defined $opsys ? $opsys : "", "\n";
486 0 0       0 print STDDBG "error = ", defined $error ? $error : "", "\n";
487             }
488 21 100       178 wantarray ? ( $userid, $opsys, $error ) : $userid;
489             }
490              
491             # do the entire rfc931 lookup in one blow.
492             # exportable subroutine, not a method
493             sub lookup ($;$) {
494 0     0 1 0 my ( $fh, $timeout ) = @_;
495              
496 0 0       0 print STDDBG "Net::Ident::lookup fh=$fh, timeout=", defined $timeout ? $timeout : "", "\n"
    0          
497             if $DEBUG > 1;
498              
499 0         0 Net::Ident->new( $fh, $timeout )->username;
500             }
501              
502             # do the entire rfc931 lookup from two in_addr structs
503             sub lookupFromInAddr ($$;$) {
504 0     0 1 0 my ( $localaddr, $remoteaddr, $timeout ) = @_;
505              
506 0     0   0 print STDDBG "Net::Ident::lookupFromInAddr localaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
507 0     0   0 ->( sockaddr_in($localaddr) ), ", remoteaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
508 0 0       0 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    0          
509             if $DEBUG > 1;
510              
511 0         0 Net::Ident->newFromInAddr( $localaddr, $remoteaddr, $timeout )->username;
512             }
513              
514             # alias Net::Ident::ident_lookup to Net::Ident::lookup
515             sub ident_lookup ($;$);
516             *ident_lookup = \&lookup;
517              
518             # prevent "used only once" warning
519             ident_lookup(0) if 0;
520              
521             # get the FileHandle ref from the object, to be used in an external select().
522             # object method
523             sub getfh ($) {
524 5     5 1 6219 my ($self) = @_;
525              
526 5         52 $self->{fh};
527             }
528              
529             # get the last error message.
530             # object method
531             sub geterror ($) {
532 10     10 1 13997 my ($self) = @_;
533              
534 10         91 $self->{error};
535             }
536              
537             # this is called whenever a function in @EXPORT_FAIL is imported.
538             # simply calls the installed export hooks from %EXPORT_HOOKS, or
539             # passes along the export_fail up the inheritance chain
540             sub export_fail {
541 3     3 0 423 my $pkg = shift;
542 3         5 my $fail;
543             my @other;
544 3         6 foreach $fail (@_) {
545 4 50 33     39 if ( $fail =~ /^_export_hook_(.*)$/ && $EXPORT_HOOKS{$1} ) {
546 4         12 &{ $EXPORT_HOOKS{$1} };
  4         10  
547             }
548             else {
549 0         0 push @other, $fail;
550             }
551             }
552 3 50       8 if (@other) {
553 0         0 @other = $pkg->SUPER::export_fail(@other);
554             }
555 3         189 @other;
556             }
557              
558             # add lookup method for FileHandle objects. Note that this relies on the
559             # use FileHandle;
560             sub _add_fh_method {
561              
562             # determine package to add method to
563 3 50   3   17 my $pkg = grep( /^IO::/, @FileHandle::ISA ) ? "IO::Handle" : "FileHandle";
564              
565             # insert method in package. Arguments are already OK for std lookup
566             # turn off strict refs for this glob-mangling trick
567 11     11   26368 no strict 'refs';
  11         24  
  11         1605  
568 3         5 *{"${pkg}::ident_lookup"} = \&lookup;
  3         27  
569              
570 3 50       13 print STDDBG "Added ${pkg}::ident_lookup method\n" if $DEBUG;
571             }
572              
573             sub _add_apache_method {
574              
575             # add method to Apache::Connection class
576 11     11   71 no strict 'refs';
  11         16  
  11         2192  
577 1         5 *{"Apache::Connection::ident_lookup"} = sub {
578 0     0   0 my ( $self, $timeout ) = @_;
579              
580 0 0       0 print STDDBG "Apache::Connection::ident_lookup self=$self, ", "timeout=", defined $timeout ? $timeout : "", "\n"
    0          
581             if $DEBUG > 1;
582 0         0 lookupFromInAddr( $self->local_addr, $self->remote_addr, $timeout );
583 1     1   4 };
584              
585 1 50       3 print STDDBG "Added Apache::Connection::ident_lookup method\n" if $DEBUG;
586             }
587              
588             1;
589              
590             __END__