File Coverage

blib/lib/Net/Ident.pm
Criterion Covered Total %
statement 86 227 37.8
branch 18 166 10.8
condition 1 24 4.1
subroutine 19 31 61.2
pod 9 10 90.0
total 133 458 29.0


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