ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/AnyEvent-Porttracker/Porttracker.pm
Revision: 1.14
Committed: Tue Nov 16 19:43:15 2010 UTC (13 years, 7 months ago) by root
Branch: MAIN
Changes since 1.13: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::Porttracker - Porttracker/PortIQ API client interface.
4
5 =head1 SYNOPSIS
6
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 ;
15
16 # Example 1
17 # a simple request: ping the server
18
19 $api->req ("ping", sub {
20 my ($api, $ok, $timestamp, $pid) = @_;
21 ...
22 });
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
28 my $cv = AE::cv;
29
30 $cv->begin;
31 # find all realms
32 $api->req (realm_info => ["gid", "name"], sub {
33 my ($api, @realms) = @_;
34
35 # start discovery on all realms
36 for my $realm (@realms) {
37 my ($gid, $name) = @$realm;
38
39 $cv->begin;
40 $api->req (realm_discover => $gid, sub {
41 warn "discovery for realm '$name' finished\n";
42 $cv->end;
43 });
44 }
45
46 $cv->end;
47 });
48
49 $cv->recv;
50
51 # Example 3
52 # subscribe to realm_poll_stop events and report each occurance
53
54 $api->req (subscribe => "realm_poll_stop", sub {});
55 $api->on (realm_poll_stop_event => sub {
56 my ($api, $gid) = @_;
57 warn "this just in: poll for realm <$gid> finished.\n";
58 });
59
60 =head1 DESCRIPTION
61
62 Porttracker (L<http://www.porttracker.com/>) is a product that (among
63 other things) scans switches and routers in a network and gives a coherent
64 view of which end devices are connected to which switch ports on which
65 switches and routers. It also offers a JSON-based client API, for which
66 this module is an implementation.
67
68 In addition to Porttracker, the PortIQ product is also supported, as it
69 uses the same protocol.
70
71 If you do not have access to either a Porttracker or PortIQ box then this
72 module will be of little value to you.
73
74 This module is an L<AnyEvent> user, you need to make sure that you use and
75 run a supported event loop.
76
77 To quickly understand how this module works you should read how to
78 construct a new connection object and then read about the event/callback
79 system.
80
81 The actual low-level protocol and, more importantly, the existing
82 requests and responses, are documented in the official Porttracker
83 API documentation (a copy of which is included in this module as
84 L<AnyEvent::Porttracker::protocol>.
85
86 =head1 THE AnyEvent::Porttracker CLASS
87
88 The AnyEvent::Porttracker class represents a single connection.
89
90 =over 4
91
92 =cut
93
94 package AnyEvent::Porttracker;
95
96 use common::sense;
97
98 use Scalar::Util ();
99
100 use AnyEvent ();
101 use AnyEvent::Handle ();
102
103 use MIME::Base64 ();
104 use Digest::HMAC_MD6 ();
105 use JSON ();
106
107 our $VERSION = '0.1';
108
109 sub call {
110 my ($self, $type, @args) = @_;
111
112 $self->{$type}
113 ? $self->{$type}($self, @args)
114 : ($type = (UNIVERSAL::can $self, $type))
115 ? $type->($self, @args)
116 : ()
117 }
118
119 =item $api = new AnyEvent::Porttracker [key => value...]
120
121 Creates a new porttracker API connection object and tries to connect to
122 the specified host (see below). After the connection has been established,
123 the TLS handshake (if requested) will take place, followed by a login
124 attempt using either the C<none>, C<login_cram_md6> or C<login> methods,
125 in this order of preference (typically, C<login_cram_md6> is used, which
126 shields against some man-in-the-middle attacks and avoids transferring the
127 password).
128
129 It is permissible to send requests immediately after creating the object -
130 they will be queued until after successful login.
131
132 Possible key-value pairs are:
133
134 =over 4
135
136 =item host => $hostname [MANDATORY]
137
138 The hostname or IP address of the Porttracker box.
139
140 =item port => $service
141
142 The service (port) to use (default: C<porttracker=55>).
143
144 =item user => $string, pass => $string
145
146 These are the username and password to use when authentication is required
147 (which it is in almost all cases, so these keys are normally mandatory).
148
149 =item tls => $bool
150
151 Enables or disables TLS (default: disables). When enabled, then the
152 connection will try to handshake a TLS connection before logging in. If
153 unsuccessful a fatal error will be raised.
154
155 Since most Porttracker/PortIQ boxes will not have a sensible/verifiable
156 certificate, no attempt at verifying it will be done (which means
157 man-in-the-middle-attacks will be trivial). If you want some form of
158 verification you need to provide your own C<tls_ctx> object with C<<
159 verify => 1, verify_peername => [1, 1, 1] >> or whatever verification mode
160 you wish to use.
161
162 =item tls_ctx => $tls_ctx
163
164 The L<AnyEvent::TLS> object to use. See C<tls>, above.
165
166 =item on_XYZ => $coderef
167
168 You can specify event callbacks either by sub-classing and overriding the
169 respective methods or by specifying code-refs as key-value pairs when
170 constructing the object. You add or remove event handlers at any time with
171 the C<event> method.
172
173 =back
174
175 =cut
176
177 sub new {
178 my $class = shift;
179
180 my $self = bless {
181 id => "a",
182 ids => [],
183 queue => [], # initially queue everything
184 @_,
185 }, $class;
186
187 {
188 Scalar::Util::weaken (my $self = $self);
189
190 $self->{hdl} = new AnyEvent::Handle
191 connect => [$self->{host}, $self->{port} || "porttracker=55"],
192 on_error => sub {
193 $self->error ($_[2]);
194 },
195 on_connect => sub {
196 if ($self->{tls}) {
197 $self->_req (start_tls => sub {
198 $_[1]
199 or return $self->error ("TLS rejected by server");
200
201 $self->_login;
202 });
203 }
204 },
205 on_read => sub {
206 while ($_[0]{rbuf} =~ s/^([^\x0a]*)\x0a//) {
207 my $msg = JSON::decode_json $1;
208 my $id = shift @$msg;
209
210 if (defined $id) {
211 my $cb = delete $self->{cb}{$id}
212 or return $self->error ("received unexpected reply msg with id $id");
213
214 push @{ $self->{ids} }, $id;
215
216 $cb->($self, @$msg);
217 } else {
218 $msg->[0] = "on_$msg->[0]_notify";
219 call $self, @$msg;
220 }
221 }
222 },
223 ;
224 }
225
226 $self
227 }
228
229 sub DESTROY {
230 my ($self) = @_;
231
232 $self->{hdl}->destroy
233 if $self->{hdl};
234 }
235
236 sub error {
237 my ($self, $msg) = @_;
238
239 call $self, on_error => $msg;
240
241 ()
242 }
243
244 sub _req {
245 my $self = shift;
246 my $cb = pop;
247
248 my $id = (pop @{ $self->{ids} }) || $self->{id}++;
249
250 unshift @_, $id;
251 $self->{cb}{$id} = $cb;
252
253 my $msg = JSON::encode_json \@_;
254
255 $self->{hdl}->push_write ($msg);
256 }
257
258 =item $api->req ($type => @args, $callback->($api, @reply))
259
260 Sends a generic request of type C<$type> to the server. When the server
261 responds, the API object and the response arguments (without the success
262 status) are passed to the callback, which is the last argument to this
263 method.
264
265 If the request fails, then a fatal error will be raised. If you want to
266 handle failures gracefully, you need to use C<< ->req_failok >> instead.
267
268 The available requests are documented in the Porttracker API
269 documentation (a copy of which is included in this module as
270 L<AnyEvent::Porttracker::protocol>.
271
272 It is permissible to call this (or any other request function) at any
273 time, even before the connection has been established - the API object
274 always waits until after login before it actually sends the requests, and
275 queues them until then.
276
277 Example: ping the porttracker server.
278
279 $api->req ("ping", sub {
280 my ($api, $ok, $timestamp, $pid) = @_;
281 ...
282 });
283
284 Example: determine the product ID.
285
286 $api->req (product_id => sub {
287 my ($api, $ok, $branding, $product_id) = @_;
288 ...
289 });
290
291 Example: set a new license.
292
293 $api->req (set_license => $LICENSE_STRING, sub {
294 my ($api, $ok) = @_;
295
296 $ok or die "failed to set license";
297 });
298
299 =cut
300
301 sub req {
302 my $cb = pop;
303 push @_, sub {
304 splice @_, 1, 1
305 or $_[0]->error ($_[1]);
306
307 &$cb
308 };
309
310 $_[0]{queue}
311 ? push @{ $_[0]{queue} }, [@_]
312 : &_req
313 }
314
315 =item $api->req_failok ($type => @args, $callback->($api, $success, @reply))
316
317 Just like C<< ->req >>, with two differences: first, a failure will not
318 raise an error, second, the initial status reply which indicates success
319 or failure is not removed before calling the callback.
320
321 =cut
322
323 sub req_failok {
324 $_[0]{queue}
325 ? push @{ $_[0]{queue} }, [@_]
326 : &_req
327 }
328
329 =item $api->on (XYZ => $callback)
330
331 Overwrites any currently registered handler for C<on_XYZ> or
332 installs a new one. Or, when C<$callback> is undef, unregisters any
333 currently-registered handler.
334
335 Example: replace/set the handler for C<on_discover_stop_event>.
336
337 $api->on (discover_stop_event => sub {
338 my ($api, $gid) = @_;
339 ...
340 });
341
342 =cut
343
344 sub on {
345 my $self = shift;
346
347 while (@_) {
348 my ($event, $cb) = splice @_, 0, 2;
349 $event =~ s/^on_//;
350
351 $self->{"on_$event"} = $cb;
352 }
353 }
354
355 sub on_start_tls_notify {
356 my ($self) = @_;
357
358 $self->{hdl}->starttls (connect => $self->{tls_ctx});
359 $self->{tls} ||= 1;
360
361 $self->_login;
362 }
363
364 sub on_hello_notify {
365 my ($self, $version, $auths, $nonce) = @_;
366
367 $version == 1
368 or return $self->error ("protocol mismatch, got $version, expected/supported 1");
369
370 $nonce = MIME::Base64::decode_base64 $nonce;
371
372 $self->{hello} = [$auths, $nonce];
373
374 $self->_login
375 unless $self->{tls}; # delay login when trying to handshake tls
376 }
377
378 sub _login_success {
379 my ($self, $method) = @_;
380
381 _req @$_
382 for @{ delete $self->{queue} };
383
384 call $self, on_login => $method;
385 }
386
387 sub _login {
388 my ($self) = @_;
389
390 my ($auths, $nonce) = @{ delete $self->{hello} or return };
391
392 if (grep $_ eq "none", @$auths) {
393 $self->_login_success ("none");
394
395 } elsif (grep $_ eq "login_cram_md6", @$auths) {
396 my $cc = join "", map chr 256 * rand, 0..63;
397
398 my $key = Digest::HMAC_MD6::hmac_md6 $self->{pass}, $self->{user}, 64, 256;
399 my $cr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$cc$nonce", 64, 256;
400 my $sr = Digest::HMAC_MD6::hmac_md6_base64 $key, "$nonce$cc", 64, 256;
401
402 $cc = MIME::Base64::encode_base64 $cc;
403
404 $self->_req (login_cram_md6 => $self->{user}, $cr, $cc, sub {
405 my ($self, $ok, $msg) = @_;
406
407 $ok
408 or return call $self, on_login_failure => $msg;
409
410 $msg eq $sr
411 or return call $self, on_login_failure => "sr and cr mismatch, possible man in the middle attack";
412
413 $self->_login_success ("login_cram_md6");
414 });
415 } elsif (grep $_ eq "login", @$auths) {
416 $self->_req (login => $self->{user}, $self->{pass}, sub {
417 my ($self, $ok, $msg) = @_;
418
419 $ok
420 or return call $self, on_login_failure => $msg;
421
422 $self->_login_success ("login");
423 });
424 } else {
425 call $self, on_login_failure => "no supported auth method (@$auths)";
426 }
427
428 # we no longer need these, make it a bit harder to get them
429 delete $self->{user};
430 delete $self->{pass};
431 }
432
433 sub on_info_notify {
434 my ($self, $msg) = @_;
435
436 warn $msg;
437 }
438
439 sub on_error_notify {
440 my ($self, $msg) = @_;
441
442 $self->error ($msg);
443 }
444
445 sub on_error {
446 my ($self, $msg) = @_;
447
448 warn $msg;
449
450 %$self = ();
451 }
452
453 sub on_login_failure {
454 my ($self, $msg) = @_;
455
456 $msg =~ s/\n$//;
457 $self->error ("login failed: $msg");
458 }
459
460 sub on_event_notify {
461 my ($self, $event, @args) = @_;
462
463 call $self, "on_${event}_event", @args;
464 }
465
466 =back
467
468 =head1 EVENTS/CALLBACKS
469
470 AnyEvent::Porttracker connections are fully event-driven, and naturally
471 there are a number of events that can occur. All these events have a name
472 starting with C<on_> (example: C<on_login_failure>).
473
474 Programs can catch these events in two ways: either by providing
475 constructor arguments with the event name as key and a code-ref as value:
476
477 my $api = new AnyEvent::Porttracker
478 host => ...,
479 user => ..., pass => ...,
480 on_error => sub {
481 my ($api, $msg) = @_;
482 warn $msg;
483 exit 1;
484 },
485 ;
486
487 Or by sub-classing C<AnyEvent::Porttracker> and overriding methods of the
488 same name:
489
490 package MyClass;
491
492 use base AnyEvent::Porttracker;
493
494 sub on_error {
495 my ($api, $msg) = @_;
496 warn $msg;
497 exit 1;
498 }
499
500 Event callbacks are not expected to return anything and are always passed
501 the API object as first argument. Some might have default implementations
502 (for example, C<on_error>), others are ignored unless overriden.
503
504 Description of individual events follow:
505
506 =over 4
507
508 =item on_error $api, $msg
509
510 Is called for every (fatal) error, including C<error> notifies. The
511 default prints the message and destroys the object, so it is highly
512 advisable to override this event.
513
514 =item on_login $api, $method
515
516 Called after a successful login, after which commands can be send. It is
517 permissible to send commands before a successful login: those will be
518 queued and sent just before this event is invoked. C<$method> is the auth
519 method that was used.
520
521 =item on_login_failure $api, $msg
522
523 Called when all login attempts have failed - the default raises a fatal
524 error with the error message from the server.
525
526 =item on_hello_notify $api, $version, $authtypes, $nonce
527
528 This protocol notification is used internally by AnyEvent::Porttracker -
529 you can override it, but the module will most likely not work.
530
531 =item on_info_notify $api, $msg
532
533 Called for informational messages from the server - the default
534 implementation calls C<warn> but otherwise ignores this notification.
535
536 =item on_error_notify $api, $msg
537
538 Called for fatal errors from the server - the default implementation calls
539 C<warn> and destroys the API object.
540
541 =item on_start_tls_notify $api
542
543 Called when the server wants to start TLS negotiation. This is used
544 internally and - while it is possible to override it - should not be
545 overridden.
546
547 =item on_event_notify $api, $eventname, @args
548
549 Called when the server broadcasts an event the API object is subscribed
550 to. The default implementation (which should not be overridden) simply
551 re-issues an "on_eventname_event" event with the @args.
552
553 =item on_XYZ_notify $api, ...
554
555 In general, any protocol notification will result in an event of the form
556 C<on_NOTIFICATION_notify>.
557
558 =item on_XYZ_event $api, ...
559
560 Called when the server broadcasts the named (XYZ) event.
561
562 =back
563
564 =head1 SEE ALSO
565
566 L<AnyEvent>, L<http://www.porttracker.com/>, L<http://www.infoblox.com/en/products/portiq.html>.
567
568 =head1 AUTHOR
569
570 Marc Lehmann <marc@porttracker.net>
571
572 =cut
573
574 1