=head1 NAME
AnyEvent::Porttracker - Porttracker/PortIQ API client interface.
=head1 SYNOPSIS
use AnyEvent::Porttracker;
=head1 DESCRIPTION
Porttracker (L) is a product that (among
other things) scans switches and routers in a network and gives a coherent
view of which end devices are connected to which switch ports on which
switches and routers. It also offers a JSON-based client API, for which
this module is an implementation.
In addition to Porttracker, the PortIQ product is also supported, as it
uses the same protocol.
If you do not have access to either a Porttracker or PortIQ box then this
module will be of little value to you.
This module is an L user, you need to make sure that you use and
run a supported event loop.
=head1 THE AnyEvent::Porttracker CLASS
=over 4
=cut
package AnyEvent::Porttracker;
use common::sense;
use Scalar::Util ();
use AnyEvent ();
use AnyEvent::Handle ();
use MIME::Base64 ();
use Digest::HMAC_MD6 ();
use JSON ();
our $VERSION = '0.0';
sub call {
my ($self, $type, @args) = @_;
$self->{$type}
? $self->{$type}($self, @args)
: ($type = (UNIVERSAL::can $self, $type))
? $type->($self, @args)
: ()
}
=item new AnyEvent::Porttracker
=cut
sub new {
my $class = shift;
my $self = bless {
id => "a",
@_,
}, $class;
{
Scalar::Util::weaken (my $self = $self);
$self->{hdl} = new AnyEvent::Handle
connect => [$self->{host}, $self->{port} || "porttracker=55"],
on_error => sub {
$self->error ();
},
on_connect => sub {
if ($self->{tls}) {
$self->{queue} ||= [];
$self->_req (start_tls => sub {
$_[1]
or return $self->error ("TLS rejected by server");
$self->unqueue;
});
}
},
on_read => sub {
while ($_[0]{rbuf} =~ s/^([^\x0a]*)\x0a//) {
my $msg = JSON::decode_json $1;
my $id = shift @$msg;
if (defined $id) {
my $cb = delete $self->{cb}{$id}
or return $self->error ("received unexpected reply msg with id $id");
$cb->($self, @$msg);
} else {
$msg->[0] = "on_$msg->[0]_notify";
call $self, @$msg;
}
}
},
;
}
$self
}
sub DESTROY {
my ($self) = @_;
$self->{hdl}->destroy
if $self->{hdl};
}
sub error {
my ($self, $msg) = @_;
warn $msg;
()
}
sub _req {
my $self = shift;
my $cb = pop;
my $id = ++$self->{id};
unshift @_, $id;
$self->{cb}{$id} = $cb;
my $msg = JSON::encode_json \@_;
$self->{hdl}->push_write ($msg);
}
sub req {
$_[0]{queue}
? push @{ $_[0]{queue} }, [@_]
: &_req
}
sub unqueue {
my ($self) = @_;
my $queue = delete $self->{queue}
or return;
_req @$_
for @$queue;
}
sub on_start_tls_notify {
my ($self) = @_;
$self->{hdl}->starttls ("connect");
$self->unqueue;
}
sub on_hello_notify {
my ($self, $version, $auths, $nonce) = @_;
$version == 1
or return $self->error ("protocol mismatch, got $version, expected/supported 1");
$nonce = MIME::Base64::decode_base64 $nonce;
if (grep $_ eq "none", @$auths) {
call $self, "on_login";
} elsif (grep $_ eq "login_cram_md6", @$auths) {
my $cc = join "", map chr 256 * rand, 0..63;
my $key = Digest::HMAC_MD6::hmac_md6 $self->{password}, $self->{username}, 64, 256;
my $cr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$cc$nonce", 64, 256;
my $sr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$nonce$cc", 64, 256;
$cc = MIME::Base64::encode_base64 $cc;
$self->req (login_cram_md6 => $self->{username}, $cr, $cc, sub {
my ($self, $ok, $msg) = @_;
$ok
or return call $self, on_login_failure => $msg;
$msg eq $sr
or return call $self, on_login_failure => "sr and cr mismatch, possible man in the middle attack";
call $self, "on_login";
});
} elsif (grep $_ eq "login", @$auths) {
$self->req (login => $self->{username}, $self->{password}, sub {
my ($self, $ok, $msg) = @_;
$ok
or return call $self, on_login_failure => $msg;
call $self, "on_login";
});
} else {
call $self, on_login_failure => "no supported auth method (@$auths)";
}
}
sub on_login_failure {
my ($self, $msg) = @_;
$msg =~ s/\n$//;
$self->error ("login failed: $msg");
}
sub on_error_notify {
my ($self, $msg) = @_;
$self->error ($msg);
}
=back
=head1 SEE ALSO
L, L, L.
=head1 AUTHOR
Marc Lehmann
=cut
1