| Mail-Karmasphere-Client documentation | Contained in the Mail-Karmasphere-Client distribution. |
Mail::Karmasphere::Client - Client for Karmasphere Reputation Server
use Mail::Karmasphere::Client qw(:all);
my $client = new Mail::Karmasphere::Client(
PeerAddr => 'query.karmasphere.com',
PeerPort => 8666,
Principal => "my_assigned_query_username",
Credentials => "my_assigned_query_password",
# see http://my.karmasphere.com/devzone/client/configuration#credentials
# quickstart: use temporary credentials for "generic perl".
# recommended: use permanent credentials -- register for an account.
);
my $query = new Mail::Karmasphere::Query();
$query->identity('127.0.0.2', IDT_IP4);
$query->composite('karmasphere.email-sender');
my $response = $client->ask($query, 6);
print $response->as_string;
my $id = $client->send($query);
my $response = $client->recv($query, 12);
my $response = $client->recv($id, 12);
my $response = $client->query(
Identities => [ ... ]
Composite => 'karmasphere.email-sender',
);
The Perl Karma Client API consists of three objects: The Query, the Response, and the Client. The user constructs a Query and passes it to a Client, which returns a Response.
The class method new(...) constructs a new Client object. All arguments are optional. The following parameters are recognised as arguments to new():
The IP address or hostname to contact. See IO::Socket::INET. The default is 'query.karmasphere.com'.
The TCP or UDP to contact. See IO::Socket::INET. The default is 8666.
Either 'udp' or 'tcp'. The default is 'udp' because it is faster.
A username and password are required to authenticate client connections. They are assigned by Karmasphere. See http://my.karmasphere.com/devzone/client/configuration#credentials
"Principal" corresponds to "username", and "Credentials" corresponds to "password". Note that these are not the same username and password you use to sign in to the website.
Either a true value for debugging to stderr, or a custom debug handler. The custom handler will be called with N arguments, the first of which is a string 'debug context'. The custom handler may choose to ignore messages from certain contexts.
Returns a Mail::Karmasphere::Response to a Mail::Karmasphere::Query. The core of this method is equivalent to
$client->recv($client->send($query), $timeout)
The method retries up to 3 times, doubling the timeout each time. If the application requires more control over retries or backoff, it should use send() and recv() individually. $timeout is optional.
Sends a Mail::Karmasphere::Query to the server, and returns the id of the query, which may be passed to recv(). Note that any query longer than 64KB will be rejected by the server with a message advising that the maximum message length has been exceeded.
Returns a Mail::Karmasphere::Response to the query with id $id, assuming that the query has already been sent using send(). If no matching response is read before the timeout, undef is returned.
A convenience method, equivalent to
$client->ask(new Mail::Karmasphere::Query(...));
See Mail::Karmasphere::Query for more details.
Identity type constants.
Identity tags, indicating the context of an identity to the server.
A flag indicating that all facts must be returned explicitly in the Response.
The server will discard any packet in TCP mode which exceeds 64K. Although the packet length field is 4 bytes, it is relatively common to get non-Karmasphere clients connecting to the port. Therefore the server checks that the top two bytes are \0 before accepting the packet. This saves everybody a headache.
Some flags, notably those which generate large response packets, are totally ignored for UDP queries, even in the case that they would not generate a large response. This also saves many headaches.
UDP retries are not yet implemented.
Mail::Karmasphere::Query, Mail::Karmasphere::Response, http://www.karmasphere.com/, http://my.karmasphere.com/devzone/client/configuration, Mail::SpamAssassin::Plugin::Karmasphere
Copyright (c) 2005-2006 Shevek, Karmasphere. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Mail-Karmasphere-Client documentation | Contained in the Mail-Karmasphere-Client distribution. |
package Mail::Karmasphere::Client; use strict; use warnings; use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS %QUEUE @QUEUE $QUEUE); use Exporter; use Data::Dumper; use Convert::Bencode qw(bencode bdecode); use IO::Socket::INET; use Time::HiRes; use IO::Select; use Socket; use constant { IDT_IP4_ADDRESS => "ip4", IDT_IP6_ADDRESS => "ip6", IDT_DOMAIN_NAME => "domain", IDT_EMAIL_ADDRESS => "email", IDT_IP4 => "ip4", IDT_IP6 => "ip6", IDT_DOMAIN => "domain", IDT_EMAIL => "email", IDT_URL => "url", }; use constant { AUTHENTIC => "a", SMTP_CLIENT_IP => "smtp.client-ip", SMTP_ENV_HELO => "smtp.env.helo", SMTP_ENV_MAIL_FROM => "smtp.env.mail-from", SMTP_ENV_RCPT_TO => "smtp.env.rcpt-to", SMTP_HEADER_FROM_ADDRESS => "smtp.header.from.address", FL_FACTS => 1, FL_DATA => 2, FL_TRACE => 4, FL_MODELTRACE => 8, }; use constant { PROTO_TCP => 0+getprotobyname('tcp'), PROTO_UDP => 0+getprotobyname('udp'), }; BEGIN { @ISA = qw(Exporter); $VERSION = "2.18"; @EXPORT_OK = qw( IDT_IP4_ADDRESS IDT_IP6_ADDRESS IDT_DOMAIN_NAME IDT_EMAIL_ADDRESS IDT_IP4 IDT_IP6 IDT_DOMAIN IDT_EMAIL IDT_URL AUTHENTIC SMTP_CLIENT_IP SMTP_ENV_HELO SMTP_ENV_MAIL_FROM SMTP_ENV_RCPT_TO SMTP_HEADER_FROM_ADDRESS FL_FACTS FL_DATA FL_TRACE FL_MODELTRACE ); %EXPORT_TAGS = ( 'all' => \@EXPORT_OK, 'ALL' => \@EXPORT_OK, ); %QUEUE = (); @QUEUE = (); $QUEUE = 100; } # We can't use these until we set up the above variables. use Mail::Karmasphere::Query; use Mail::Karmasphere::Response; sub new { my $class = shift; my $self = ($#_ == 0) ? { %{ (shift) } } : { @_ }; if ($self->{Debug} and ref($self->{Debug}) ne 'CODE') { $self->{Debug} = sub { print STDERR Dumper(@_); }; } $self->{Debug}->('new', $self) if $self->{Debug}; unless ($self->{Socket}) { $self->{Proto} = 'udp' unless defined $self->{Proto}; $self->{PeerAddr} = $self->{PeerHost} unless defined $self->{PeerAddr}; $self->{PeerAddr} = 'query.karmasphere.com' unless defined $self->{PeerAddr}; $self->{PeerPort} = 8666 unless $self->{Port}; _connect($self); } return bless $self, $class; } sub _connect { my $self = shift; $self->{Debug}->('connect') if $self->{Debug}; $self->{Socket} = new IO::Socket::INET( Proto => $self->{Proto}, PeerAddr => $self->{PeerAddr}, PeerPort => $self->{PeerPort}, ReuseAddr => 1, ); unless (defined $self->{Socket}) { delete $self->{Socket}; my @args = map { "$_=" . $self->{$_} } keys %$self; die "Failed to create socket: $! (@args)"; } } sub query { my $self = shift; return $self->ask(new Mail::Karmasphere::Query(@_)); } sub _previous_socket { my $self = shift; return undef unless exists $self->{PreviousTime}; if ($self->{PreviousTime} + 10 > time()) { $self->{Debug}->('previous') if $self->{Debug}; return $self->{PreviousSocket} } delete $self->{PreviousSocket}; delete $self->{PreviousTime}; return undef; } sub _is_tcp { my ($self, $socket) = @_; return $socket->protocol == PROTO_TCP; } sub _send_real { my ($self, $data) = @_; my $socket = $self->{Socket}; if ($socket->protocol == PROTO_UDP) { if (length $data > 1024) { # Server's UDP_MAX $self->{Debug}->('fallback') if $self->{Debug}; $self->{PreviousSocket} = $self->{Socket}; $self->{PreviousTime} = time(); $self->{Proto} = 'tcp'; # XXX This loses the old socket and any queries # sent thereon. $self->_connect(); $socket = $self->{Socket}; } } # This can NOT be an else as we clobber the variable above. if ($socket->protocol == PROTO_TCP) { $self->{Debug}->('tcp prefix') if $self->{Debug}; $data = pack("N", length($data)) . $data; $self->{Debug}->('send_real', $data) if $self->{Debug}; } $socket->send($data) or die "Failed to send to socket: $!"; } sub send { my ($self, $query) = @_; die "Not blessed reference: $query" unless ref($query) =~ /[a-z]/; die "Not a query: $query" unless $query->isa('Mail::Karmasphere::Query'); $self->{Debug}->('send_query', $query) if $self->{Debug}; my $id = $query->id; my $packet = { _ => $id, }; $packet->{i} = $query->identities if $query->has_identities; $packet->{s} = $query->composites if $query->has_composites; $packet->{f} = $query->feeds if $query->has_feeds; $packet->{c} = $query->combiners if $query->has_combiners; $packet->{fl} = $query->flags if $query->has_flags; if (defined $self->{Principal}) { my $creds = defined $self->{Credentials} ?$self->{Credentials} : ''; $packet->{a} = [ $self->{Principal}, $creds ]; } $self->{Debug}->('send_packet', $packet) if $self->{Debug}; my $data = bencode($packet); $self->{Debug}->('send_data', $data) if $self->{Debug}; $self->_send_real($data); return $id; } sub _recv_real { my ($self, $socket) = @_; my $data; if ($socket->protocol == PROTO_TCP) { $socket->read($data, 4) or die "Failed to receive length from socket: $!"; my $length = unpack("N", $data); $data = ''; while ($length > 0) { my $block; my $bytes = $socket->read($block, $length) or die "Failed to receive data from socket: $!"; $data .= $block; $length -= $bytes; } $self->{Debug}->('recv_data', $data) if $self->{Debug}; } else { $socket->recv($data, 8192) or die "Failed to receive from socket: $!"; $self->{Debug}->('recv_data', $data) if $self->{Debug}; } my $packet = bdecode($data); die $packet unless ref($packet) eq 'HASH'; my $response = new Mail::Karmasphere::Response($packet); $self->{Debug}->('recv_response', $response) if $self->{Debug}; return $response; } sub recv { my ($self, $query, $timeout) = @_; my $id = ref($query) ? $query->id : $query; if (defined($id)) { if ($QUEUE{$id}) { $self->{Debug}->('recv_find', $id, $QUEUE{$id}) if $self->{Debug}; @QUEUE = grep { $_ ne $id } @QUEUE; return delete $QUEUE{$id}; } } else { if (@QUEUE) { $id = shift @QUEUE; return delete $QUEUE{$id}; } } $timeout = 10 unless defined $timeout; my $finish = time() + $timeout; my $select = new IO::Select(); $select->add($self->{Socket}); my $prev = $self->_previous_socket; $select->add($prev) if $prev; while ($timeout > 0) { my @ready = $select->can_read($timeout); if (@ready) { my $response = $self->_recv_real($ready[0]); $response->{query} = $query if ref $query; return $response unless defined $id; return $response if $response->id eq $id; my $rid = $response->id; push(@QUEUE, $rid); $QUEUE{$rid} = $response; if (@QUEUE > $QUEUE) { my $oid = shift @QUEUE; delete $QUEUE{$oid}; } } $timeout = $finish - time(); } print STDERR "Failed to receive from socket: $!\n"; return undef; } sub ask { my ($self, $query, $timeout) = @_; $timeout = 5 unless defined $timeout; for (0..2) { my $id = $self->send($query); my $response = $self->recv($query, $timeout); # $response->{query} = $query; return $response if $response; $timeout += $timeout; } return undef; }
1;