File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 208 228 91.2
branch 110 172 63.9
condition 21 27 77.7
subroutine 25 30 83.3
pod 9 10 90.0
total 373 467 79.8


line stmt bran cond sub pod time code
1             package Net::Ident;
2              
3 14     14   1476970 use 5.010;
  14         49  
4 14     14   101 use strict;
  14         98  
  14         422  
5 14     14   79 use warnings;
  14         49  
  14         754  
6              
7 14     14   7895 use Socket;
  14         53012  
  14         5922  
8 14     14   5435 use FileHandle;
  14         122411  
  14         66  
9 14     14   3607 use Carp;
  14         19  
  14         2468  
10 14     14   11473 use Errno;
  14         29867  
  14         6992  
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 14     14   26 my ( $tag, $hook );
29 14         80 while ( ( $tag, $hook ) = each %EXPORT_HOOKS ) {
30 42         74 my $hookname = "_export_hook_$tag"; # pseudo-function name
31 42         79 $EXPORT_TAGS{$tag} = [$hookname];
32 42         65 push @EXPORT_OK, $hookname;
33 42         203 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
41             # our @EXPORT = qw(_export_hook_fh);
42              
43             our $VERSION = "1.31";
44              
45             our $DEBUG = 0;
46             *STDDBG = *STDERR;
47              
48             sub _set_debug {
49 6     6   6855 $DEBUG++;
50 6         88 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 11     11   29 my ($fh) = @_;
63              
64             # test if $fh is a reference. if it's not, we need to process...
65 11 100       56 if ( !ref $fh ) {
66 2 50       5 print STDDBG "passed fh: $fh is not a reference\n" if $DEBUG;
67              
68             # check for fully qualified name
69 2 100       12 if ( $fh !~ /'|::/ ) {
70 1 50       3 print STDDBG "$fh is not fully qualified\n" if $DEBUG;
71              
72             # get our current package
73 1         3 my $mypkg = (caller)[0];
74 1 50       3 print STDDBG "We are package $mypkg\n" if $DEBUG;
75              
76             # search for calling package
77 1         2 my $depth = 1;
78 1         1 my $otherpkg;
79 1         12 $depth++ while ( ( $otherpkg = caller($depth) ) eq $mypkg );
80 1 50       3 print STDDBG "We are called from package $otherpkg\n" if $DEBUG;
81 1         2 $fh = "${otherpkg}::$fh";
82 1 50       2 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 14     14   108 no strict 'refs';
  14         24  
  14         13286  
87 2         1 $fh = \*{$fh};
  2         6  
88             }
89 11         33 $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 13     13 1 324027 my ( $class, $fh, $timeout ) = @_;
97 13         37 my ( $localaddr, $remoteaddr );
98              
99 13 50       148 print STDDBG "Net::Ident::new fh=$fh, timeout=" . ( defined $timeout ? $timeout : "" ) . "\n"
    100          
100             if $DEBUG > 1;
101              
102             # "try"
103 13         64 eval {
104 13 100       44 defined $fh or die "= fh undef\n";
105 11         95 $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 11 100       143 $localaddr = getsockname($fh) or die "= getsockname failed: $!\n";
112              
113             # get information about remote end of connection
114 5 100       45 $remoteaddr = getpeername($fh) or die "= getpeername failed: $!\n";
115             };
116 13 100       144 if ( $@ =~ /^= (.*)/ ) {
    50          
117              
118             # here's the catch of the throw
119             # return false, try to preserve errno
120 9         61 local ($!);
121              
122             # we make a "fake" $self
123 9         91 my $self = {
124             'state' => 'error',
125             'error' => "Net::Ident::new: $1\n",
126             };
127 9 100       43 print STDDBG $self->{error} if $DEBUG;
128              
129             # return our blessed $self
130 9         55 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 4         17 $class->newFromInAddr( $localaddr, $remoteaddr, $timeout );
140             }
141              
142             sub newFromInAddr {
143 9     9 1 2911 my ( $class, $localaddr, $remoteaddr, $timeout ) = @_;
144 9         17 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 9 0       30 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    50          
149             if $DEBUG > 1;
150              
151 9         20 eval {
152             # unpack addresses and store in
153 9         14 my ( $localip, $remoteip );
154 9         31 ( $self->{localport}, $localip ) = sockaddr_in($localaddr);
155 9         101 ( $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 9         70 my $localbind = sockaddr_in( 0, $localip );
161              
162             # store max time
163 9 100       83 $self->{maxtime} = defined($timeout) ? time + $timeout : undef;
164              
165             # create a remote connect point
166 9         29 my $identbind = sockaddr_in( $identport, $remoteip );
167              
168             # create a new FileHandle
169 9         104 $self->{fh} = FileHandle->new;
170              
171             # create a stream socket.
172 9 50       824 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 9 100       371 bind( $self->{fh}, $localbind ) or die "= bind failed: $!\n";
177              
178             # make it a non-blocking socket
179 8 50       45 if ( $^O ne 'MSWin32' ) {
180 8 50       79 $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 8 100 66     386 or die "= connect failed: $!\n";
188 6 50       146 $self->{fh}->blocking(0) if $^O eq 'MSWin32';
189             };
190 9 100       145 if ( $@ =~ /^= (.*)/ ) {
    50          
191              
192             # here's the catch of the throw
193             # return false, try to preserve errno
194 3         10 local ($!);
195 3         12 $self->{error} = "Net::Ident::new: $1\n";
196 3 50       10 print STDDBG $self->{error} if $DEBUG;
197              
198             # this deletes the FileHandle, which gets closed,
199             # so that might change errno
200 3         76 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 9         20 $! = 0;
212              
213             # mark the state of the connection, consistent with new()
214 9 100       32 $self->{state} = $self->{error} ? 'error' : 'connect';
215              
216             # return a blessed reference
217 9         60 bless $self, $class;
218             }
219              
220             # send the query to the remote daemon.
221             # object method
222             sub query {
223 54     54 1 1084123 my ($self) = @_;
224 54         156 my ( $wmask, $timeout, $fileno, $err, $query );
225              
226 54 50       214 print STDDBG "Net::Ident::query\n" if $DEBUG > 1;
227              
228             # bomb out if no fh
229 54 100       283 return undef unless $self->{fh};
230              
231             # "try"
232 40         101 eval {
233 40 100       216 $self->{state} eq 'connect' or die "= calling in the wrong order\n";
234 35         121 $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 35 100 100     369 if ( defined( $self->{maxtime} )
239             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
240 2         10 die "= Connection timed out\n";
241             }
242              
243             # wait until the socket becomes writable.
244 33         93 $wmask = '';
245 33         165 vec( $wmask, $fileno, 1 ) = 1;
246 33 50       411 scalar select( undef, $wmask, undef, $timeout )
247             or die "= Connection timed out\n";
248              
249             # fh must be writable now
250 33 50       109 vec( $wmask, $fileno, 1 ) or die "= connection timed out or error: $!\n";
251              
252             # check for errors via getsockopt(SO_ERROR)
253 33         416 $err = getsockopt( $self->{fh}, SOL_SOCKET, SO_ERROR );
254 33 100 66     519 if ( !defined($err) || ( $! = unpack( 'L', $err ) ) ) {
255 6         65 die "= connect: $!\n";
256             }
257              
258             # create the query, based on the remote port and the local port
259 27         139 $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 27 50       491 syswrite( $self->{fh}, $query, length $query ) == length $query
264             or die "= fragmented write on socket: $!\n";
265             };
266 40 100       227 if ( $@ =~ /^= (.*)/ ) {
    50          
267              
268             # here's the catch of the throw
269             # return false, try to preserve errno
270 13         91 local ($!);
271 13         79 $self->{error} = "Net::Ident::query: $1\n";
272 13 50       45 print STDDBG $self->{error} if $DEBUG;
273              
274             # this deletes the FileHandle, which gets closed,
275             # so that might change errno
276 13         315 delete $self->{fh};
277 13         163 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 27         127 $self->{answer} = '';
287              
288             # mark the state of the connection
289 27         81 $self->{state} = 'query';
290              
291             # return the same object on success
292 27         100 $self;
293             }
294              
295             # read data, if any, and check if it's enough.
296             # object method
297             sub ready {
298 60     60 1 1827984 my ( $self, $blocking ) = @_;
299 60         213 my ( $timeout, $rmask, $answer, $ret, $fileno );
300              
301 60 0       273 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 60 100       358 if ( $self->{state} eq 'ready' ) {
305 7         51 return 1;
306             }
307              
308             # perform the query if not already done.
309 53 100       237 if ( $self->{state} ne 'query' ) {
310 15 100       418 $self->query or return undef;
311             }
312              
313             # bomb out if no fh
314 40 50       167 return undef unless $self->{fh};
315              
316             # "try"
317 40         122 $ret = eval {
318 40         192 $fileno = fileno $self->{fh};
319              
320             # while $blocking, but at least once...
321 40         132 do {
322             # calculate the time left, abort if necessary.
323 56 100 100     824 if ( defined( $self->{maxtime} )
324             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
325 6         47 die "= Timeout\n";
326             }
327              
328             # zero timeout for non-blocking
329 50 100       1727 $timeout = 0 unless $blocking;
330              
331             # wait for something
332 50         204 $rmask = '';
333 50         367 vec( $rmask, $fileno, 1 ) = 1;
334 50 100       1906165 if ( select( $rmask, undef, undef, $timeout ) ) {
335              
336             # check for incoming data
337 34 50       159 if ( vec( $rmask, $fileno, 1 ) ) {
338              
339             # try to read as much data as possible.
340 34         131 $answer = '';
341 34         942 my $nread = sysread( $self->{fh}, $answer, 1000 );
342 34 50       184 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 34 100       141 or die "= remote end closed connection\n";
353              
354             # append incoming data to total received
355 30         219 $self->{answer} .= $answer;
356              
357             # check for max length
358 30 100       143 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 29 100       338 if ( $self->{answer} =~ /[\n\r]/ ) {
365 22         185 $self->{answer} =~ s/[\n\r].*//s;
366 22 50       87 print STDDBG "Net::Ident::ready received: $self->{answer}\n"
367             if $DEBUG;
368              
369             # close the socket to the remote identd
370 22         430 close( $self->{fh} );
371 22         112 $self->{state} = 'ready';
372 22         91 return 1;
373             }
374             }
375             }
376             } while $blocking;
377              
378             # we don't block, but we didn't receive everything yet... return false.
379 7         23 0;
380             };
381 40 100       242 if ( $@ =~ /^= (.*)/ ) {
    50          
382              
383             # here's the catch of the throw
384             # return undef, try to preserve errno
385 11         96 local ($!);
386 11         90 $self->{error} = "Net::Ident::ready: $1\n";
387 11 50       54 print STDDBG $self->{error} if $DEBUG;
388              
389             # this deletes the FileHandle, which gets closed,
390             # so that might change errno
391 11         391 delete $self->{fh};
392 11         104 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 29         149 $ret;
402             }
403              
404             # return the username from the rfc931 query return.
405             # object method
406             sub username {
407 40     40 1 235508 my ($self) = @_;
408             my (
409 40         73 $remoteport, $localport, $port1, $port2, $replytype, $reply, $opsys,
410             $userid, $error
411             );
412              
413 40 50       112 print STDDBG "Net::Ident::username\n" if $DEBUG > 1;
414              
415             # wait for data, if necessary.
416 40 100       103 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         313 ( $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     231 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       38 print STDDBG $self->{error} if $DEBUG;
428 4 50       20 return wantarray ? ( undef, undef, $self->{error} ) : undef;
429             }
430              
431             # check for error return type
432 23 100       60 if ( $replytype eq "ERROR" ) {
433 10 50       26 print STDDBG "Net::Ident::username: lookup returned ERROR\n" if $DEBUG;
434 10         15 $userid = undef;
435 10         12 $opsys = "ERROR";
436 10         38 ( $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       167 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         98 $opsys =~ s/([^\\])\s+$/$1/;
451              
452             # un-backwhack opsys.
453 13         98 $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         38 $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     73 unless ( $opsys =~ /,/ || $opsys eq 'OTHER' ) {
473              
474             # remove trailing whitespace, except backwhacked whitespaces.
475 11         43 $userid =~ s/([^\\])\s+$/$1/;
476              
477             # un-backwhack
478 11         26 $userid =~ s/\\(.)/$1/g;
479             }
480 13         22 $error = undef;
481             }
482              
483             # return the requested information, depending on whether in array context.
484 23 50       54 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       109 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 4     4 1 20529 my ( $fh, $timeout ) = @_;
497              
498 4 0       19 print STDDBG "Net::Ident::lookup fh=$fh, timeout=", defined $timeout ? $timeout : "", "\n"
    50          
499             if $DEBUG > 1;
500              
501 4         30 Net::Ident->new( $fh, $timeout )->username;
502             }
503              
504             # do the entire rfc931 lookup from two in_addr structs
505             sub lookupFromInAddr ($$;$) {
506 3     3 1 204604 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 3 0       14 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    50          
511             if $DEBUG > 1;
512              
513 3         17 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 11     11 1 8345 my ($self) = @_;
527              
528 11         91 $self->{fh};
529             }
530              
531             # get the last error message.
532             # object method
533             sub geterror ($) {
534 27     27 1 17371 my ($self) = @_;
535              
536 27         222 $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 177507 my $pkg = shift;
544 7         15 my $fail;
545             my @other;
546 7         13 foreach $fail (@_) {
547 9 100 66     108 if ( $fail =~ /^_export_hook_(.*)$/ && $EXPORT_HOOKS{$1} ) {
548 7         13 &{ $EXPORT_HOOKS{$1} };
  7         25  
549             }
550             else {
551 2         5 push @other, $fail;
552             }
553             }
554 7 100       25 if (@other) {
555 2         29 @other = $pkg->SUPER::export_fail(@other);
556             }
557 7         187 @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   6368 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 14     14   33714 no strict 'refs';
  14         26  
  14         1733  
570 5         11 *{"${pkg}::ident_lookup"} = \&lookup;
  5         49  
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 14     14   73 no strict 'refs';
  14         25  
  14         2460  
579 2         26 *{"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   7244 };
586              
587 2 50       11 print STDDBG "Added Apache::Connection::ident_lookup method\n" if $DEBUG;
588             }
589              
590             1;
591              
592             __END__