File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 180 228 78.9
branch 78 172 45.3
condition 16 27 59.2
subroutine 23 30 76.6
pod 9 10 90.0
total 306 467 65.5


line stmt bran cond sub pod time code
1             package Net::Ident;
2              
3 12     12   1230043 use 5.010;
  12         42  
4 12     12   78 use strict;
  12         84  
  12         507  
5 12     12   55 use warnings;
  12         19  
  12         609  
6              
7 12     12   6048 use Socket;
  12         46727  
  12         5485  
8 12     12   5223 use FileHandle;
  12         116781  
  12         74  
9 12     12   3868 use Carp;
  12         20  
  12         785  
10 12     12   6014 use Errno;
  12         29514  
  12         6522  
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   22 my ( $tag, $hook );
29 12         67 while ( ( $tag, $hook ) = each %EXPORT_HOOKS ) {
30 36         66 my $hookname = "_export_hook_$tag"; # pseudo-function name
31 36         76 $EXPORT_TAGS{$tag} = [$hookname];
32 36         57 push @EXPORT_OK, $hookname;
33 36         142 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.28";
44              
45             our $DEBUG = 0;
46             *STDDBG = *STDERR;
47              
48             sub _set_debug {
49 6     6   8027 $DEBUG++;
50 6         380 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   13 my ($fh) = @_;
63              
64             # test if $fh is a reference. if it's not, we need to process...
65 1 50       17 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   110 no strict 'refs';
  12         36  
  12         14310  
87 0         0 $fh = \*{$fh};
  0         0  
88             }
89 1         9 $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 171463 my ( $class, $fh, $timeout ) = @_;
97 1         27 my ( $localaddr, $remoteaddr );
98              
99 1 50       66 print STDDBG "Net::Ident::new fh=$fh, timeout=" . ( defined $timeout ? $timeout : "" ) . "\n"
    50          
100             if $DEBUG > 1;
101              
102             # "try"
103 1         10 eval {
104 1 50       26 defined $fh or die "= fh undef\n";
105 1         25 $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       23 $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       47 if ( $@ =~ /^= (.*)/ ) {
    0          
117              
118             # here's the catch of the throw
119             # return false, try to preserve errno
120 1         19 local ($!);
121              
122             # we make a "fake" $self
123 1         44 my $self = {
124             'state' => 'error',
125             'error' => "Net::Ident::new: $1\n",
126             };
127 1 50       14 print STDDBG $self->{error} if $DEBUG;
128              
129             # return our blessed $self
130 1         22 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 2205 my ( $class, $localaddr, $remoteaddr, $timeout ) = @_;
144 1         2 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       4 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    50          
149             if $DEBUG > 1;
150              
151 1         2 eval {
152             # unpack addresses and store in
153 1         1 my ( $localip, $remoteip );
154 1         3 ( $self->{localport}, $localip ) = sockaddr_in($localaddr);
155 1         9 ( $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         5 my $localbind = sockaddr_in( 0, $localip );
161              
162             # store max time
163 1 50       6 $self->{maxtime} = defined($timeout) ? time + $timeout : undef;
164              
165             # create a remote connect point
166 1         3 my $identbind = sockaddr_in( $identport, $remoteip );
167              
168             # create a new FileHandle
169 1         12 $self->{fh} = FileHandle->new;
170              
171             # create a stream socket.
172 1 50       107 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       29 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       6 if ( $@ =~ /^= (.*)/ ) {
    0          
191              
192             # here's the catch of the throw
193             # return false, try to preserve errno
194 1         4 local ($!);
195 1         4 $self->{error} = "Net::Ident::new: $1\n";
196 1 50       3 print STDDBG $self->{error} if $DEBUG;
197              
198             # this deletes the FileHandle, which gets closed,
199             # so that might change errno
200 1         28 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         2 $! = 0;
212              
213             # mark the state of the connection, consistent with new()
214 1 50       4 $self->{state} = $self->{error} ? 'error' : 'connect';
215              
216             # return a blessed reference
217 1         6 bless $self, $class;
218             }
219              
220             # send the query to the remote daemon.
221             # object method
222             sub query {
223 39     39 1 905359 my ($self) = @_;
224 39         201 my ( $wmask, $timeout, $fileno, $err, $query );
225              
226 39 50       180 print STDDBG "Net::Ident::query\n" if $DEBUG > 1;
227              
228             # bomb out if no fh
229 39 100       212 return undef unless $self->{fh};
230              
231             # "try"
232 31         93 eval {
233 31 100       150 $self->{state} eq 'connect' or die "= calling in the wrong order\n";
234 26         61 $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 26 50 66     292 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 26         61 $wmask = '';
245 26         144 vec( $wmask, $fileno, 1 ) = 1;
246 26 50       251 scalar select( undef, $wmask, undef, $timeout )
247             or die "= Connection timed out\n";
248              
249             # fh must be writable now
250 26 50       57 vec( $wmask, $fileno, 1 ) or die "= connection timed out or error: $!\n";
251              
252             # check for errors via getsockopt(SO_ERROR)
253 26         307 $err = getsockopt( $self->{fh}, SOL_SOCKET, SO_ERROR );
254 26 50 33     278 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 26         141 $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 26 50       475 syswrite( $self->{fh}, $query, length $query ) == length $query
264             or die "= fragmented write on socket: $!\n";
265             };
266 31 100       131 if ( $@ =~ /^= (.*)/ ) {
    50          
267              
268             # here's the catch of the throw
269             # return false, try to preserve errno
270 5         34 local ($!);
271 5         23 $self->{error} = "Net::Ident::query: $1\n";
272 5 50       20 print STDDBG $self->{error} if $DEBUG;
273              
274             # this deletes the FileHandle, which gets closed,
275             # so that might change errno
276 5         70 delete $self->{fh};
277 5         22 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 26         140 $self->{answer} = '';
287              
288             # mark the state of the connection
289 26         116 $self->{state} = 'query';
290              
291             # return the same object on success
292 26         106 $self;
293             }
294              
295             # read data, if any, and check if it's enough.
296             # object method
297             sub ready {
298 40     40 1 1862131 my ( $self, $blocking ) = @_;
299 40         126 my ( $timeout, $rmask, $answer, $ret, $fileno );
300              
301 40 0       227 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 40 100       327 if ( $self->{state} eq 'ready' ) {
305 7         75 return 1;
306             }
307              
308             # perform the query if not already done.
309 33 100       206 if ( $self->{state} ne 'query' ) {
310 3 100       393 $self->query or return undef;
311             }
312              
313             # bomb out if no fh
314 32 50       181 return undef unless $self->{fh};
315              
316             # "try"
317 32         100 $ret = eval {
318 32         136 $fileno = fileno $self->{fh};
319              
320             # while $blocking, but at least once...
321 32         48 do {
322             # calculate the time left, abort if necessary.
323 35 50 66     661 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 35 100       109 $timeout = 0 unless $blocking;
330              
331             # wait for something
332 35         232 $rmask = '';
333 35         258 vec( $rmask, $fileno, 1 ) = 1;
334 35 100       202989 if ( select( $rmask, undef, undef, $timeout ) ) {
335              
336             # check for incoming data
337 32 50       189 if ( vec( $rmask, $fileno, 1 ) ) {
338              
339             # try to read as much data as possible.
340 32         101 $answer = '';
341 32         865 my $nread = sysread( $self->{fh}, $answer, 1000 );
342 32 50       127 if ( !defined $nread ) {
343             # On Solaris, sysread on a socketpair may
344             # return ESPIPE instead of 0 for EOF.
345             # On Windows, close() without shutdown() sends
346             # TCP RST, causing ECONNRESET on the peer.
347             die "= remote end closed connection\n"
348 0 0 0     0 if $!{ESPIPE} || $!{ECONNRESET};
349 0         0 die "= read returned error: $!\n";
350             }
351             $nread
352 32 100       173 or die "= remote end closed connection\n";
353              
354             # append incoming data to total received
355 28         194 $self->{answer} .= $answer;
356              
357             # check for max length
358 28 100       159 length( $self->{answer} ) <= 1000
359             or die "= remote daemon babbling too much\n";
360              
361             # if data contains a CR or LF, we are ready receiving.
362             # strip everything after and including the CR or LF and
363             # return success
364 27 100       322 if ( $self->{answer} =~ /[\n\r]/ ) {
365 21         174 $self->{answer} =~ s/[\n\r].*//s;
366 21 50       75 print STDDBG "Net::Ident::ready received: $self->{answer}\n"
367             if $DEBUG;
368              
369             # close the socket to the remote identd
370 21         452 close( $self->{fh} );
371 21         110 $self->{state} = 'ready';
372 21         77 return 1;
373             }
374             }
375             }
376             } while $blocking;
377              
378             # we don't block, but we didn't receive everything yet... return false.
379 6         12 0;
380             };
381 32 100       216 if ( $@ =~ /^= (.*)/ ) {
    50          
382              
383             # here's the catch of the throw
384             # return undef, try to preserve errno
385 5         37 local ($!);
386 5         40 $self->{error} = "Net::Ident::ready: $1\n";
387 5 50       19 print STDDBG $self->{error} if $DEBUG;
388              
389             # this deletes the FileHandle, which gets closed,
390             # so that might change errno
391 5         148 delete $self->{fh};
392 5         40 return undef;
393             }
394             elsif ($@) {
395              
396             # something else went wrong. barf up completely.
397 0         0 confess($@);
398             }
399              
400             # return the return value from the eval{}
401 27         125 $ret;
402             }
403              
404             # return the username from the rfc931 query return.
405             # object method
406             sub username {
407 28     28 1 203748 my ($self) = @_;
408             my (
409 28         73 $remoteport, $localport, $port1, $port2, $replytype, $reply, $opsys,
410             $userid, $error
411             );
412              
413 28 50       88 print STDDBG "Net::Ident::username\n" if $DEBUG > 1;
414              
415             # wait for data, if necessary.
416 28 50       80 return wantarray ? ( undef, undef, $self->{error} ) : undef
    100          
417             unless $self->ready(1);
418              
419             # parse the received string, split it into parts.
420 27         357 ( $port1, $port2, $replytype, $reply ) = ( $self->{answer} =~ /^\s*(\d+)\s*,\s*(\d+)\s*:\s*(ERROR|USERID)\s*:\s*(.*)$/ );
421              
422             # make sure the answer parsed properly, and that the ports are the same.
423 27 100 100     244 if ( !defined($reply)
      100        
424             || ( $self->{remoteport} != $port1 )
425             || ( $self->{localport} != $port2 ) ) {
426 4         10 $self->{error} = "Net::Ident::username couldn't parse reply or port mismatch\n";
427 4 50       11 print STDDBG $self->{error} if $DEBUG;
428 4 50       21 return wantarray ? ( undef, undef, $self->{error} ) : undef;
429             }
430              
431             # check for error return type
432 23 100       80 if ( $replytype eq "ERROR" ) {
433 10 50       32 print STDDBG "Net::Ident::username: lookup returned ERROR\n" if $DEBUG;
434 10         13 $userid = undef;
435 10         13 $opsys = "ERROR";
436 10         28 ( $error = $reply ) =~ s/\s+$//;
437             }
438             else {
439             # a normal reply, parse the opsys and userid. Note that the opsys may
440             # contain \ escaped colons, which is why the hairy regexp is necessary.
441 13 50       132 unless ( ( $opsys, $userid ) = ( $reply =~ /\s*((?:[^\\:]+|\\.)*):(.*)$/ ) ) {
442              
443             # didn't parse properly, abort.
444 0         0 $self->{error} = "Net::Ident::username: couldn't parse userid\n";
445 0 0       0 print STDDBG $self->{error} if $DEBUG;
446 0 0       0 return wantarray ? ( undef, undef, $self->{error} ) : undef;
447             }
448              
449             # remove trailing whitespace, except backwhacked whitespaces from opsys
450 13         291 $opsys =~ s/([^\\])\s+$/$1/;
451              
452             # un-backwhack opsys.
453 13         66 $opsys =~ s/\\(.)/$1/g;
454              
455             # in all cases is leading whitespace removed from the username, even
456             # though rfc1413 mentions that it shouldn't be done, current
457             # implementation practice dictates otherwise. What insane OS would
458             # use leading whitespace in usernames anyway...
459 13         44 $userid =~ s/^\s+//;
460              
461             # Test if opsys is "special": if it contains a charset definition,
462             # or if it is "OTHER". This means that it is rfc1413-like, instead
463             # of rfc931-like. (Why can't they make these RFCs non-conflicting??? ;)
464             # Note that while rfc1413 (the one that superseded rfc931) indicates
465             # that _any_ characters following the final colon are part of the
466             # username, current implementation practice inserts a space there,
467             # even "modern" identd daemons.
468             # Also, rfc931 specifically mentions escaping characters, while
469             # rfc1413 does not mention it (it isn't really necessary). Anyway,
470             # I'm going to remove trailing whitespace from userids, and I'm
471             # going to un-backwhack them, unless the opsys is "special".
472 13 100 100     140 unless ( $opsys =~ /,/ || $opsys eq 'OTHER' ) {
473              
474             # remove trailing whitespace, except backwhacked whitespaces.
475 11         57 $userid =~ s/([^\\])\s+$/$1/;
476              
477             # un-backwhack
478 11         24 $userid =~ s/\\(.)/$1/g;
479             }
480 13         55 $error = undef;
481             }
482              
483             # return the requested information, depending on whether in array context.
484 23 50       52 if ( $DEBUG > 1 ) {
485 0         0 print STDDBG "Net::Ident::username returns:\n";
486 0 0       0 print STDDBG "userid = ", defined $userid ? $userid : "", "\n";
487 0 0       0 print STDDBG "opsys = ", defined $opsys ? $opsys : "", "\n";
488 0 0       0 print STDDBG "error = ", defined $error ? $error : "", "\n";
489             }
490 23 100       166 wantarray ? ( $userid, $opsys, $error ) : $userid;
491             }
492              
493             # do the entire rfc931 lookup in one blow.
494             # exportable subroutine, not a method
495             sub lookup ($;$) {
496 0     0 1 0 my ( $fh, $timeout ) = @_;
497              
498 0 0       0 print STDDBG "Net::Ident::lookup fh=$fh, timeout=", defined $timeout ? $timeout : "", "\n"
    0          
499             if $DEBUG > 1;
500              
501 0         0 Net::Ident->new( $fh, $timeout )->username;
502             }
503              
504             # do the entire rfc931 lookup from two in_addr structs
505             sub lookupFromInAddr ($$;$) {
506 0     0 1 0 my ( $localaddr, $remoteaddr, $timeout ) = @_;
507              
508 0     0   0 print STDDBG "Net::Ident::lookupFromInAddr localaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
509 0     0   0 ->( sockaddr_in($localaddr) ), ", remoteaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
510 0 0       0 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    0          
511             if $DEBUG > 1;
512              
513 0         0 Net::Ident->newFromInAddr( $localaddr, $remoteaddr, $timeout )->username;
514             }
515              
516             # alias Net::Ident::ident_lookup to Net::Ident::lookup
517             sub ident_lookup ($;$);
518             *ident_lookup = \&lookup;
519              
520             # prevent "used only once" warning
521             ident_lookup(0) if 0;
522              
523             # get the FileHandle ref from the object, to be used in an external select().
524             # object method
525             sub getfh ($) {
526 5     5 1 6825 my ($self) = @_;
527              
528 5         24 $self->{fh};
529             }
530              
531             # get the last error message.
532             # object method
533             sub geterror ($) {
534 10     10 1 11165 my ($self) = @_;
535              
536 10         88 $self->{error};
537             }
538              
539             # this is called whenever a function in @EXPORT_FAIL is imported.
540             # simply calls the installed export hooks from %EXPORT_HOOKS, or
541             # passes along the export_fail up the inheritance chain
542             sub export_fail {
543 7     7 0 182370 my $pkg = shift;
544 7         15 my $fail;
545             my @other;
546 7         12 foreach $fail (@_) {
547 9 100 66     87 if ( $fail =~ /^_export_hook_(.*)$/ && $EXPORT_HOOKS{$1} ) {
548 7         14 &{ $EXPORT_HOOKS{$1} };
  7         63  
549             }
550             else {
551 2         7 push @other, $fail;
552             }
553             }
554 7 100       27 if (@other) {
555 2         18 @other = $pkg->SUPER::export_fail(@other);
556             }
557 7         237 @other;
558             }
559              
560             # add lookup method for FileHandle objects. Note that this relies on the
561             # use FileHandle;
562             sub _add_fh_method {
563              
564             # determine package to add method to
565 5 50   5   4951 my $pkg = grep( /^IO::/, @FileHandle::ISA ) ? "IO::Handle" : "FileHandle";
566              
567             # insert method in package. Arguments are already OK for std lookup
568             # turn off strict refs for this glob-mangling trick
569 12     12   25667 no strict 'refs';
  12         28  
  12         1416  
570 5         10 *{"${pkg}::ident_lookup"} = \&lookup;
  5         53  
571              
572 5 50       20 print STDDBG "Added ${pkg}::ident_lookup method\n" if $DEBUG;
573             }
574              
575             sub _add_apache_method {
576              
577             # add method to Apache::Connection class
578 12     12   65 no strict 'refs';
  12         16  
  12         2192  
579 2         24 *{"Apache::Connection::ident_lookup"} = sub {
580 0     0   0 my ( $self, $timeout ) = @_;
581              
582 0 0       0 print STDDBG "Apache::Connection::ident_lookup self=$self, ", "timeout=", defined $timeout ? $timeout : "", "\n"
    0          
583             if $DEBUG > 1;
584 0         0 lookupFromInAddr( $self->local_addr, $self->remote_addr, $timeout );
585 2     2   7900 };
586              
587 2 50       12 print STDDBG "Added Apache::Connection::ident_lookup method\n" if $DEBUG;
588             }
589              
590             1;
591              
592             __END__