replaced the old nonfunctional perl SAM lib with postman's new

implementation (thanks postman!)
This commit is contained in:
jrandom
2005-11-24 09:41:01 +00:00
committed by zzz
parent dbb4b3d0c2
commit fc858bc950
7 changed files with 1030 additions and 435 deletions

View File

@ -1,321 +0,0 @@
#!/usr/bin/perl
## Copyright 2004 Brian Ristuccia. This program is Free Software;
## You can redistribute it and/or modify it under the same terms as
## Perl itself.
package Net::SAM;
@ISA = ( "IO::Socket::INET" );
use strict;
use POSIX;
use Switch;
use IO::Socket;
use IO::Select;
#use Net::SAM::StreamSession;
#use Net::SAM::DatagramSession;
#use Net::SAM::RawSession;
sub new {
my ($class) = shift;
my $type = ref($class) || $class;
my $self = $type->SUPER::new("127.0.0.1:7656");
${*$self}->{incomingraw} = [];
# Connect us to the local SAM proxy.
# my $samsock = IO::Socket::INET->new('127.0.0.1:7657');
#$self->{samsock}=$samsock;
# Say hello, read response.
$self->SUPER::send("HELLO VERSION MIN=1.0 MAX=1.0\n");
while (! ${*$self}->{greeted}) {
$self->readprocess();
}
print "Created SAM object\n";
return $self;
}
sub lookup {
my $self = shift;
my $name= shift;
$self->SUPER::send("NAMING LOOKUP NAME=$name\n");
undef ${*$self}->{RESULT};
while (! ${*$self}->{RESULT}) {
$self->readprocess();
}
if ( ${*$self}->{RESULT} == "OK" ) {
return ${*$self}->{VALUE};
} else {
return undef;
}
}
#sub createsession {
# my ($self) = shift;
# my ($sesstype) = shift;
# print $self->{samsock} "SESSION CREATE STYLE=$SESSTYPE DESTINATION=$DEST, DIRECTION=
#}
#sub waitfor {
# my ($self) = shift;
# my ($prefix) = shift;
# my ($response) = <$samsock>;#
# if $response =~
#}
sub readprocesswrite {
my $self = shift;
$self->readprocess();
$self->dowrite();
}
sub doread {
my $self = shift;
my $rv;
my $data;
$rv = $self->recv($data, $POSIX::BUFSIZE, 0);
if ( defined($rv) && ( length($data) >= 1 ) ) {
# We received some data. Put it in our buffer.
${*$self}->{inbuffer} += $data;
} else {
# No data. Either we're on a non-blocking socket, or there
# was an error or EOF
if ( $!{EAGAIN} ) {
return 1;
} else {
# I suppose caller can look at $! for details
return undef;
}
}
}
sub dowrite {
my $self = shift;
my $rv;
my $data;
$rv = $self->send(${*$self}->{outbuffer}, 0);
if ( ! defined($rv) ) {
warn "SAM::dowrite - Couldn't write for no apparent reason.\n";
return undef;
}
if ( $rv == length(${*$self}->{outbuffer}) || $!{EWOULDBLOCK} ) {
substr(${*$self}->{outbuffer},0, $rv) = ''; # Remove from buffer
# Nuke buffer if empty
delete ${*$self}->{outbuffer} unless length(${*$self}->{outbuffer});
} else {
# Socket closed on us or something?
return undef;
}
}
sub messages {
my $self = shift;
return @{ ${*$self}->{messages} };
}
sub queuemessage {
my $self = shift;
my $message = shift;
push @{ ${*$self}->{messages} } , $message;
}
sub unqueuemessage {
my $self = shift;
return unshift(@{ ${*$self}->{messages} } );
}
sub readprocess {
my $self = shift;
$self->doread();
$self->process();
}
sub process {
my $self = shift;
my %tvhash;
my $payload;
# Before we can read any new messages, if an existing message has payload
# we must read it in. Otherwise we'll create garbage messages containing
# the payload of previous messages.
if ( ${*$self}->{payloadrequired} >= 1 ) {
if ( length( ${*$self}->{inbuffer} ) >= ${*$self}->{payloadrequired} ) {
# Scarf payload from inbuffer into $payload
$payload = substr(${*$self}->{inbuffer}, 0,
${*$self}->{payloadrequired});
# Nuke payload from inbuffer
substr(${*$self}->{inbuffer}, 0,
${*$self}->{payloadrequired} ) = '';
# Put message with payload into spool
push @{ ${*$self}->{messages} } ,
${*$self}->{messagerequiringpayload}.$payload;
# Delete the saved message requiring payload
delete ${*$self}->{messagerequiringpayload};
} else {
# Insufficient payload in inbuffer. Try again later.
return 1;
}
}
if ( ${*$self}->{inbuffer} =~ s/(.*\n)// ) {
%tvhash = $self->_hashtv($1); # Returns a tag/value hash
if ( $tvhash{SIZE} ) {
# We've got a message with payload on our hands. :(
${*$self}->{payloadrequired} = $tvhash{SIZE};
${*$self}->{messagerequiringpayload} = $1;
return 1; # Could call ourself here, but we'll get called again.
} else {
push @{ ${*$self}->{messages} } , $1;
}
}
return 1;
}
# sub junk {
# print "readprocess: " . $self->connected() . "\n";
# # May block if the SAM bridge gets hosed
# my $response = <$self>;
# print "readprocess: $!" . $self->connected() . "\n";
# chomp $response;
# my ($primative, $more, $extra) = split (' ', $response, 3);
# $primative = uc($primative);
# print "readprocess: " . $self->connected() . " -- $primative -- $more -- $extra\n";
# switch ($primative) {
# case "HELLO" {
# if ($more !~ m/REPLY/ ) { die ("Bogus HELLO response") }
# if ($extra =~ m/NOVERSION/ ) {
# die("SAM Bridge Doesn't support my version") ;
# }
# $self->_hashtv($extra);
# ${*$self}->{greeted} = 1;
# };
# case "SESSION" {
# if ( $more !~ m/STATUS/ ) {
# die("Bogus SESSION response");
# }
# $self->_hashtv($extra);
# }
# case "STREAM" {};
# case "DATAGRAM" {
# if ( $more !~ m/RECEIVE/ ) {
# die("Bogus DATAGRAM response.");
# }
# $self->_hashtv($extra);
# push @{ ${*$self}->{incomingdatagram } },
# [ ${*$self}->{DESTINATION},
# $self->_readblock(${*$self}->{SIZE}) ];
# };
# case "RAW" {
# if ( $more !~ m/RECEIVE/ ) {
# die("Bogus RAW response.");
# }
# $self->_hashtv($extra);
# push @{ $self->{incomingraw} }, $self->_readblock($self->{SIZE});
# };
# case "NAMING" {
# if ( $more !~ m/REPLY/ ) {
# die("Bogus NAMING response");
# }
# $self->_hashtv($extra);
# };
# case "DEST" {};
# }
# return 1;
# }
sub getfh {
# Return the FH of the SAM socket so apps can select() or poll() on it
my $self = shift;
return $self->{samsock};
}
sub _readblock {
my $self = shift;
my $size = shift;
my $chunk;
my $payload;
while ( $size > 1 ) {
# XXX: May block. No error checking.
print "readblock: $size\n";
$size -= $self->SUPER::recv($chunk, $size);
$payload .= $chunk;
}
return $payload;
}
sub _hashtv {
my $self = shift;
my $tvstring = shift;
my $tvhash;
while ( $tvstring =~ m/(\S+)=(\S+)/sg ) {
$tvhash->{$1}=$2;
print "hashtv: $1=$2\n"
}
return $tvhash;
}
sub DESTROY {
# Do nothing yet.
}
#sub StreamSession {
# my $self = shift;
# return Net::SAM::StreamSession->new($self);
#}
#sub DatagramSession {
# return Net::SAM::DatagramSession->new($self);
#}
#sub RawSession {
# return Net::SAM::RawSession->new($self);
#}
1;

View File

@ -1,48 +0,0 @@
#!/usr/bin/perl
package Net::SAM::DatagramSession;
use Net::SAM;
@ISA = ("Net::SAM");
sub new {
my ($class) = shift;
my ($dest , $direction, $options) = shift;
my $self = $class->SUPER::new(@_);
$self->SUPER::send("SESSION CREATE STYLE=DATAGRAM DESTINATION=$dest DIRECTION=$direction $options\n");
undef ${*$self}->{RESULT};
while ( ! ${*$self}->{RESULT} ) {
$self->readprocess() || return undef;
}
if ( ${*$self}->{RESULT} == "OK" ) {
return $self;
} else {
return undef; # sorry.
}
}
sub send {
my $self = shift;
my $destination = shift;
my $message = shift;
my $size = length($message);
$self->SUPER::send("DATAGRAM SEND DESTINATION=$destination SIZE=$size\n$message");
}
sub receive {
my $self = shift;
# Shift one off the fifo array. Returns undef if none wait.
return shift @{ $self->{incomingdatagram} };
}
1;

View File

@ -1,45 +0,0 @@
#!/usr/bin/perl
package Net::SAM::RawSession;
use Net::SAM;
@ISA = ("Net::SAM");
sub new {
my ($class) = shift;
my ($dest , $direction, $options) = shift;
my $self = $class->SUPER::new(@_);
$self->send("SESSION CREATE STYLE=RAW DESTINATION=$dest DIRECTION=$direction $options\n");
undef $self->{result};
while ( ! $self->{RESULT} ) {
$self->readprocess();
}
if ( $self->{RESULT} == "OK" ) {
return $self;
} else {
return 0; # sorry.
}
}
sub send {
my $self = shift;
my $destination = shift;
my $message = shift;
my $samsock = $self->{samsock};
my $size = length($message);
print $samsock "RAW SEND DESTINATION=$destination SIZE=$size\n$message";
}
sub receive {
my $self = shift;
# Shift one off the fifo array. Returns undef if none wait.
return shift @{ $self->{incomingraw} };
}
1;

View File

@ -1,3 +0,0 @@
#!/usr/bin/perl
1;

108
apps/sam/perl/README Normal file
View File

@ -0,0 +1,108 @@
# BASIC Perl SAM Module
# created 2005 by postman (postman@i2pmail.org)
1. What does it do?
The SAM module is a little Perl add-on that - on one side -
establishes communication with a I2P router's (http://www.i2p.net) SAM bridge
(Simple anonymous messaging ( http://www.i2p.net/sam)). On the
other side it exposes a simple socket like interface to the user.
Over this interface the user can send or receive datastreams from I2P
destinations as if he would communicate with a normal IP socket.
The SAM module can be integrated into perl scripts that
want to communicate with I2P services.
2. Is this code usable?
This perl module should be considered as proof-of-concept
quality. It did surely work for me and my test setups, but
it might not work at all on your system. If you run into problems
you can contact me.
3. Does ist support DATAGRAM and RAW sessions?
No, at the moment the module only supports STREAM sessions.
Support for other session types might be added in the future.
4. How to install it?
Create a Subfolder called I2P in your Perl Installation's Net Module
folder (i.e. /usr/lib/perl5/5.8.4./Net/I2P ) and copy the module there.
You can now use it with use Net::I2P::SAM.
The module only depends on Net::IO::Socket for operations. This
should be already installed.
5. How to debug?
You can switch on debugging with the constructor ( see below ).
6. How to use it?
$sam = new Net::I2P::SAM('127.0.0.1','7656',1);
# you can omit host/port - then the defult is assumed
# the 3rd argument is the debugging switch. If you switched it on
# there'll be a default debug in /tmp/sam-debug
# $sam will now either contain a object reference or 0
# if it's 0 then we coudl not talk to the SAM bridge at all ( connect failed)
# or we could not agree to a version
# next we can tune the tunnel settings we want for this session:
# The syntax is just like the one used on www.i2p.net/sam
$sam->change_settings("inbound.length=1,inbound.lenghVariance=0,outbound.length=1,outbound.lengthVariance=0,inbound.nickname=fun,outbound.nickname=fun");
# next we open a new session.
# only stream is supported
# most of the time we use a transient destination
# otherwise state the hosts.txt name you want to use as in your session
# direction is most of the times both :)
# this cab return 1 for success or 0 for failure
$sam->create_session("STREAM","TRANSIENT","BOTH");
# now connect to our service
$sam->connect($sam->lookup("myservice.i2p"));
# or
$sam->connect("I2PDESTINATIONKEY.....AAAA");
# if this returns 1 - we got a stream session and can now receive and send data
# otherwise consult the debug.
# Send data is just like the the raw perl send
# we send the data as scalar var and optional flags (most of the times 0)
$sam->send($data,0);
# receiving data is similar to the perl recv
# we define the mac number of bytes and optional flags
$indata = $sam->receive(512,0);
# close the session
$sam->close();
# that's the most important things to know.

922
apps/sam/perl/SAM.pm Normal file
View File

@ -0,0 +1,922 @@
# Perl Basic SAM module
# created 2005 by postman (postman@i2pmail.org)
# This code is under GPL.
# This code is proof of concept
package Net::I2P::SAM;
require 5.001;
use strict;
use POSIX;
# we do not extend the IO::Socket:INET Package
# we just use it, so we keep our stuff sorted
# This is a testsetup - the next release but
# be an extension of IO::Socket::INET;
use IO::Socket::INET;
use vars qw($VERSION @ISA @EXPORT);
sub new {
my ($this,$host,$port,$debug) = @_;
my $classname=ref($this) || $this;
my $self = {};
bless($self,$classname);
#%{$self->{conf}} = The hash where we store tunnel / SAM Settings
#$self->{debug} = Whether we should output debugging lines ( this is very helpful when having problems)
#$self->{debugfile} = Where to log
#%{$self->{samreply}} = This hash stores the key=VALUE pairs of sam reply
#$self->{sock} = The INET Socket over which we talk to the SAM bridge
#$self->{inbuffer} = a simple layouted inbuffer
#$self->{outbuffer} = a simple layouted outbuffer
# ( the buffers are for dealing with differences between user wanted in/outputsizes
# and what we're able to deliver on a machine side)
#$self->{sessiontype} = unused ( will be used when we support different sessiontypes )
#$self->{lookupresult}= contains the result of a SAM host/userhost naming lookup;
#$self->{samerror} = The human readable SAM error message ( if wanted )
#$self->{streamid} = The virtual streamid. It will be created upon connect;
#$self->{bytestoread} = contains the reported size of a packet from sam
#$self->{bytestosend} = contains the wanted size of a packet to sam
#$self->{hasbanner} = is set to 1 when we receive a greeting string upon connect
#
%{$self->{conf}}=();
if($debug==1) {
$self->{debug}=1;
}
$self->{debugfile}="/tmp/sam-debug";
$self->{debughandle}=undef;
%{$self->{samreply}}=undef;
$self->{sock}=undef;
$self->{inbuffer}=undef;
$self->{outbuffer}=undef;
$self->{sessiontype}=undef;
$self->{lookupresult}=undef;
$self->{samerror}=undef;
$self->{streamid}=undef;
$self->{bytestoread}=undef;
$self->{bytestosend}=undef;
$self->{hasbanner}=1;
# state == -1 (no socket exists)
# state == 0 (socket exists, but we're not helloed)
# state == 1 (socket exists, and we're helloed)
# state == 200 ( we bound a session)
# state == 250 ( we have a virtual stream)
$self->{state}=-1;
# if the user has specified a host/port for contacting the SAM
# Bridge, we'll override the defaults. Otherwise we just use
# defaults.
#
if($host) {
${$self->{conf}}{host}=$host;
} else {
${$self->{conf}}{host}="127.0.0.1";
}
if($port) {
${$self->{conf}}{port}=$port;
} else {
${$self->{conf}}{port}=7656;
}
# defaults for the tunnelparameters
# see www.i2p.net/sam
${$self->{conf}}{iblength}=2;
${$self->{conf}}{oblength}=2;
${$self->{conf}}{ibquant}=2;
${$self->{conf}}{obquant}=2;
${$self->{conf}}{ibbackup}=0;
${$self->{conf}}{obbackup}=0;
${$self->{conf}}{ibvariance}=0;
${$self->{conf}}{obvariance}=0;
${$self->{conf}}{iballowzero}="true";
${$self->{conf}}{oballowzero}="true";
${$self->{conf}}{ibduration}=600000;
${$self->{conf}}{obduration}=600000;
${$self->{conf}}{ibnickname}="SAM-Perl-Destination";
${$self->{conf}}{obnickname}="SAM-Perl-Destination";
# ok, let's open a simple debug file
# if the user wants us
if($self->{debug} == 1) {
if ( ! open (LOGFILE,">" .$self->{debugfile})) {
print "constructor: Cannot open debugging file, switching debugging off.";
$self->{debug}=0;
}
# switch off the nerveracking buffer for the debugfile
select((select(LOGFILE), $| = 1)[0]);
}
# ok now, lets move to the manager
# he manages connecting and hello
# if the proc_mgr returns 1 the user will get our
# object reference. if not, he'll just get a 0.
if($self->proc_mgr()) {
return $self;
} else {
return 0;
}
}
sub proc_mgr {
my ($self) = @_;
my $return=undef;
if ($self->{state} == -1) {
$self->log("Debug: SAM::proc_mgr(): Opening Socket Connection to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
$return=$self->sam_connect();
if($return==0) {
return 0;
}
if($return == 1) {
$self->log("Debug: SAM::proc_mgr(): State Transition -1 => 0");
$self->{state}=0;
}
}
if($self->{state}==0) {
if(!$self->hello()) {
$self->log("Debug: SAM::proc_mgr(): Closing Socket");
$self->{sock}->close();
$self->log("State SAM::proc_mgr(): Transition 0 => -1");
return 0;
}
}
return 1;
}
sub change_settings {
my ($self,$list)=@_;
my (@tochange,$id,$v,$k);
# we cannot change the settings if we have a session already
# so our state must be 1
if($self->{state} >1) {
$self->log("Debug: SAM::change_settings(): Cannot change tunnel settings after establishing a session.");
return 0;
}
@tochange=split(",",$list);
foreach $id (@tochange) {
($k,$v)=split("=",$id);
lc($v);
lc($k);
$self->log("Debug: SAM::change_settings(): Parsed Setting: Key: $k - Value: $v");
if($k eq "inbound.length" || $k eq "outbound.length") {
# make v an int
$v*1;
if ($v >3 || $v < 0) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
return 0;
}
if(${$self->{conf}}{iballowzero} eq "false" && $k eq "inbound.length" && $v==0) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
return 0;
}
if(${$self->{conf}}{oballowzero} eq "false" && $k eq "outbound.length" && $v==0) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
return 0;
}
if($k eq "inbound.length") {
${$self->{conf}}{iblength}=$v;
}
if($k eq "outbound.length") {
${$self->{conf}}{oblength}=$v;
}
}
if($k eq "inbound.quantity" || $k eq "outbound.quantity") {
$v*1;
if($v < 0 || $v >3 ) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
return 0;
}
if ($k eq "inbound.quantity") {
${$self->{conf}}{ibquant}=$v;
}
if($k eq "outbound.quantity") {
${$self->{conf}}{obquant}=$v;
}
}
if($k eq "inbound.backupquantity" || $k eq "outbound.backupquantity") {
$v*1;
if($v < 0 || $v >2 ) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
return 0;
}
if ($k eq "inbound.backupquantity") {
${$self->{conf}}{ibbackup}=$v;
}
if($k eq "outbound.backupquantity") {
${$self->{conf}}{obbackup}=$v;
}
}
if($k eq "inbound.lengthvariance" || $k eq "outbound.lengthvariance") {
$v*1;
if($v < -2 || $v >2 ) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
return 0;
}
if ($k eq "inbound.lengthvariance") {
${$self->{conf}}{ibvariance}=$v;
}
if($k eq "outbound.lengthvariance") {
${$self->{conf}}{ibvariance}=$v;
}
}
if($k eq "inbound.duration" || $k eq "outbound.duration") {
$v*1;
if($v < 300000 || $v >1200000 ) {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
return 0;
}
if ($k eq "inbound.duration") {
${$self->{conf}}{ibduration}=$v;
}
if($k eq "outbound.duration") {
${$self->{conf}}{obduration}=$v;
}
}
if($k eq "inbound.nickname" || $k eq "outbound.nickname") {
$v=substr($v,0,20);
if ($k eq "inbound.nickname") {
${$self->{conf}}{ibnickname}=$v;
}
if($k eq "outbound.nickname") {
${$self->{conf}}{obnickname}=$v;
}
}
if($k eq "inbound.allowzerohop" || $k eq "outbound.allowzerohop") {
if($v ne "true" && $v ne "false") {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (must be boolean)");
return 0;
}
if(${$self->{conf}}{iblength} ==0 && $k eq "inbound.allowzerohop" && $v eq "false") {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
return 0;
}
if(${$self->{conf}}{oblength} == 0 && $k eq "outbound.allowzerohop" && $v eq "false") {
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
return 0;
}
if ($k eq "inbound.allowzerohop") {
${$self->{conf}}{iballowzero}=$v;
}
if($k eq "outbound.allowzerohop") {
${$self->{conf}}{oballowzero}=$v;
}
}
$self->log("Debug: SAM::change_settings(): Setting $k to $v");
}
return 1;
}
sub hello {
my ($self) = @_;
my $greeting="HELLO VERSION MIN=1.0 MAX=1.0\n";
my $return=undef;
my $return2=undef;
$self->{outbuffer} .= $greeting;
$return=$self->raw_send();
if($return == 1) {
if($self->raw_read()) {
if($self->parse_sam("HELLO")) {
$self->{state}=1;
$self->log("Debug: SAM::hello(): State Transition 0 => 1");
delete $self->{inbuffer};
delete $self->{outbuffer};
return 1;
}
} else {
$self->log("Error: SAM::hello(): HELLO Failed. Cannot read HELLO response");
return 0;
}
}
if($return == 0) {
$self->log("Error: SAM::hello(): HELLO Failed. Cannot send HELLO String");
return 0;
}
}
sub create_session {
my ($self,$type,$destination,$direction) = @_;
my $line="SESSION CREATE ";
my $return;
uc($type);
# WE ARE ONLY DOING STREAMS ATM
if ($type ne "STREAM") {
$self->log("Error: SAM::create_session(): SESSION failed. Only session of STREAM type are allowed");
return 0;
}
if(length($destination)==0) {
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on destination to TRANSIENT.");
$destination="TRANSIENT";
}
$line.="STYLE=$type DESTINATION=$destination";
uc($direction);
if($direction ne "BOTH" && $direction ne "CREATE" && $direction ne "RECEIVE") {
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on direction to BOTH.");
$direction="BOTH";
}
$line .= " DIRECTION=$direction";
$line .= " inbound.length=".${$self->{conf}}{iblength}." outbound.length=".${$self->{conf}}{oblength};
$line .= " inbound.quantity=".${$self->{conf}}{ibquant}." outbound.quantity=".${$self->{conf}}{obquant};
$line .= " inbound.backupQuantity=".${$self->{conf}}{ibbackup}." outbound.backupQuantity=".${$self->{conf}}{obbackup};
$line .= " inbound.lengthVariance=".${$self->{conf}}{ibvariance}." outbound.lengthVariance=".${$self->{conf}}{obvariance};
$line .= " inbound.duration=".${$self->{conf}}{ibduration}." outbound.duration=".${$self->{conf}}{obduration};
$line .= " inbound.nickname=".${$self->{conf}}{ibnickname}." outbound.nickname=".${$self->{conf}}{obnickname};
$line .= " inbound.allowZeroHop=".${$self->{conf}}{iballowzero}." outbound.allowZeroHop=".${$self->{conf}}{oballowzero};
$line .="\n";
$self->{outbuffer}.=$line;
$return=$self->raw_send();
if($return == 1) {
if($self->raw_read()) {
if($self->parse_sam("SESSION ")) {
$self->{state}=200;
$self->log("Debug: SAM::create_session(): State Transition 1 => 200");
# flush the whole inbuffer;
delete $self->{inbuffer};
delete $self->{outbuffer};
return 1;
}
} else {
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot read SAM Response");
return 0;
}
}
if($return == 0) {
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot send SESSION String");
return 0;
}
}
sub parse_sam {
# this is the main function that parses all SAM replies
# depending on wanted action and state we'll set different
# properties like $self->{bytestoread} etc.
# parse_sam does not CUT OUT SAM messages from the payloads
# (look at $self->recv for that)
#
my ($self,$state) = @_;
my (@data,$id,$k,$v);
%{$self->{samreply}}=();
uc($state);
if( $self->{inbuffer} =~ /^(.[^ ]*) (.[^ ]*) (.*)$/m ) {
${$self->{samreply}}{COMMAND}=$1;
${$self->{samreply}}{REPLY}=$2;
@data=split(" ",$3);
foreach $id (@data) {
($k,$v)=split("=",$id);
#print "k: $k - v: $v\n";
${$self->{samreply}}{$k}=$v;
}
} else {
$self->log("Error: SAM::parse_sam(): Could not parse the SAM Reply. Has the specs changed?");
return 0;
}
if($state eq "HELLO") {
if (${$self->{samreply}}{COMMAND} ne "HELLO") {
$self->log("Error: SAM::parse_sam(): We're in state HELLO but got no proper response from SAM");
return 0;
}
if(${$self->{samreply}}{REPLY} eq "REPLY") {
if(${$self->{samreply}}{RESULT} eq "OK") {
$self->log("Debug: SAM::parse_sam(): Got a OK result for HELLO");
return 1;
} else {
$self->log("Error :SAM::parse_sam(): Got no OK Result for HELLO");
return 0;
}
} else {
$self->log("Error: SAM::parse_sam(): Unknown Reply type for HELLO dialogue");
return 0;
}
}
if($state eq "SESSION") {
if (${$self->{samreply}}{COMMAND} ne "SESSION") {
$self->log("Error: SAM::parse_sam(): We're in state SESSION but got no proper response from SAM");
return 0;
}
if(${$self->{samreply}}{REPLY} eq "STATUS") {
if(${$self->{samreply}}{RESULT} eq "OK") {
$self->log("Debug: SAM::parse_sam(): Got a OK result for SESSION");
return 1;
} else {
$self->log("Error: SAM::parse_sam(): Got no OK Result for SESSION: ".${$self->{samreply}}{RESULT});
return 0;
}
} else {
$self->log("Error: SAM::parse_sam(): Unknown Reply type for SESSION dialogue");
return 0;
}
}
if($state eq "NAMING") {
if (${$self->{samreply}}{COMMAND} ne "NAMING") {
$self->log("Error: SAM::parse_sam(): We're in state NAMING but got no proper response from SAM");
return 0;
}
if(${$self->{samreply}}{REPLY} eq "REPLY") {
if(${$self->{samreply}}{RESULT} eq "OK") {
$self->log("Debug: SAM::parse_sam(): Got a OK result for NAMING");
$self->{lookupresult}=${$self->{samreply}}{VALUE};
return 1;
} else {
$self->log("Error: SAM::parse_sam(): Got no OK Result for NAMING: ".${$self->{samreply}}{RESULT});
return 0;
}
} else {
$self->log("Error: SAM::parse_sam(): Unknown Reply type for NAMING dialogue");
return 0;
}
}
if($state eq "STREAM") {
if (${$self->{samreply}}{COMMAND} ne "STREAM") {
$self->log("Error: SAM::parse_sam(): We're in state STREAM but got no proper response from SAM");
return 0;
}
# CREATING STREAMS
if(${$self->{samreply}}{REPLY} eq "STATUS") {
if(${$self->{samreply}}{RESULT} eq "OK") {
$self->log("Debug: SAM::parse_sam(): STREAM STATUS OK - Next action is awaited");
return 1;
} else {
$self->log("Error: SAM::parse_sam(): STREAM STATUS NOT OK.: ".${$self->{samreply}}{RESULT});
if(length(${$self->{samreply}}{MESSAGE}) == 0) {
$self->{samerror}=${$self->{samreply}}{RESULT};
} else {
$self->{samerror}=${$self->{samreply}}{MESSAGE};
}
return 0;
}
}
# SEND/RECEIVING STREAMS
# this can happen directly after a connect
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
if(${$self->{samreply}}{SIZE} > 0) {
$self->log("Debug: SAM::parse_sam(): SAM notify: RECEIVED Data. SIZE=".${$self->{samreply}}{SIZE});
$self->{bytestoread}=${$self->{samreply}}{SIZE};
return 1;
} else {
$self->log("Error: SAM::parse_sam(): Received empty payload");
return 0;
}
}
# STREAMS are closed - bad thing
# this can happen directly after a connect
if(${$self->{samreply}}{REPLY} eq "CLOSED") {
$self->log("Error: SAM::parse_sam(): Stream is closed: We need to interupt the session");
return 0;
}
}
}
sub raw_send {
# this function sends a crafted SAM request + payload to the
# SAM socket
my ($self) = @_;
my $return;
$self->log("Debug: SAM::raw_send(): >>> ".$self->{outbuffer});
$return = $self->{sock}->send($self->{outbuffer},0);
if(! defined($return)) {
$self->log("Error: SAM::raw_send(): Cannot send to Socket");
$self->close();
return 0;
}
if ( $return == length($self->{outbuffer}) || $!{EWOULDBLOCK} ) {
substr($self->{outbuffer},0, $return) = '';
delete $self->{outbuffer} unless length($self->{outbuffer});
return 1;
} else {
$self->log("Error :SAM::raw_send(): Could send, but length does not match. Closing");
$self->close();
return 0;
}
}
sub raw_read {
my ($self,$size) = @_;
my $input;
my $data;
# this reads SAM replies from the SAM socket and fills the
# inbuffer
if(!$size || $size > POSIX::BUFSIZ) {
$size=POSIX::BUFSIZ;
}
$input = $self->{sock}->recv($data, $size, 0);
if(defined($input) && length($data) >= 1) {
$self->log("Debug: SAM::raw_read(): <<< $data");
if(length($self->{inbuffer}) == 0 ) {
$self->{inbuffer} = $data;
} else {
$self->{inbuffer} .= $data;
}
return 1;
} else {
if ( $!{EAGAIN} ) {
$self->{bytestoread}=0;
return 1;
} else {
return 0;
}
}
}
sub sam_connect {
# bsic connect to the sam bridge socket itself
my ($self)=@_;
my $return=undef;
$return=$self->{sock}=IO::Socket::INET->new(${$self->{conf}}{host}.":".${$self->{conf}}{port});
if($return==0) {
$self->log("Debug: SAM::sam_connect(): Connection failed to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
return 0;
} else {
$self->log("Debug: SAM::sam_connect(): Connection established to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
return 1;
}
}
sub send {
# the public fumction to send data to sam
# the user gives his payload and we create
# valid SAM requests + payload from it ( as much as needed)
my ($self,$content,$flags)=@_;
my $return;
my $maxsize=(POSIX::BUFSIZ-100);
if($self->{state}!=250) {
$self->log("Error: SAM::send(): wrong state for send command: Needed:250 Recent:".$self->{state});
return 0;
}
# ok, what can happen?
# it could be that $content is far bigger than
# POSIX::BUFSIZ; so we need to do a little loop
# apart from that sending is in our hand
if(length($content) > $maxsize) {
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=$maxsize\n".substr($content,0,$maxsize);
$content=substr($content,$maxsize,length($content));
} else {
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=".length($content)."\n".$content;
}
if( $self->raw_send()) {
return 1;
} else {
$self->log("Error: SAM::send(): Could not send. Closing Link");
}
}
sub recv {
my($self,$varname,$size,$flags)=@_;
my $return;
my $tunebuffer;
my $counter;
my $chunk;
# main recv wrapper. We need to invest a few thoughts
# for this. To the user it will be like a $socket->recv
# (hopefully)
# at first some sanity checks
if(!$flags) {
$flags=0;
}
$self->{bytestoread}=0;
# size must not exceed The posix limit;
if(!$size || $size > POSIX::BUFSIZ) {
$self->log("Warn: SAM::recv(): Setting buffersize to POSIX::BUFSIZ");
$size=POSIX::BUFSIZ;
}
# nobody should call is prior to state 250
if($self->{state}!=250) {
$self->log("Error: SAM::recv(): wrong state for rcv command: Needed:250 Recent:".$self->{state});
return 0;
}
# we have a greeting banner left from connect
# flush it to the user. This can happen on several services
# like smtp/pop3/nntp but not on HTTP and other stuff
if($self->{hasbanner}) {
#print "D: ".$self->{inbuffer};
if(length($self->{inbuffer}) >0 ) {
$chunk=substr($self->{inbuffer},0, $size);
$self->{hasbanner}=0;
substr($self->{inbuffer},0, $size)='';
return $chunk;
} else {
$self->log("Error: SAM::recv(): Should have a banner but i have empty inbuffer?");
return 0;
}
# should never reach here
return 1;
}
# when there's something in the inbuffer left
# flush it to the user. If the amount of data is bigger than
# the userspecified limit, only transfer $size Bytes to the
# client
if(length($self->{inbuffer}) > 0) {
$chunk=substr($self->{inbuffer},0, $size);
substr($self->{inbuffer},0, $size)='';
return $chunk;
}
# OK, we got noting in the inbuffer
# we'll fetch a new chunk of data and then add the data to the inbuffer
# if bytestoread is bigger than POSIX::BUFSIZ we'll internally use
# a loop of reads and fill the buffer til bytestoread is 0
if(length($self->{inbuffer}) == 0) {
# read the first packet
if($self->raw_read()) {
if($self->parse_sam("STREAM") && ${$self->{samreply}}{REPLY} eq "RECEIVED") {
# it's possible that the packet does not contain any payload at all!!
# if this is the case we need
if($self->{inbuffer}=~/^.*$/) {
$self->log("Warn: SAM::recv(): Got only only one line from SAM - but i expected some payload too. Calling raw_read again ");
$self->raw_read();
}
# ok, cut the SAM HEADER from the payload
$self->{inbuffer}=substr($self->{inbuffer},(length($self->{inbuffer})-$self->{bytestoread}), length($self->{inbuffer}));
$self->log("Debug: SAM::recv(): Recived a Stream Packet and cut the SAM header out");
# ok, check if bytestoread is still bigger than our buffersize
# this means we can load more. Dangerous loop but well...
while(length($self->{inbuffer}) < $self->{bytestoread}) {
# this should definately end some day
$counter++;
if($counter > 10000) {
$self->log("Error: SAM::recv(): WTF, could not fill inbuffer as predicted by SAM header");
last;
}
# read as long til we have read all of the payload provided by SAM
$self->log("Debug: SAM::recv(): Load another chunk. Buffersize:".length($self->{inbuffer})." / Bytestoread:".$self->{bytestoread} );
$self->raw_read();
}
} else {
$self->log("Error: SAM::recv(): parse_sam() did not succeed. Interupting");
delete $self->{inbuffer};
return 0;
}
} else {
$self->log("Error: SAM::recv(): Could not read from the socket");
delete $self->{inbuffer};
return 0;
}
$chunk=substr($self->{inbuffer},0, $size);
substr($self->{inbuffer},0, $size)='';
return $chunk;
}
return 1;
}
sub lookup {
my($self,$name)=@_;
my $return;
$self->{outbuffer}="NAMING LOOKUP NAME=$name\n";
$return=$self->raw_send();
if($return == 1) {
if($self->raw_read()) {
if($self->parse_sam("NAMING")) {
$self->log("Debug: SAM::lookup(): Naming Lookup successful");
delete $self->{inbuffer};
delete $self->{outbuffer};
return $self->{lookupresult};
}
} else {
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot read SAM Response");
return 0;
}
}
if($return == 0) {
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot send NAMING String");
return 0;
}
}
sub sam_close {
my ($self) =@_;
$self->log("Debug: SAM::sam_close(): Closing Socket to SAM");
$self->{sock}->close();
return 1;
}
sub close {
my ($self)=@_;
my $return;
$self->{outbuffer}.="STREAM CLOSE ID=".$self->{streamid}."\n";
$self->log("Debug: SAM::close(): CLosing Stream with id: ".$self->{streamid});
$return=$self->raw_send();
# well, we do not care wether this worked or not
$self->sam_close();
return 1;
}
sub connect {
my ($self,$destination)=@_;
my $return;
$self->{streamid}= int(rand(200000000));
if(length($destination) == 0) {
$self->log("Error: SAM::connect(): need I2P destination to connect to with the SAM Bridge");
return 0;
}
$self->{outbuffer}.="STREAM CONNECT ID=".$self->{streamid}." DESTINATION=$destination\n";
$return=$self->raw_send();
if($return == 1) {
if($self->raw_read()) {
if($self->parse_sam("STREAM")) {
if(${$self->{samreply}}{REPLY} eq "STATUS") {
$self->{state}=250;
$self->log("Debug: SAM::connect(): State Transition 200 => 250");
# flush the whole inbuffer;
delete $self->{inbuffer};
delete $self->{outbuffer};
return 1;
}
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
$self->{state}=250;
$self->log("Debug: SAM::connect(): State Transition 200 => 250. Got a banner");
delete $self->{inbuffer};
#print "D: toread:".$self->{bytestoread};
#print "D: buffer:".$self->{inbuffer}."\n";
#print "D: buffersize:".length($self->{inbuffer})."\n";
$self->raw_read();
#print "D: toread:".$self->{bytestoread};
#print "D: buffer:".$self->{inbuffer}."\n";
#print "D: buffersize:".length($self->{inbuffer})."\n";
$self->{inbuffer}=substr($self->{inbuffer}, 0, $self->{bytestoread});
$self->{hasbanner}=1;
#print "D: toread:".$self->{bytestoread};
#print "D: buffer:".$self->{inbuffer}."\n";
#print "D: buffersize:".length($self->{inbuffer})."\n";
return 1;
}
}
} else {
$self->log("Error: SAM::connect(): STREAM Failed. Cannot read SAM Response");
return 0;
}
}
if($return == 0) {
$self->log("Error: SAM::connect(): STREAM Failed. Cannot send SESSION String");
return 0;
}
}
sub log {
my($self,$line)=@_;
if($line=~/.*\n$/) {
chomp $line;
}
if ( $self->{debug} ) {
print LOGFILE "$line\n";
}
return 1;
}

View File

@ -1,18 +0,0 @@
#!/usr/bin/perl
use Net::SAM::RawSession;
use Net::SAM::DatagramSession;
$sam=Net::SAM::DatagramSession->new($ARGV[0], "BOTH", "tunnels.depthInbound=0");
print "Connected? " . $sam->connected() . "\n";
$me = $sam->lookup("ME");
print "Sending to $me.\n";
$sam->send($me,"fooquux");
$sam->readprocess();
($source, $message) = @{ $sam->receive() };
print "$source -- $message";