File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 180 226 79.6
branch 78 170 45.8
condition 16 24 66.6
subroutine 23 30 76.6
pod 9 10 90.0
total 306 460 66.5


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