Thanks for this, works perfectly. And since I had so much trouble
finding examples of working code with SSL authentication and a
persistent connection to pass data over I figured I would post the
working code back so it's in the archive for others to poke at and
steal. This is mostly just a combo of examples from CPAN and the perl
POE cookbook but it seems to work as I wanted and allows multiple
persistent clients etc. With this sever I am authenticating every
command sent by the client against it's SSL cert. Probably un-needed
beyond the SSLifying of the initial connection but colour me paranoid.
Server code :
#!/usr/bin/perl
use strict;
use warnings;
use Socket;
use POE qw(
Wheel::SocketFactory
Wheel::ReadWrite
Driver::SysRW
Filter::SSL
Filter::Stackable
Filter::Stream
);
my $serverport = 2001;
my $debug = 1;
POE::Session->create(
inline_states => {
_start => \&parent_start,
_stop => \&parent_stop,
socket_birth => \&socket_birth,
socket_death => \&socket_death,
}
);
sub parent_start {
my $heap = $_[HEAP];
$heap->{debug} = 1;
if ($heap->{debug}) { print "= I = Starting POE session and
initialising socket\n"};
$heap->{listener} = POE::Wheel::SocketFactory->new(
BindAddress => '127.0.0.1',
BindPort => $serverport,
Reuse => 'yes',
SuccessEvent => 'socket_birth',
FailureEvent => 'socket_death',
);
if ($heap->{debug}) { print "= I = Socket initialised Waiting for
connections\n"};
}
# clean up if we shut down the server
sub parent_stop {
my $heap = $_[HEAP];
delete $heap->{listener};
delete $heap->{session};
if ($_[HEAP]->{debug}) { print "= I = Listener Death!\n"};
}
# open the socket for the remote session.
sub socket_birth {
my ($socket, $address, $port) = @_[ARG0, ARG1, ARG2];
$address = inet_ntoa($address);
print "\n= S = Socket birth client connecting\n" if $debug;
POE::Session->create(
inline_states => {
_start => \&socket_success,
_stop => \&socket_death,
socket_input => \&socket_input,
socket_death => \&socket_death,
},
args => [$socket, $address, $port],
);
}
# close the socket session when the user exits.
sub socket_death {
my $heap = $_[HEAP];
if ($heap->{socket_wheel}) {
print "= S = Socket death, client disconnected\n" if $debug;
delete $heap->{socket_wheel};
}
}
# yay! we sucessfully opened a socket. Set up the session.
sub socket_success {
my ($heap, $kernel, $connected_socket, $address, $port) =
@_[HEAP, KERNEL, ARG0, ARG1, ARG2];
$heap->{debug} = 1;
print "= I = CONNECTION from $address : $port \n" if
$heap->{debug};
if ($_[HEAP]->{debug}) { print "= SSL = Creating SSL Object\n"};
## make your own certificates to use here. Standard Cert
creation. I used easy-rsa which is part of the openvpn package. Makes it
really easy. As defined here:
http://www.hermann-uwe.de/blog/howto-using-openvpn-on-debian-gnu-linux
to the point of building client1 then you can stop.
$heap->{sslfilter} = POE::Filter::SSL->new(
crt => 'keys/server.crt',
key => 'keys/server.key',
cacrt => 'keys/ca.crt',
cipher => 'DHE-RSA-AES256-GCM-SHA384:AES256-SHA',
debug => 1,
clientcert => 1
);
);
$heap->{socket_wheel} = POE::Wheel::ReadWrite->new(
Handle => $connected_socket,
Driver => POE::Driver::SysRW->new(),
Filter => POE::Filter::Stackable->new(Filters => [
$heap->{sslfilter},
POE::Filter::Stream->new(),
]),
InputEvent => 'socket_input',
ErrorEvent => 'socket_death',
);
if ($_[HEAP]->{debug}) { print "= SSL = SSL connection
established!\n"};
}
sub socket_input {
my ($heap, $kernel, $buf) = @_[HEAP, KERNEL, ARG0];
my $response;
my $sub;
my (@certid) = ($heap->{sslfilter}->clientCertIds());
my $content = '';
$heap->{debug} = 1;
chomp($buf);
if ($_[HEAP]->{debug}) {print "= I = Clint command received :
$buf\n"};
if ($_[HEAP]->{debug}) { print "= SSL = Authing Client
Command\n"};
if ($heap->{sslfilter}->clientCertValid()) {
if ($_[HEAP]->{debug}) { print "= SSL = Client
Certificate Valid, Authorised\n"};
# this is where you take your input from the client, do
something with it and send something back. I am getting temperature
values and sending them to the client. YMMV
if ($buf eq "temp") {
$content = `/home/pi/c0de/sht15/temp-munin.py`;
} else {
$content = "Unknown request\n";
}
my $response = $content;
if ($_[HEAP]->{debug}) { print "= I = Sending Client
Result: $content\n"};
$heap->{socket_wheel}->put($response);
} else {
# User is a bad bad person. No cookie!
if ($_[HEAP]->{debug}) { print "= SSL = Client
Certificate Invalid! Rejecting command and disconnecting!\n"};
$content = "INVALID CERT! Connection rejected!\n";
my $response = $content;
$heap->{socket_wheel}->put($response);
if ($_[HEAP]->{debug}) { print "= I = Sending Client
Result: $content\n"};
$kernel->delay(socket_death => 1);
}
}
$poe_kernel->run();
## END OF LINE
Client code:
#!/usr/bin/perl
use warnings;
use strict;
use POE qw(Component::Client::TCP Filter::SSL);
POE::Component::Client::TCP->new(
RemoteAddress => "localhost",
RemotePort => 2001,
## make your own certificates to use here. Standard Cert creation. I
used easy-rsa which is part of the openvpn package. Makes it really
easy. As defined here:
http://www.hermann-uwe.de/blog/howto-using-openvpn-on-debian-gnu-linux
to the point of building client1 then you can stop.
Filter => [ "POE::Filter::SSL", crt => 'keys/client1.crt',
key => 'keys/client1.key', client => 1 ],
Connected => sub {
$_[HEAP]->{next_alarm_time} = int(time()); # Immediately
trigger an alarm
$_[KERNEL]->alarm(tick => $_[HEAP]->{next_alarm_time});
},
ServerInput => sub {
print "from server: ".$_[ARG0]."\n";
},
InlineStates => {
tick => sub {
#send the server a command server input will
then capture the response while this bit waits 10 seconds and then does
it all again. Thanks to Gunnar on the POE mailing list for help with
this.
my $command = "temp";
print "Sending to server : $command\n";
$_[HEAP]{server}->put($command);
$_[HEAP]->{next_alarm_time}+=10;
$_[KERNEL]->alarm(tick =>
$_[HEAP]->{next_alarm_time});
},
}
);
POE::Kernel->run();
## END OF LINE
Thanks for the help and I hope this helps someone in the future.
Kai