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.6 by root, Tue Nov 16 01:10:50 2010 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines