File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 86 229 37.5
branch 18 170 10.5
condition 1 24 4.1
subroutine 19 31 61.2
pod 9 10 90.0
total 133 464 28.6


line stmt bran cond sub pod time code
1             package Net::Ident;
2              
3 5     5   22161 use strict;
  5         28  
  5         149  
4 5     5   22 use warnings;
  5         6  
  5         118  
5              
6 5     5   1935 use Socket;
  5         13005  
  5         1852  
7 5     5   36 use Fcntl;
  5         9  
  5         795  
8 5     5   1877 use FileHandle;
  5         40832  
  5         26  
9 5     5   1799 use Carp;
  5         7  
  5         240  
10 5     5   27 use Config;
  5         6  
  5         232  
11 5     5   1864 use Errno;
  5         6458  
  5         2592  
12             require Exporter;
13              
14             our @ISA = qw(Exporter);
15             our @EXPORT_OK = qw(ident_lookup lookup lookupFromInAddr);
16             our @EXPORT_FAIL;
17             our %EXPORT_TAGS;
18              
19             # EXPORT_HOOKS is a sortof Exporter extension. Whenever one of the keys
20             # of this hash is imported as a "tag", the corresponding function is called
21             our %EXPORT_HOOKS = (
22             'fh' => \&_add_fh_method,
23             'apache' => \&_add_apache_method,
24             'debug' => \&_set_debug,
25             );
26              
27             # provide import magic
28             sub _export_hooks () {
29 5     5   8 my ( $tag, $hook );
30 5         31 while ( ( $tag, $hook ) = each %EXPORT_HOOKS ) {
31 15         33 my $hookname = "_export_hook_$tag"; # pseudo-function name
32 15         29 $EXPORT_TAGS{$tag} = [$hookname];
33 15         23 push @EXPORT_OK, $hookname;
34 15         44 push @EXPORT_FAIL, $hookname;
35             }
36             }
37              
38             # put the export hooks in the standard Exporter structures
39             _export_hooks();
40              
41             # for compatibility mode, uncomment the next line @@ s/^#\s*// @@
42             # @EXPORT = qw(_export_hook_fh);
43              
44             our $VERSION = "1.25";
45              
46             our $DEBUG = 0;
47             *STDDBG = *STDERR;
48              
49             sub _set_debug {
50 0     0   0 $DEBUG++;
51 0         0 print STDDBG "Debugging turned to level $DEBUG\n";
52             }
53              
54             # protocol number for tcp.
55             my $tcpproto = ( getprotobyname('tcp') )[2] || 6;
56              
57             # get identd port (default to 113).
58             my $identport = ( getservbyname( 'ident', 'tcp' ) )[2] || 113;
59              
60             # what to use to make nonblocking sockets
61             my $NONBLOCK = eval "&$Config{o_nonblock}";
62              
63             # turn a filehandle passed as a string, or glob, into a ref
64             # private subroutine
65             sub _passfh ($) {
66 1     1   8 my ($fh) = @_;
67              
68             # test if $fh is a reference. if it's not, we need to process...
69 1 50       10 if ( !ref $fh ) {
70 1 50       13 print STDDBG "passed fh: $fh is not a reference\n" if $DEBUG;
71              
72             # check for fully qualified name
73 1 50       18 if ( $fh !~ /'|::/ ) {
74 1 50       8 print STDDBG "$fh is not fully qualified\n" if $DEBUG;
75              
76             # get our current package
77 1         10 my $mypkg = (caller)[0];
78 1 50       14 print STDDBG "We are package $mypkg\n" if $DEBUG;
79              
80             # search for calling package
81 1         3 my $depth = 1;
82 1         1 my $otherpkg;
83 1         22 $depth++ while ( ( $otherpkg = caller($depth) ) eq $mypkg );
84 1 50       10 print STDDBG "We are called from package $otherpkg\n" if $DEBUG;
85 1         4 $fh = "${otherpkg}::$fh";
86 1 50       17 print STDDBG "passed fh now fully qualified: $fh\n" if $DEBUG;
87             }
88              
89             # turn $fh into a reference to a $fh. we need to disable strict refs
90 5     5   34 no strict 'refs';
  5         8  
  5         4514  
91 1         21 $fh = \*{$fh};
  1         5  
92             }
93 1         8 $fh;
94             }
95              
96             # create a Net::Ident object, and perform a non-blocking connect()
97             # to the remote identd port.
98             # class method, constructor
99             sub new {
100 1     1 1 2084 my ( $class, $fh, $timeout ) = @_;
101 1         8 my ( $localaddr, $remoteaddr );
102              
103 1 50       76 print STDDBG "Net::Ident::new fh=$fh, timeout=" . ( defined $timeout ? $timeout : "" ) . "\n"
    50          
104             if $DEBUG > 1;
105              
106             # "try"
107 1         10 eval {
108 1 50       14 defined $fh or die "= fh undef\n";
109 1         24 $fh = _passfh($fh);
110              
111             # get information about this (the local) end of the connection. We
112             # assume that $fh is a connected socket of type SOCK_STREAM. If
113             # it isn't, you'll find out soon enough because one of these functions
114             # will return undef real fast.
115 1 50       24 $localaddr = getsockname($fh) or die "= getsockname failed: $!\n";
116              
117             # get information about remote end of connection
118 0 0       0 $remoteaddr = getpeername($fh) or die "= getpeername failed: $!\n";
119             };
120 1 50       17 if ( $@ =~ /^= (.*)/ ) {
    0          
121              
122             # here's the catch of the throw
123             # return false, try to preserve errno
124 1         16 local ($!);
125              
126             # we make a "fake" $self
127 1         36 my $self = {
128             'state' => 'error',
129             'error' => "Net::Ident::new: $1\n",
130             };
131 1 50       30 print STDDBG $self->{error} if $DEBUG;
132              
133             # return our blessed $self
134 1         16 return bless $self, $class;
135             }
136             elsif ($@) {
137              
138             # something else went wrong. barf up completely.
139 0         0 confess($@);
140             }
141              
142             # continue with the NewFromInAddr constructor
143 0         0 $class->newFromInAddr( $localaddr, $remoteaddr, $timeout );
144             }
145              
146             sub newFromInAddr {
147 0     0 1 0 my ( $class, $localaddr, $remoteaddr, $timeout ) = @_;
148 0         0 my $self = {};
149              
150 0     0   0 print STDDBG "Net::Ident::newFromInAddr localaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
151 0     0   0 ->( sockaddr_in($localaddr) ), ", remoteaddr=", sub { inet_ntoa( $_[1] ) . ":$_[0]" }
152 0 0       0 ->( sockaddr_in($remoteaddr) ), ", timeout=", defined $timeout ? $timeout : "", "\n"
    0          
153             if $DEBUG > 1;
154              
155 0         0 eval {
156             # unpack addresses and store in
157 0         0 my ( $localip, $remoteip );
158 0         0 ( $self->{localport}, $localip ) = sockaddr_in($localaddr);
159 0         0 ( $self->{remoteport}, $remoteip ) = sockaddr_in($remoteaddr);
160              
161             # create a local binding port. We cannot bind to INADDR_ANY, it has
162             # to be bind (bound?) to the same IP address as the connection we're
163             # interested in on machines with multiple IP addresses
164 0         0 my $localbind = sockaddr_in( 0, $localip );
165              
166             # store max time
167 0 0       0 $self->{maxtime} = defined($timeout) ? time + $timeout : undef;
168              
169             # create a remote connect point
170 0         0 my $identbind = sockaddr_in( $identport, $remoteip );
171              
172             # create a new FileHandle
173 0         0 $self->{fh} = new FileHandle;
174              
175             # create a stream socket.
176 0 0       0 socket( $self->{fh}, PF_INET, SOCK_STREAM, $tcpproto )
177             or die "= socket failed: $!\n";
178              
179             # bind it to the same IP number as the local end of THESOCK
180 0 0       0 bind( $self->{fh}, $localbind ) or die "= bind failed: $!\n";
181              
182             # make it a non-blocking socket
183 0 0       0 if ( $^O ne 'MSWin32' ) {
184 0 0       0 fcntl( $self->{fh}, F_SETFL, $NONBLOCK ) or die "= fcntl failed: $!\n";
185             }
186              
187             # connect it to the remote identd port, this can return EINPROGRESS.
188             # for some reason, reading $! twice doesn't work as it should
189             connect( $self->{fh}, $identbind )
190             or $!{EINPROGRESS}
191 0 0 0     0 or die "= connect failed: $!\n";
192 0 0       0 $self->{fh}->blocking(0) if $^O eq 'MSWin32';
193             };
194 0 0       0 if ( $@ =~ /^= (.*)/ ) {
    0          
195              
196             # here's the catch of the throw
197             # return false, try to preserve errno
198 0         0 local ($!);
199 0         0 $self->{error} = "Net::Ident::new: $1\n";
200 0 0       0 print STDDBG $self->{error} if $DEBUG;
201              
202             # this deletes the FileHandle, which gets closed,
203             # so that might change errno
204 0         0 delete $self->{fh};
205              
206             # do NOT return, so the constructor always succeeds
207             }
208             elsif ($@) {
209              
210             # something else went wrong. barf up completely.
211 0         0 confess($@);
212             }
213              
214             # clear errno in case it contains EINPROGRESS
215 0         0 $! = 0;
216              
217             # mark the state of the connection
218 0         0 $self->{state} = 'connect';
219              
220             # return a blessed reference
221 0         0 bless $self, $class;
222             }
223              
224             # send the query to the remote daemon.
225             # object method
226             sub query {
227 0     0 1 0 my ($self) = @_;
228 0         0 my ( $wmask, $timeout, $emask, $fileno, $err, $query );
229              
230 0 0       0 print STDDBG "Net::Ident::query\n" if $DEBUG > 1;
231              
232             # bomb out if no fh
233 0 0       0 return undef unless $self->{fh};
234              
235             # "try"
236 0         0 eval {
237 0 0       0 $self->{state} eq 'connect' or die "= calling in the wrong order\n";
238 0         0 $fileno = fileno $self->{fh};
239              
240             # calculate the time left, abort if necessary. Note that $timeout
241             # is simply left undef if $self->{maxtime} is not defined
242 0 0 0     0 if ( defined( $self->{maxtime} )
243             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
244 0         0 die "= Connection timed out\n";
245             }
246              
247             # wait until the socket becomes writable.
248 0         0 $wmask = '';
249 0         0 vec( $wmask, $fileno, 1 ) = 1;
250 0 0       0 scalar select( undef, $wmask, $emask = $wmask, $timeout )
251             or die "= Connection timed out\n";
252              
253             # Check for errors via select (you never know)
254 0 0       0 vec( $emask, $fileno, 1 ) and die "= connection error: $!\n";
255              
256             # fh must be writable now
257 0 0       0 vec( $wmask, $fileno, 1 ) or die "= connection timed out or error: $!\n";
258              
259             # check for errors via getsockopt(SO_ERROR)
260 0         0 $err = getsockopt( $self->{fh}, SOL_SOCKET, SO_ERROR );
261 0 0 0     0 if ( !defined($err) || ( $! = unpack( 'L', $err ) ) ) {
262 0         0 die "= connect: $!\n";
263             }
264              
265             # create the query, based on the remote port and the local port
266 0         0 $query = "$self->{remoteport},$self->{localport}\r\n";
267              
268             # write the query. Ignore the chance that such a short
269             # write will be fragmented.
270 0 0       0 syswrite( $self->{fh}, $query, length $query ) == length $query
271             or die "= fragmented write on socket: $!\n";
272             };
273 0 0       0 if ( $@ =~ /^= (.*)/ ) {
    0          
274              
275             # here's the catch of the throw
276             # return false, try to preserve errno
277 0         0 local ($!);
278 0         0 $self->{error} = "Net::Ident::query: $1\n";
279 0 0       0 print STDDBG $self->{error} if $DEBUG;
280              
281             # this deletes the FileHandle, which gets closed,
282             # so that might change errno
283 0         0 delete $self->{fh};
284 0         0 return undef;
285             }
286             elsif ($@) {
287              
288             # something else went wrong. barf up completely.
289 0         0 confess($@);
290             }
291              
292             # initialise empty answer to prevent uninitialised value warning
293 0         0 $self->{answer} = '';
294              
295             # mark the state of the connection
296 0         0 $self->{state} = 'query';
297              
298             # return the same object on success
299 0         0 $self;
300             }
301              
302             # read data, if any, and check if it's enough.
303             # object method
304             sub ready {
305 0     0 1 0 my ( $self, $blocking ) = @_;
306 0         0 my ( $timeout, $rmask, $emask, $answer, $ret, $fileno );
307              
308 0 0       0 print STDDBG "Net::Ident::ready blocking=" . ( $blocking ? "true\n" : "false\n" ) if $DEBUG > 1;
    0          
309              
310             # perform the query if not already done.
311 0 0       0 if ( $self->{state} ne 'query' ) {
    0          
312 0 0       0 $self->query or return undef;
313             }
314              
315             # exit immediately if ready returned 1 before.
316             elsif ( $self->{state} eq 'ready' ) {
317 0         0 return 1;
318             }
319              
320             # bomb out if no fh
321 0 0       0 return undef unless $self->{fh};
322              
323             # "try"
324 0         0 $ret = eval {
325 0         0 $fileno = fileno $self->{fh};
326              
327             # while $blocking, but at least once...
328 0         0 do {
329             # calculate the time left, abort if necessary.
330 0 0 0     0 if ( defined( $self->{maxtime} )
331             && ( $timeout = $self->{maxtime} - time ) < 0 ) {
332 0         0 die "= Timeout\n";
333             }
334              
335             # zero timeout for non-blocking
336 0 0       0 $timeout = 0 unless $blocking;
337              
338             # wait for something
339 0         0 $rmask = '';
340 0         0 vec( $rmask, $fileno, 1 ) = 1;
341 0 0       0 if ( select( $rmask, undef, $emask = $rmask, $timeout ) ) {
342              
343             # something came in
344 0 0       0 vec( $emask, $fileno, 1 ) and die "= error while reading: $!\n";
345              
346             # check for incoming data
347 0 0       0 if ( vec( $rmask, $fileno, 1 ) ) {
348              
349             # try to read as much data as possible.
350 0         0 $answer = '';
351 0 0       0 defined sysread( $self->{fh}, $answer, 1000 )
352             or die "= read returned error: $!\n";
353              
354             # append incoming data to total received
355 0         0 $self->{answer} .= $answer;
356              
357             # check for max length
358 0 0       0 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 0 0       0 if ( $self->{answer} =~ /[\n\r]/ ) {
365 0         0 $self->{answer} =~ s/[\n\r].*//s;
366 0 0       0 print STDDBG "Net::Ident::ready received: $self->{answer}\n"
367             if $DEBUG;
368              
369             # close the socket to the remote identd
370 0         0 close( $self->{fh} );
371 0         0 $self->{state} = 'ready';
372 0         0 return 1;
373             }
374             }
375             }
376             } while $blocking;
377              
378             # we don't block, but we didn't receive everything yet... return false.
379 0         0 0;
380             };
381 0 0       0 if ( $@ =~ /^= (.*)/ ) {
    0          
382              
383             # here's the catch of the throw
384             # return undef, try to preserve errno
385 0         0 local ($!);
386 0         0 $self->{error} = "Net::Ident::ready: $1\n";
387 0 0       0 print STDDBG $self->{error} if $DEBUG;
388              
389             # this deletes the FileHandle, which gets closed,
390             # so that might change errno
391 0         0 delete $self->{fh};
392 0         0 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 0         0 $ret;
402             }
403              
404             # return the username from the rfc931 query return.
405             # object method
406             sub username {
407 0     0 1 0 my ($self) = @_;
408             my (
409 0         0 $remoteport, $localport, $port1, $port2, $replytype, $reply, $opsys,
410             $userid, $error
411             );
412              
413 0 0       0 print STDDBG "Net::Ident::username\n" if $DEBUG > 1;
414              
415             # wait for data, if necessary.
416 0 0       0 return wantarray ? ( undef, undef, $self->{error} ) : undef
    0          
417             unless $self->ready(1);
418              
419             # parse the received string, split it into parts.
420 0         0 ( $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 0 0 0     0 if ( !defined($reply)
      0        
424             || ( $self->{remoteport} != $port1 )
425             || ( $self->{localport} != $port2 ) ) {
426 0         0 $self->{error} = "Net::Ident::username couldn't parse reply or port mismatch\n";
427 0 0       0 print STDDBG $self->{error} if $DEBUG;
428 0 0       0 return wantarray ? ( undef, undef, $self->{error} ) : undef;
429             }
430              
431             # check for error return type
432 0 0       0 if ( $replytype eq "ERROR" ) {
433 0 0       0 print STDDBG "Net::Ident::username: lookup returned ERROR\n" if $DEBUG;
434 0         0 $userid = undef;
435 0         0 $opsys = "ERROR";
436 0         0 ( $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 0 0       0 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 0         0 $opsys =~ s/([^\\])\s+$/$1/;
451              
452             # un-backwhack opsys.
453 0         0 $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 0         0 $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 0 0 0     0 unless ( $opsys =~ /,/ || $opsys eq 'OTHER' ) {
473              
474             # remove trailing whitespace, except backwhacked whitespaces.
475 0         0 $userid =~ s/([^\\])\s+$/$1/;
476              
477             # un-backwhack
478 0         0 $userid =~ s/\\(.)/$1/g;
479             }
480 0         0 $error = undef;
481             }
482              
483             # return the requested information, depending on whether in array context.
484 0 0       0 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 0 0       0 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 1     1 1 17 my ($self) = @_;
527              
528 1         4 $self->{fh};
529             }
530              
531             # get the last error message.
532             # object method
533             sub geterror ($) {
534 2     2 1 24 my ($self) = @_;
535              
536 2         2 $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 3     3 0 358 my $pkg = shift;
544 3         6 my $fail;
545             my @other;
546 3         7 foreach $fail (@_) {
547 4 50 33     34 if ( $fail =~ /^_export_hook_(.*)$/ && $EXPORT_HOOKS{$1} ) {
548 4         5 &{ $EXPORT_HOOKS{$1} };
  4         11  
549             }
550             else {
551 0         0 push @other, $fail;
552             }
553             }
554 3 50       7 if (@other) {
555 0         0 @other = SUPER::export_fail(@other);
556             }
557 3         1527 @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 3 50   3   13 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 5     5   7842 no strict 'refs';
  5         18  
  5         481  
570 3         7 *{"${pkg}::ident_lookup"} = \&lookup;
  3         22  
571              
572 3 50       31 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 5     5   30 no strict 'refs';
  5         8  
  5         805  
579 1         9 *{"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 1     1   4 };
586              
587 1 50       4 print STDDBG "Added Apache::Connection::ident_lookup method\n" if $DEBUG;
588             }
589              
590             1;
591              
592             __END__