##################################################################
#                                                                #
#  Net::Finger, a Perl implementation of a finger client.        #
#                                                                #
#  By Dennis "FIMM" Taylor, <corbeau@execpc.com>                 #
#                                                                #
#  This module may be used and distributed under the same terms  #
#  as Perl itself. See your Perl distribution for details.       #
#                                                                #
##################################################################
# $Id$

package Net::Finger;

use strict;
use Socket;
use Carp;
use vars qw($VERSION @ISA @EXPORT $error $debug);

require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw( &finger );

$VERSION = '1.03';
$debug = 0;


# I know the if ($debug) crap gets in the way of the code a bit, but
# it's a worthy sacrifice as far as I'm concerned.

sub finger {
    my ($addr, $verbose) = @_;
    my ($host, $port, $request, @lines);

    unless (@_) {
        carp "Not enough arguments to Net::Finger::finger()";
    }

    # Set the error indicator to something innocuous.
    $error = "";

    $addr ||= '';
    if (index( $addr, '@' ) >= 0) {
        my @tokens = split /\@/, $addr;
        $host = pop @tokens;
        $request = join '@', @tokens;
        
    } else {
        $host = 'localhost';
        $request = $addr;
    }

    if ($request and $verbose) {
        $request = "/W $request";
    }

    if ($debug) {
        warn "Creating a new socket.\n";
    }

    unless (socket( SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'))) {
        $error = "Can\'t create a new socket: $!";
        return;
    }
    select SOCK;  $| = 1;  select STDOUT;

    $port = ($host =~ s/:([0-9]*)$// && $1) ? $1 :
	                (getservbyname('finger', 'tcp'))[2];
	
    if ($debug) {
        warn "Connecting to $host, port $port.\n";
    }

    unless (connect( SOCK, sockaddr_in($port, inet_aton($host)) ))
    {
        $error = "Can\'t connect to $host: $!";
        return;
    }

    if ($debug) {
        warn "Sending request: \"$request\"\n";
    }

    print SOCK "$request\015\012";

    if ($debug) {
        warn "Waiting for response.\n";
    }

    while (<SOCK>) {
        push @lines, $_;
    }

    if ($debug) {
        warn "Response received. Closing connection.\n";
    }

    close SOCK;
    return( wantarray ? @lines : join('', @lines) );
}



1;
__END__

=head1 NAME

Net::Finger - a Perl implementation of a finger client.

=head1 SYNOPSIS

  use Net::Finger;

  # You can put the response in a scalar...
  $response = finger('corbeau@execpc.com');
  unless ($response) {
      warn "Finger problem: $Net::Finger::error";
  }

  # ...or an array.
  @lines = finger('corbeau@execpc.com', 1);

=head1 DESCRIPTION

Net::Finger is a simple, straightforward implementation of a finger client
in Perl -- so simple, in fact, that writing this documentation is almost
unnecessary.

This module has one automatically exported function, appropriately
entitled C<finger()>. It takes two arguments:

=over

=item *

A username or email address to finger. (Yes, it does support the
vaguely deprecated "user@host@host" syntax.) If you need to use a port
other than the default finger port (79), you can specify it like so:
"username@hostname:port".

=item *

(Optional) A boolean value for verbosity. True == verbose output. If
you don't give it a value, it defaults to false. Actually, whether
this output will differ from the non-verbose version at all is up to
the finger server.

=back

C<finger()> is context-sensitive. If it's used in a scalar context, it
will return the server's response in one large string. If it's used in
an array context, it will return the response as a list, line by
line. If an error of some sort occurs, it returns undef and puts a
string describing the error into the package global variable
C<$Net::Finger::error>. If you'd like to see some excessively verbose
output describing every step C<finger()> takes while talking to the
other server, put a true value in the variable C<$Net::Finger::debug>.

Here's a sample program that implements a very tiny, stripped-down
finger(1):

    #!/usr/bin/perl -w

    use Net::Finger;
    use Getopt::Std;
    use vars qw($opt_l);

    getopts('l');
    $x = finger($ARGV[0], $opt_l);

    if ($x) {
        print $x;
    } else {
        warn "$0: error: $Net::Finger::error\n";
    }

=head1 BUGS

=over

=item *

Doesn't yet do non-blocking requests. (FITNR. Really.)

=item *

Doesn't do local requests unless there's a finger server running on localhost.

=item *

Contrary to the name's implications, this module involves no teledildonics.

=head1 AUTHOR

Dennis Taylor, E<lt>corbeau@execpc.comE<gt>

=head1 SEE ALSO

perl(1), finger(1), RFC 1288.

=cut
