923 lines
25 KiB
Perl
923 lines
25 KiB
Perl
# 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;
|
|
|
|
}
|
|
|
|
|