ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/AnyEvent-Porttracker/Porttracker.pm
(Generate patch)

Comparing cvsroot/AnyEvent-Porttracker/Porttracker.pm (file contents):
Revision 1.3 by root, Mon Nov 15 19:49:36 2010 UTC vs.
Revision 1.19 by root, Tue Jul 26 16:12:46 2016 UTC

1=head1 NAME 1=head1 NAME
2 2
3AnyEvent::Porttracker - Porttracker/PortIQ API client interface. 3AnyEvent::Porttracker - Porttracker API client interface.
4 4
5=head1 SYNOPSIS 5=head1 SYNOPSIS
6 6
7 use AnyEvent::Porttracker; 7 use AnyEvent::Porttracker;
8
9 my $api = new AnyEvent::Porttracker
10 host => "10.0.0.1",
11 user => "admin",
12 pass => "31331",
13 tls => 1,
14 on_error => sub {
15 die $_[1];
16 },
17 ;
18
19 # Example 1
20 # a simple request: ping the server synchronously
21
22 my ($timestamp, $pid) = $api->req_sync ("ping");
23
24 # Example 2
25 # find all realms, start a discovery on all of them
26 # and wait until all discovery processes have finished
27 # but execute individual discoveries in parallel,
28 # asynchronously
29
30 my $cv = AE::cv;
31
32 $cv->begin;
33 # find all realms
34 $api->req (realm_info => ["gid", "name"], sub {
35 my ($api, @realms) = @_;
36
37 # start discovery on all realms
38 for my $realm (@realms) {
39 my ($gid, $name) = @$realm;
40
41 $cv->begin;
42 $api->req (realm_discover => $gid, sub {
43 warn "discovery for realm '$name' finished\n";
44 $cv->end;
45 });
46 }
47
48 $cv->end;
49 });
50
51 $cv->recv;
52
53 # Example 3
54 # subscribe to realm_poll_stop events and report each occurance
55
56 $api->req (subscribe => "realm_poll_stop", sub {});
57 $api->on (realm_poll_stop_event => sub {
58 my ($api, $gid) = @_;
59 warn "this just in: poll for realm <$gid> finished.\n";
60 });
61
62 AE::cv->recv; # wait forever
8 63
9=head1 DESCRIPTION 64=head1 DESCRIPTION
10 65
11Porttracker (L<http://www.porttracker.com/>) is a product that (among 66Porttracker (L<http://www.porttracker.com/>) is a product that (among
12other things) scans switches and routers in a network and gives a coherent 67other things) scans switches and routers in a network and gives a coherent
13view of which end devices are connected to which switch ports on which 68view of which end devices are connected to which switch ports on which
14switches and routers. It also offers a JSON-based client API, for which 69switches and routers. It also offers a JSON-based client API, for which
15this module is an implementation. 70this module is an implementation.
16 71
17In addition to Porttracker, the PortIQ product is also supported, as it
18uses the same protocol.
19
20If you do not have access to either a Porttracker or PortIQ box then this 72If you do not have access to a Porttracker box then this module will be of
21module will be of little value to you. 73little value to you.
22 74
23This module is an L<AnyEvent> user, you need to make sure that you use and 75This module is an L<AnyEvent> user, you need to make sure that you use and
24run a supported event loop. 76run a supported event loop.
25 77
26To quickly understand how this module works you should read how to 78To quickly understand how this module works you should read how to
27construct a new connection object and then read about the event/callback 79construct a new connection object and then read about the event/callback
28system. 80system.
29 81
82The actual low-level protocol and, more importantly, the existing
83requests and responses, are documented in the official Porttracker
84API documentation (a copy of which is included in this module as
85L<AnyEvent::Porttracker::protocol>.
86
30=head1 THE AnyEvent::Porttracker CLASS 87=head1 THE AnyEvent::Porttracker CLASS
31 88
32The AnyEvent::Porttracker class represents a single connection. 89The AnyEvent::Porttracker class represents a single connection.
33 90
34=over 4 91=over 4
37 94
38package AnyEvent::Porttracker; 95package AnyEvent::Porttracker;
39 96
40use common::sense; 97use common::sense;
41 98
99use Carp ();
42use Scalar::Util (); 100use Scalar::Util ();
43 101
44use AnyEvent (); 102use AnyEvent ();
45use AnyEvent::Handle (); 103use AnyEvent::Handle ();
46 104
47use MIME::Base64 (); 105use MIME::Base64 ();
48use Digest::HMAC_MD6 ();
49use JSON (); 106use JSON ();
50 107
51our $VERSION = '0.0'; 108our $VERSION = 1.02;
52 109
53sub call { 110sub call {
54 my ($self, $type, @args) = @_; 111 my ($self, $type, @args) = @_;
55 112
56 $self->{$type} 113 $self->{$type}
58 : ($type = (UNIVERSAL::can $self, $type)) 115 : ($type = (UNIVERSAL::can $self, $type))
59 ? $type->($self, @args) 116 ? $type->($self, @args)
60 : () 117 : ()
61} 118}
62 119
63=item new AnyEvent::Porttracker [key => value...] 120=item $api = new AnyEvent::Porttracker [key => value...]
64 121
65Creates a new porttracker API connection object and tries to connect to 122Creates a new porttracker API connection object and tries to connect
66the specified host (see below). After the connection has been established, 123to the specified host (see below). After the connection has been
67the TLS handshake (if requested) will take place, followed by a login 124established, the TLS handshake (if requested) will take place, followed
68attempt using either the C<none>, C<login_cram_md6> or C<login> methods, 125by a login attempt using either the C<none>, C<login_cram_sha3>,
69in this order of preference (typically, C<login_cram_md6> is used, which 126C<login_cram_md6> or C<login> methods, in this order of preference
127(typically, C<login_cram_sha3> is used, which shields against some
70shields against some man-in-the-middle attacks and avoids transferring the 128man-in-the-middle attacks and avoids transferring the password).
71password).
72 129
73It is permissible to send requests immediately after creating the object - 130It is permissible to send requests immediately after creating the object -
74they will be queued until after successful login. 131they will be queued until after successful login.
75 132
76Possible key-value pairs are: 133Possible key-value pairs are:
88=item user => $string, pass => $string 145=item user => $string, pass => $string
89 146
90These are the username and password to use when authentication is required 147These are the username and password to use when authentication is required
91(which it is in almost all cases, so these keys are normally mandatory). 148(which it is in almost all cases, so these keys are normally mandatory).
92 149
93=item tls => ... 150=item tls => $bool
94 151
95#TODO# 152Enables or disables TLS (default: disables). When enabled, then the
153connection will try to handshake a TLS connection before logging in. If
154unsuccessful a fatal error will be raised.
155
156Since most Porttracker boxes will not have a sensible/verifiable
157certificate, no attempt at verifying it will be done (which means
158man-in-the-middle-attacks will be trivial). If you want some form of
159verification you need to provide your own C<tls_ctx> object with C<<
160verify => 1, verify_peername => [1, 1, 1] >> or whatever verification mode
161you wish to use.
162
163=item tls_ctx => $tls_ctx
164
165The L<AnyEvent::TLS> object to use. See C<tls>, above.
96 166
97=item on_XYZ => $coderef 167=item on_XYZ => $coderef
98 168
99You can specify event callbacks either by subclassing and overriding the 169You can specify event callbacks either by sub-classing and overriding the
100respective methods or by specifying coderefs as key-value pairs when 170respective methods or by specifying code-refs as key-value pairs when
101constructing the object. 171constructing the object. You add or remove event handlers at any time with
172the C<event> method.
102 173
103=back 174=back
104 175
105=cut 176=cut
106 177
107sub new { 178sub new {
108 my $class = shift; 179 my $class = shift;
109 180
110 my $self = bless { 181 my $self = bless {
111 id => "a", 182 id => "a",
183 ids => [],
112 queue => [], # ininitially queue everything 184 queue => [], # initially queue everything
113 @_, 185 @_,
114 }, $class; 186 }, $class;
115 187
116 { 188 {
117 Scalar::Util::weaken (my $self = $self); 189 Scalar::Util::weaken (my $self = $self);
118 190
119 $self->{hdl} = new AnyEvent::Handle 191 $self->{hdl} = new AnyEvent::Handle
120 connect => [$self->{host}, $self->{port} || "porttracker=55"], 192 connect => [$self->{host}, $self->{port} || "porttracker=55"],
121 on_error => sub { 193 on_error => sub {
122 $self->error (); 194 $self->error ($_[2]);
123 }, 195 },
124 on_connect => sub { 196 on_connect => sub {
125 if ($self->{tls}) { 197 if ($self->{tls}) {
126 $self->_req (start_tls => sub { 198 $self->_req (start_tls => sub {
127 $_[1] 199 $_[1]
137 my $id = shift @$msg; 209 my $id = shift @$msg;
138 210
139 if (defined $id) { 211 if (defined $id) {
140 my $cb = delete $self->{cb}{$id} 212 my $cb = delete $self->{cb}{$id}
141 or return $self->error ("received unexpected reply msg with id $id"); 213 or return $self->error ("received unexpected reply msg with id $id");
214
215 push @{ $self->{ids} }, $id;
142 216
143 $cb->($self, @$msg); 217 $cb->($self, @$msg);
144 } else { 218 } else {
145 $msg->[0] = "on_$msg->[0]_notify"; 219 $msg->[0] = "on_$msg->[0]_notify";
146 call $self, @$msg; 220 call $self, @$msg;
161} 235}
162 236
163sub error { 237sub error {
164 my ($self, $msg) = @_; 238 my ($self, $msg) = @_;
165 239
166 call on_error => $msg; 240 call $self, on_error => $msg;
167 241
168 () 242 ()
169} 243}
170 244
171sub _req { 245sub _req {
172 my $self = shift; 246 my $self = shift;
173 my $cb = pop; 247 my $cb = pop;
174 248
175 my $id = ++$self->{id}; 249 my $id = (pop @{ $self->{ids} }) || $self->{id}++;
176 250
177 unshift @_, $id; 251 unshift @_, $id;
178 $self->{cb}{$id} = $cb; 252 $self->{cb}{$id} = $cb;
179 253
180 my $msg = JSON::encode_json \@_; 254 my $msg = JSON::encode_json \@_;
181 255
182 $self->{hdl}->push_write ($msg); 256 $self->{hdl}->push_write ($msg);
183} 257}
184 258
259=item $api->req ($type => @args, $callback->($api, @reply))
260
261Sends a generic request of type C<$type> to the server. When the server
262responds, the API object and the response arguments (without the success
263status) are passed to the callback, which is the last argument to this
264method.
265
266If the request fails, then a fatal error will be raised. If you want to
267handle failures gracefully, you need to use C<< ->req_failok >> instead.
268
269The available requests are documented in the Porttracker API
270documentation (a copy of which is included in this module as
271L<AnyEvent::Porttracker::protocol>.
272
273It is permissible to call this (or any other request function) at any
274time, even before the connection has been established - the API object
275always waits until after login before it actually sends the requests, and
276queues them until then.
277
278Example: ping the porttracker server.
279
280 $api->req ("ping", sub {
281 my ($api, $ok, $timestamp, $pid) = @_;
282 ...
283 });
284
285Example: determine the product ID.
286
287 $api->req (product_id => sub {
288 my ($api, $ok, $branding, $product_id) = @_;
289 ...
290 });
291
292Example: set a new license.
293
294 $api->req (set_license => $LICENSE_STRING, sub {
295 my ($api, $ok) = @_;
296
297 $ok or die "failed to set license";
298 });
299
300=cut
301
185sub req { 302sub req {
303 my $cb = pop;
304 push @_, sub {
305 splice @_, 1, 1
306 or $_[0]->error ($_[1]);
307
308 &$cb
309 };
310
186 $_[0]{queue} 311 $_[0]{queue}
187 ? push @{ $_[0]{queue} }, [@_] 312 ? push @{ $_[0]{queue} }, [@_]
188 : &_req 313 : &_req
189} 314}
190 315
316=item @res = $api->req_sync ($type => @args)
317
318Similar to C<< ->req >>, but waits for the results of the request and on
319success, returns the values instead (without the success flag, and only
320the first value in scalar context). On failure, the method will C<croak>
321with the error message.
322
323=cut
324
325sub req_sync {
326 push @_, my $cv = AE::cv;
327 &req;
328 my ($ok, @res) = $cv->recv;
329
330 $ok
331 or Carp::croak $res[0];
332
333 wantarray ? @res : $res[0]
334}
335
336=item $api->req_failok ($type => @args, $callback->($api, $success, @reply))
337
338Just like C<< ->req >>, with two differences: first, a failure will not
339raise an error, second, the initial status reply which indicates success
340or failure is not removed before calling the callback.
341
342=cut
343
344sub req_failok {
345 $_[0]{queue}
346 ? push @{ $_[0]{queue} }, [@_]
347 : &_req
348}
349
350=item $api->on (XYZ => $callback)
351
352Overwrites any currently registered handler for C<on_XYZ> or
353installs a new one. Or, when C<$callback> is undef, unregisters any
354currently-registered handler.
355
356Example: replace/set the handler for C<on_discover_stop_event>.
357
358 $api->on (discover_stop_event => sub {
359 my ($api, $gid) = @_;
360 ...
361 });
362
363=cut
364
365sub on {
366 my $self = shift;
367
368 while (@_) {
369 my ($event, $cb) = splice @_, 0, 2;
370 $event =~ s/^on_//;
371
372 $self->{"on_$event"} = $cb;
373 }
374}
375
191sub on_start_tls_notify { 376sub on_start_tls_notify {
192 my ($self) = @_; 377 my ($self) = @_;
193 378
194 $self->{hdl}->starttls ("connect"); 379 $self->{hdl}->starttls (connect => $self->{tls_ctx});
195 $self->{tls} ||= 1; 380 $self->{tls} ||= 1;
196 381
197 $self->_login; 382 $self->_login;
198} 383}
199 384
222 407
223sub _login { 408sub _login {
224 my ($self) = @_; 409 my ($self) = @_;
225 410
226 my ($auths, $nonce) = @{ delete $self->{hello} or return }; 411 my ($auths, $nonce) = @{ delete $self->{hello} or return };
412 use Data::Dump; ddx $auths;#d#
227 413
228 if (grep $_ eq "none", @$auths) { 414 if (grep $_ eq "none", @$auths) {
229 $self->_login_success ("none"); 415 $self->_login_success ("none");
416 } elsif (grep $_ eq "login_cram_sha3", @$auths) {
417 my $cc = join "", map chr 256 * rand, 0..63;
230 418
419 require Digest::SHA3;
420 require Digest::HMAC;
421
422 my $hmac_sha3 = sub ($$){ # $key, $text
423 Digest::HMAC::hmac ($_[1], $_[0], \&Digest::SHA3::sha3_512, 72)
424 };
425
426 my $key = $hmac_sha3->($self->{pass}, $self->{user});
427 my $cr = $hmac_sha3->($key, "$cc$nonce");
428 my $sr = $hmac_sha3->($key, "$nonce$cc");
429
430 $cc = MIME::Base64::encode_base64 $cc;
431 $cr = MIME::Base64::encode_base64 $cr;
432
433 $self->_req (login_cram_sha3 => $self->{user}, $cr, $cc, sub {
434 my ($self, $ok, $msg) = @_;
435
436 $ok
437 or return call $self, on_login_failure => $msg;
438
439 (MIME::Base64::decode_base64 $msg) eq $sr
440 or return call $self, on_login_failure => "sr and cr mismatch, possible man in the middle attack";
441
442 $self->_login_success ("login_cram_sha3");
443 });
231 } elsif (grep $_ eq "login_cram_md6", @$auths) { 444 } elsif (grep $_ eq "login_cram_md6", @$auths) {
232 my $cc = join "", map chr 256 * rand, 0..63; 445 my $cc = join "", map chr 256 * rand, 0..63;
233 446
447 require Digest::HMAC_MD6;
448
234 my $key = Digest::HMAC_MD6::hmac_md6 $self->{pass}, $self->{user}, 64, 256; 449 my $key = Digest::HMAC_MD6::hmac_md6 ($self->{pass}, $self->{user}, 64, 256);
235 my $cr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$cc$nonce", 64, 256; 450 my $cr = Digest::HMAC_MD6::hmac_md6 ($key, "$cc$nonce", 64, 256);
236 my $sr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$nonce$cc", 64, 256; 451 my $sr = Digest::HMAC_MD6::hmac_md6 ($key, "$nonce$cc", 64, 256);
237 452
238 $cc = MIME::Base64::encode_base64 $cc; 453 $cc = MIME::Base64::encode_base64 $cc;
454 $cr = MIME::Base64::encode_base64 $cr;
239 455
240 $self->_req (login_cram_md6 => $self->{user}, $cr, $cc, sub { 456 $self->_req (login_cram_md6 => $self->{user}, $cr, $cc, sub {
241 my ($self, $ok, $msg) = @_; 457 my ($self, $ok, $msg) = @_;
242 458
243 $ok 459 $ok
244 or return call $self, on_login_failure => $msg; 460 or return call $self, on_login_failure => $msg;
245 461
246 $msg eq $sr 462 (MIME::Base64::decode_base64 $msg) eq $sr
247 or return call $self, on_login_failure => "sr and cr mismatch, possible man in the middle attack"; 463 or return call $self, on_login_failure => "sr and cr mismatch, possible man in the middle attack";
248 464
249 $self->_login_success ("login_cram_md6"); 465 $self->_login_success ("login_cram_md6");
250 }); 466 });
251 } elsif (grep $_ eq "login", @$auths) { 467 } elsif (grep $_ eq "login", @$auths) {
291 507
292 $msg =~ s/\n$//; 508 $msg =~ s/\n$//;
293 $self->error ("login failed: $msg"); 509 $self->error ("login failed: $msg");
294} 510}
295 511
512sub on_event_notify {
513 my ($self, $event, @args) = @_;
514
515 call $self, "on_${event}_event", @args;
516}
517
296=back 518=back
297 519
298=head2 EVENTS 520=head1 EVENTS/CALLBACKS
299 521
300AnyEvent::Porttracker conenctions are fully event-driven, and naturally 522AnyEvent::Porttracker connections are fully event-driven, and naturally
301there are a number of events that can occur. All these events have a name 523there are a number of events that can occur. All these events have a name
302starting with C<on_> (example: C<on_login_failure>). 524starting with C<on_> (example: C<on_login_failure>).
303 525
304Programs can catch these events in two ways: either by providing 526Programs can catch these events in two ways: either by providing
305constructor arguments with the event name as key and a coderef as value: 527constructor arguments with the event name as key and a code-ref as value:
306 528
307 my $api = new AnyEvent::Porttracker 529 my $api = new AnyEvent::Porttracker
308 host => ..., 530 host => ...,
309 user => ..., pass => ..., 531 user => ..., pass => ...,
310 on_error => sub { 532 on_error => sub {
312 warn $msg; 534 warn $msg;
313 exit 1; 535 exit 1;
314 }, 536 },
315 ; 537 ;
316 538
317Or by subclassing C<AnyEvent::Porttracker> and overriding methods of the 539Or by sub-classing C<AnyEvent::Porttracker> and overriding methods of the
318same name: 540same name:
319 541
320 package MyClass; 542 package MyClass;
321 543
322 use base AnyEvent::Porttracker; 544 use base AnyEvent::Porttracker;
370 592
371=item on_start_tls_notify $api 593=item on_start_tls_notify $api
372 594
373Called when the server wants to start TLS negotiation. This is used 595Called when the server wants to start TLS negotiation. This is used
374internally and - while it is possible to override it - should not be 596internally and - while it is possible to override it - should not be
375overriden. 597overridden.
598
599=item on_event_notify $api, $eventname, @args
600
601Called when the server broadcasts an event the API object is subscribed
602to. The default implementation (which should not be overridden) simply
603re-issues an "on_eventname_event" event with the @args.
376 604
377=item on_XYZ_notify $api, ... 605=item on_XYZ_notify $api, ...
378 606
379In general, any protocol notification will result in an event of the form 607In general, any protocol notification will result in an event of the form
380C<on_NOTIFICATION_notify>. 608C<on_NOTIFICATION_notify>.
381 609
610=item on_XYZ_event $api, ...
611
612Called when the server broadcasts the named (XYZ) event.
613
382=back 614=back
383 615
384=head1 SEE ALSO 616=head1 SEE ALSO
385 617
386L<AnyEvent>, L<http://www.porttracker.com/>, L<http://www.infoblox.com/en/products/portiq.html>. 618L<AnyEvent>, L<http://www.porttracker.com/>.
387 619
388=head1 AUTHOR 620=head1 AUTHOR
389 621
390 Marc Lehmann <marc@porttracker.net> 622 Marc Lehmann <marc@nethype.de>
391 623
392=cut 624=cut
393 625
3941 6261

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines