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.2 by root, Mon Nov 15 04:57:39 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
78To quickly understand how this module works you should read how to
79construct a new connection object and then read about the event/callback
80system.
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
26=head1 THE AnyEvent::Porttracker CLASS 87=head1 THE AnyEvent::Porttracker CLASS
27 88
89The AnyEvent::Porttracker class represents a single connection.
90
28=over 4 91=over 4
29 92
30=cut 93=cut
31 94
32package AnyEvent::Porttracker; 95package AnyEvent::Porttracker;
33 96
34use common::sense; 97use common::sense;
35 98
99use Carp ();
36use Scalar::Util (); 100use Scalar::Util ();
37 101
38use AnyEvent (); 102use AnyEvent ();
39use AnyEvent::Handle (); 103use AnyEvent::Handle ();
40 104
41use MIME::Base64 (); 105use MIME::Base64 ();
42use Digest::HMAC_MD6 ();
43use JSON (); 106use JSON ();
44 107
45our $VERSION = '0.0'; 108our $VERSION = 1.02;
46 109
47sub call { 110sub call {
48 my ($self, $type, @args) = @_; 111 my ($self, $type, @args) = @_;
49 112
50 $self->{$type} 113 $self->{$type}
52 : ($type = (UNIVERSAL::can $self, $type)) 115 : ($type = (UNIVERSAL::can $self, $type))
53 ? $type->($self, @args) 116 ? $type->($self, @args)
54 : () 117 : ()
55} 118}
56 119
57=item new AnyEvent::Porttracker 120=item $api = new AnyEvent::Porttracker [key => value...]
121
122Creates a new porttracker API connection object and tries to connect
123to the specified host (see below). After the connection has been
124established, the TLS handshake (if requested) will take place, followed
125by a login attempt using either the C<none>, C<login_cram_sha3>,
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
128man-in-the-middle attacks and avoids transferring the password).
129
130It is permissible to send requests immediately after creating the object -
131they will be queued until after successful login.
132
133Possible key-value pairs are:
134
135=over 4
136
137=item host => $hostname [MANDATORY]
138
139The hostname or IP address of the Porttracker box.
140
141=item port => $service
142
143The service (port) to use (default: C<porttracker=55>).
144
145=item user => $string, pass => $string
146
147These are the username and password to use when authentication is required
148(which it is in almost all cases, so these keys are normally mandatory).
149
150=item tls => $bool
151
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.
166
167=item on_XYZ => $coderef
168
169You can specify event callbacks either by sub-classing and overriding the
170respective methods or by specifying code-refs as key-value pairs when
171constructing the object. You add or remove event handlers at any time with
172the C<event> method.
173
174=back
58 175
59=cut 176=cut
60 177
61sub new { 178sub new {
62 my $class = shift; 179 my $class = shift;
63 180
64 my $self = bless { 181 my $self = bless {
65 id => "a", 182 id => "a",
183 ids => [],
184 queue => [], # initially queue everything
66 @_, 185 @_,
67 }, $class; 186 }, $class;
68 187
69 { 188 {
70 Scalar::Util::weaken (my $self = $self); 189 Scalar::Util::weaken (my $self = $self);
71 190
72 $self->{hdl} = new AnyEvent::Handle 191 $self->{hdl} = new AnyEvent::Handle
73 connect => [$self->{host}, $self->{port} || "porttracker=55"], 192 connect => [$self->{host}, $self->{port} || "porttracker=55"],
74 on_error => sub { 193 on_error => sub {
75 $self->error (); 194 $self->error ($_[2]);
76 }, 195 },
77 on_connect => sub { 196 on_connect => sub {
78 if ($self->{tls}) { 197 if ($self->{tls}) {
79 $self->{queue} ||= [];
80 $self->_req (start_tls => sub { 198 $self->_req (start_tls => sub {
81 $_[1] 199 $_[1]
82 or return $self->error ("TLS rejected by server"); 200 or return $self->error ("TLS rejected by server");
83 201
84 $self->unqueue; 202 $self->_login;
85 }); 203 });
86 } 204 }
87 }, 205 },
88 on_read => sub { 206 on_read => sub {
89 while ($_[0]{rbuf} =~ s/^([^\x0a]*)\x0a//) { 207 while ($_[0]{rbuf} =~ s/^([^\x0a]*)\x0a//) {
91 my $id = shift @$msg; 209 my $id = shift @$msg;
92 210
93 if (defined $id) { 211 if (defined $id) {
94 my $cb = delete $self->{cb}{$id} 212 my $cb = delete $self->{cb}{$id}
95 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;
96 216
97 $cb->($self, @$msg); 217 $cb->($self, @$msg);
98 } else { 218 } else {
99 $msg->[0] = "on_$msg->[0]_notify"; 219 $msg->[0] = "on_$msg->[0]_notify";
100 call $self, @$msg; 220 call $self, @$msg;
115} 235}
116 236
117sub error { 237sub error {
118 my ($self, $msg) = @_; 238 my ($self, $msg) = @_;
119 239
120 warn $msg; 240 call $self, on_error => $msg;
121 241
122 () 242 ()
123} 243}
124 244
125sub _req { 245sub _req {
126 my $self = shift; 246 my $self = shift;
127 my $cb = pop; 247 my $cb = pop;
128 248
129 my $id = ++$self->{id}; 249 my $id = (pop @{ $self->{ids} }) || $self->{id}++;
130 250
131 unshift @_, $id; 251 unshift @_, $id;
132 $self->{cb}{$id} = $cb; 252 $self->{cb}{$id} = $cb;
133 253
134 my $msg = JSON::encode_json \@_; 254 my $msg = JSON::encode_json \@_;
135 255
136 $self->{hdl}->push_write ($msg); 256 $self->{hdl}->push_write ($msg);
137} 257}
138 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
139sub req { 302sub req {
303 my $cb = pop;
304 push @_, sub {
305 splice @_, 1, 1
306 or $_[0]->error ($_[1]);
307
308 &$cb
309 };
310
140 $_[0]{queue} 311 $_[0]{queue}
141 ? push @{ $_[0]{queue} }, [@_] 312 ? push @{ $_[0]{queue} }, [@_]
142 : &_req 313 : &_req
143} 314}
144 315
145sub unqueue { 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 {
146 my ($self) = @_; 366 my $self = shift;
147 367
148 my $queue = delete $self->{queue} 368 while (@_) {
149 or return; 369 my ($event, $cb) = splice @_, 0, 2;
370 $event =~ s/^on_//;
150 371
151 _req @$_ 372 $self->{"on_$event"} = $cb;
152 for @$queue; 373 }
153} 374}
154 375
155sub on_start_tls_notify { 376sub on_start_tls_notify {
156 my ($self) = @_; 377 my ($self) = @_;
157 378
158 $self->{hdl}->starttls ("connect"); 379 $self->{hdl}->starttls (connect => $self->{tls_ctx});
380 $self->{tls} ||= 1;
159 381
160 $self->unqueue; 382 $self->_login;
161} 383}
162 384
163sub on_hello_notify { 385sub on_hello_notify {
164 my ($self, $version, $auths, $nonce) = @_; 386 my ($self, $version, $auths, $nonce) = @_;
165 387
166 $version == 1 388 $version == 1
167 or return $self->error ("protocol mismatch, got $version, expected/supported 1"); 389 or return $self->error ("protocol mismatch, got $version, expected/supported 1");
168 390
169 $nonce = MIME::Base64::decode_base64 $nonce; 391 $nonce = MIME::Base64::decode_base64 $nonce;
170 392
393 $self->{hello} = [$auths, $nonce];
394
395 $self->_login
396 unless $self->{tls}; # delay login when trying to handshake tls
397}
398
399sub _login_success {
400 my ($self, $method) = @_;
401
402 _req @$_
403 for @{ delete $self->{queue} };
404
405 call $self, on_login => $method;
406}
407
408sub _login {
409 my ($self) = @_;
410
411 my ($auths, $nonce) = @{ delete $self->{hello} or return };
412 use Data::Dump; ddx $auths;#d#
413
171 if (grep $_ eq "none", @$auths) { 414 if (grep $_ eq "none", @$auths) {
172 call $self, "on_login"; 415 $self->_login_success ("none");
416 } elsif (grep $_ eq "login_cram_sha3", @$auths) {
417 my $cc = join "", map chr 256 * rand, 0..63;
173 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 });
174 } elsif (grep $_ eq "login_cram_md6", @$auths) { 444 } elsif (grep $_ eq "login_cram_md6", @$auths) {
175 my $cc = join "", map chr 256 * rand, 0..63; 445 my $cc = join "", map chr 256 * rand, 0..63;
176 446
447 require Digest::HMAC_MD6;
448
177 my $key = Digest::HMAC_MD6::hmac_md6 $self->{password}, $self->{username}, 64, 256; 449 my $key = Digest::HMAC_MD6::hmac_md6 ($self->{pass}, $self->{user}, 64, 256);
178 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);
179 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);
180 452
181 $cc = MIME::Base64::encode_base64 $cc; 453 $cc = MIME::Base64::encode_base64 $cc;
454 $cr = MIME::Base64::encode_base64 $cr;
182 455
183 $self->req (login_cram_md6 => $self->{username}, $cr, $cc, sub { 456 $self->_req (login_cram_md6 => $self->{user}, $cr, $cc, sub {
184 my ($self, $ok, $msg) = @_; 457 my ($self, $ok, $msg) = @_;
185 458
186 $ok 459 $ok
187 or return call $self, on_login_failure => $msg; 460 or return call $self, on_login_failure => $msg;
188 461
189 $msg eq $sr 462 (MIME::Base64::decode_base64 $msg) eq $sr
190 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";
191 464
192 call $self, "on_login"; 465 $self->_login_success ("login_cram_md6");
193 }); 466 });
194 } elsif (grep $_ eq "login", @$auths) { 467 } elsif (grep $_ eq "login", @$auths) {
195 $self->req (login => $self->{username}, $self->{password}, sub { 468 $self->_req (login => $self->{user}, $self->{pass}, sub {
196 my ($self, $ok, $msg) = @_; 469 my ($self, $ok, $msg) = @_;
197 470
198 $ok 471 $ok
199 or return call $self, on_login_failure => $msg; 472 or return call $self, on_login_failure => $msg;
200 473
201 call $self, "on_login"; 474 $self->_login_success ("login");
202 }); 475 });
203 } else { 476 } else {
204 call $self, on_login_failure => "no supported auth method (@$auths)"; 477 call $self, on_login_failure => "no supported auth method (@$auths)";
205 } 478 }
479
480 # we no longer need these, make it a bit harder to get them
481 delete $self->{user};
482 delete $self->{pass};
483}
484
485sub on_info_notify {
486 my ($self, $msg) = @_;
487
488 warn $msg;
489}
490
491sub on_error_notify {
492 my ($self, $msg) = @_;
493
494 $self->error ($msg);
495}
496
497sub on_error {
498 my ($self, $msg) = @_;
499
500 warn $msg;
501
502 %$self = ();
206} 503}
207 504
208sub on_login_failure { 505sub on_login_failure {
209 my ($self, $msg) = @_; 506 my ($self, $msg) = @_;
210 507
211 $msg =~ s/\n$//; 508 $msg =~ s/\n$//;
212 $self->error ("login failed: $msg"); 509 $self->error ("login failed: $msg");
213} 510}
214 511
215sub on_error_notify { 512sub on_event_notify {
216 my ($self, $msg) = @_; 513 my ($self, $event, @args) = @_;
217 514
218 $self->error ($msg); 515 call $self, "on_${event}_event", @args;
219} 516}
220 517
221=back 518=back
222 519
520=head1 EVENTS/CALLBACKS
521
522AnyEvent::Porttracker connections are fully event-driven, and naturally
523there are a number of events that can occur. All these events have a name
524starting with C<on_> (example: C<on_login_failure>).
525
526Programs can catch these events in two ways: either by providing
527constructor arguments with the event name as key and a code-ref as value:
528
529 my $api = new AnyEvent::Porttracker
530 host => ...,
531 user => ..., pass => ...,
532 on_error => sub {
533 my ($api, $msg) = @_;
534 warn $msg;
535 exit 1;
536 },
537 ;
538
539Or by sub-classing C<AnyEvent::Porttracker> and overriding methods of the
540same name:
541
542 package MyClass;
543
544 use base AnyEvent::Porttracker;
545
546 sub on_error {
547 my ($api, $msg) = @_;
548 warn $msg;
549 exit 1;
550 }
551
552Event callbacks are not expected to return anything and are always passed
553the API object as first argument. Some might have default implementations
554(for example, C<on_error>), others are ignored unless overriden.
555
556Description of individual events follow:
557
558=over 4
559
560=item on_error $api, $msg
561
562Is called for every (fatal) error, including C<error> notifies. The
563default prints the message and destroys the object, so it is highly
564advisable to override this event.
565
566=item on_login $api, $method
567
568Called after a successful login, after which commands can be send. It is
569permissible to send commands before a successful login: those will be
570queued and sent just before this event is invoked. C<$method> is the auth
571method that was used.
572
573=item on_login_failure $api, $msg
574
575Called when all login attempts have failed - the default raises a fatal
576error with the error message from the server.
577
578=item on_hello_notify $api, $version, $authtypes, $nonce
579
580This protocol notification is used internally by AnyEvent::Porttracker -
581you can override it, but the module will most likely not work.
582
583=item on_info_notify $api, $msg
584
585Called for informational messages from the server - the default
586implementation calls C<warn> but otherwise ignores this notification.
587
588=item on_error_notify $api, $msg
589
590Called for fatal errors from the server - the default implementation calls
591C<warn> and destroys the API object.
592
593=item on_start_tls_notify $api
594
595Called when the server wants to start TLS negotiation. This is used
596internally and - while it is possible to override it - should not be
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.
604
605=item on_XYZ_notify $api, ...
606
607In general, any protocol notification will result in an event of the form
608C<on_NOTIFICATION_notify>.
609
610=item on_XYZ_event $api, ...
611
612Called when the server broadcasts the named (XYZ) event.
613
614=back
615
223=head1 SEE ALSO 616=head1 SEE ALSO
224 617
225L<AnyEvent>, L<http://www.porttracker.com/>, L<http://www.infoblox.com/en/products/portiq.html>. 618L<AnyEvent>, L<http://www.porttracker.com/>.
226 619
227=head1 AUTHOR 620=head1 AUTHOR
228 621
229 Marc Lehmann <marc@porttracker.net> 622 Marc Lehmann <marc@nethype.de>
230 623
231=cut 624=cut
232 625
2331 6261

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines