You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
9.5 KiB
368 lines
9.5 KiB
package Net::OSCAR::Connection; |
|
|
|
$VERSION = '0.62'; |
|
|
|
use strict; |
|
use vars qw($VERSION); |
|
use Carp; |
|
use Socket; |
|
use Symbol; |
|
use Digest::MD5; |
|
use Fcntl qw(:flock :mode :seek :DEFAULT); |
|
|
|
use constant EAGAIN => 11; |
|
use constant EINPROGRESS => undef; |
|
|
|
use Net::OSCAR::Common qw(:all); |
|
use Net::OSCAR::TLV; |
|
use Net::OSCAR::Callbacks; |
|
use Net::OSCAR::OldPerl; |
|
|
|
sub new($$$$$$) { # Think you got enough parameters there, Chester? |
|
my $class = ref($_[0]) || $_[0] || "Net::OSCAR::Connection"; |
|
shift; |
|
my $self = { }; |
|
bless $self, $class; |
|
$self->{seqno} = 0; |
|
$self->{session} = shift; |
|
$self->{auth} = shift; |
|
$self->{conntype} = shift; |
|
$self->{description} = shift; |
|
$self->{paused} = 0; |
|
$self->{outbuff} = ""; |
|
$self->connect(shift); |
|
|
|
return $self; |
|
} |
|
|
|
sub fileno($) { |
|
my $self = shift; |
|
if(!$self->{socket}) { |
|
$self->{sockerr} = 1; |
|
$self->disconnect(); |
|
return undef; |
|
} |
|
return fileno $self->{socket}; |
|
} |
|
|
|
sub flap_encode($$;$) { |
|
my ($self, $msg, $channel) = @_; |
|
|
|
$channel ||= FLAP_CHAN_SNAC; |
|
return pack("CCnna*", 0x2A, $channel, ++$self->{seqno}, length($msg), $msg); |
|
} |
|
|
|
sub flap_put($;$$) { |
|
my($self, $msg, $channel) = @_; |
|
my $emsg; |
|
my $had_outbuff = 0; |
|
|
|
return unless $self->{socket} and CORE::fileno($self->{socket}) and getpeername($self->{socket}); # and !$self->{socket}->error; |
|
|
|
$had_outbuff = 1 if $self->{outbuff}; |
|
if($msg) { |
|
$emsg = $self->flap_encode($msg, $channel); |
|
$self->{outbuff} .= $emsg; |
|
} |
|
my $nchars = syswrite($self->{socket}, $self->{outbuff}, length($self->{outbuff})); |
|
if(!defined($nchars)) { |
|
return "" if $! == EAGAIN; |
|
$self->log_print(OSCAR_DBG_NOTICE, "Couldn't write to socket: $!"); |
|
$self->{sockerr} = 1; |
|
$self->disconnect(); |
|
return undef; |
|
} else { |
|
$emsg = substr($self->{outbuff}, 0, $nchars, ""); |
|
if($self->{outbuff}) { |
|
$self->log_print(OSCAR_DBG_NOTICE, "Couldn't do complete write - had to buffer ", length($self->{outbuff}), " bytes."); |
|
$self->{session}->callback_connection_changed($self, "readwrite"); |
|
} elsif($had_outbuff) { |
|
$self->{session}->callback_connection_changed($self, "read"); |
|
} |
|
$self->log_print(OSCAR_DBG_PACKETS, "Put ", hexdump($emsg)); |
|
} |
|
} |
|
|
|
sub flap_get($) { |
|
my $self = shift; |
|
my $socket = $self->{socket}; |
|
my ($buffer, $channel, $len); |
|
my $nchars; |
|
|
|
if(!exists($self->{buff_gotflap})) { |
|
$self->{buffsize} ||= 6; |
|
$self->{buffer} ||= ""; |
|
|
|
$nchars = sysread($self->{socket}, $buffer, $self->{buffsize} - length($self->{buffer})); |
|
if(!defined($nchars)) { |
|
return "" if $! == EAGAIN; |
|
$self->log_print(OSCAR_DBG_NOTICE, "Couldn't read from socket: $!"); |
|
$self->{sockerr} = 1; |
|
$self->disconnect(); |
|
return undef; |
|
} else { |
|
$self->{buffer} .= $buffer; |
|
} |
|
|
|
if(length($self->{buffer}) == 6) { |
|
$self->{buff_gotflap} = 1; |
|
($buffer) = delete $self->{buffer}; |
|
(undef, $self->{channel}, undef, $self->{buffsize}) = unpack("CCnn", $buffer); |
|
$self->{buffer} = ""; |
|
} else { |
|
return ""; |
|
} |
|
} |
|
|
|
$nchars = sysread($self->{socket}, $buffer, $self->{buffsize} - length($self->{buffer})); |
|
if(!defined($nchars)) { |
|
return "" if $! == EAGAIN; |
|
$self->log_print(OSCAR_DBG_NOTICE, "Couldn't read from socket: $!"); |
|
$self->{sockerr} = 1; |
|
$self->disconnect(); |
|
return undef; |
|
} else { |
|
$self->{buffer} .= $buffer; |
|
} |
|
|
|
if(length($self->{buffer}) == $self->{buffsize}) { |
|
$self->log_print(OSCAR_DBG_PACKETS, "Got ", hexdump($self->{buffer})); |
|
$buffer = $self->{buffer}; |
|
|
|
delete $self->{buffer}; |
|
delete $self->{buff_gotflap}; |
|
delete $self->{buffsize}; |
|
|
|
return $buffer; |
|
} else { |
|
return ""; |
|
} |
|
} |
|
|
|
sub snac_encode($%) { |
|
my($self, %snac) = @_; |
|
|
|
$snac{family} ||= 0; |
|
$snac{subtype} ||= 0; |
|
$snac{flags1} ||= 0; |
|
$snac{flags2} ||= 0; |
|
$snac{data} ||= ""; |
|
$snac{reqdata} ||= ""; |
|
$snac{reqid} ||= ($snac{subtype}<<16) | (unpack("n", randchars(2)))[0]; |
|
$self->{reqdata}->[$snac{family}]->{pack("N", $snac{reqid})} = $snac{reqdata} if $snac{reqdata}; |
|
|
|
return pack("nnCCNa*", $snac{family}, $snac{subtype}, $snac{flags1}, $snac{flags2}, $snac{reqid}, $snac{data}); |
|
} |
|
|
|
sub snac_put($%) { |
|
my($self, %snac) = @_; |
|
$snac{channel} ||= FLAP_CHAN_SNAC; |
|
$self->flap_put($self->snac_encode(%snac), $snac{channel}); |
|
} |
|
|
|
sub snac_get($) { |
|
my($self) = shift; |
|
my $snac = $self->flap_get() or return 0; |
|
return $self->snac_decode($snac); |
|
} |
|
|
|
sub snac_decode($$) { |
|
my($self, $snac) = @_; |
|
my($family, $subtype, $flags1, $flags2, $reqid, $data) = (unpack("nnCCNa*", $snac)); |
|
|
|
return { |
|
family => $family, |
|
subtype => $subtype, |
|
flags1 => $flags1, |
|
flags2 => $flags2, |
|
reqid => $reqid, |
|
data => $data |
|
}; |
|
} |
|
|
|
sub snac_dump($$) { |
|
my($self, $snac) = @_; |
|
return "family=".$snac->{family}." subtype=".$snac->{subtype}; |
|
} |
|
|
|
sub disconnect($) { |
|
my($self) = @_; |
|
|
|
$self->{session}->delconn($self); |
|
} |
|
|
|
sub set_blocking($$) { |
|
my $self = shift; |
|
my $blocking = shift; |
|
my $flags = 0; |
|
|
|
=for unix |
|
fcntl($self->{socket}, F_GETFL, $flags); |
|
if($blocking) { |
|
$flags &= ~O_NONBLOCK; |
|
} else { |
|
$flags |= O_NONBLOCK; |
|
} |
|
fcntl($self->{socket}, F_SETFL, $flags); |
|
=cut |
|
|
|
return $self->{socket}; |
|
} |
|
|
|
sub connect($$) { |
|
my($self, $host) = @_; |
|
my $temp; |
|
my %tlv; |
|
my $port; |
|
|
|
tie %tlv, "Net::OSCAR::TLV"; |
|
|
|
return $self->{session}->crapout($self, "Empty host!") unless $host; |
|
$host =~ s/:(.+)//; |
|
if(!$1) { |
|
if(exists($self->{session})) { |
|
$port = $self->{session}->{port}; |
|
} else { |
|
return $self->{session}->crapout($self, "No port!"); |
|
} |
|
} else { |
|
$port = $1; |
|
if($port =~ /^[^0-9]/) { |
|
$port = $self->{session}->{port}; |
|
} |
|
} |
|
$self->{host} = $host; |
|
$self->{port} = $port; |
|
|
|
$self->log_print(OSCAR_DBG_NOTICE, "Connecting to $host:$port."); |
|
$self->{socket} = gensym; |
|
socket($self->{socket}, PF_INET, SOCK_STREAM, getprotobyname('tcp')); |
|
|
|
$self->{ready} = 0; |
|
$self->{connected} = 0; |
|
|
|
$self->set_blocking(0); |
|
my $addr = inet_aton($host) or return $self->{session}->crapout($self, "Couldn't resolve $host."); |
|
if(!connect($self->{socket}, sockaddr_in($port, $addr))) { |
|
return 1 if $! == EINPROGRESS; |
|
return $self->{session}->crapout($self, "Couldn't connect to $host:$port: $!"); |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
sub get_filehandle($) { shift->{socket}; } |
|
|
|
# $read/$write tell us if select indicated readiness to read and/or write |
|
# Dittor for $error |
|
sub process_one($;$$$) { |
|
my($self, $read, $write, $error) = @_; |
|
my $snac; |
|
my %tlv; |
|
|
|
if($error) { |
|
$self->{sockerr} = 1; |
|
return $self->disconnect(); |
|
} |
|
|
|
tie %tlv, "Net::OSCAR::TLV"; |
|
|
|
$read ||= 1; |
|
$write ||= 1; |
|
|
|
if($write && $self->{outbuff}) { |
|
$self->log_print(OSCAR_DBG_DEBUG, "Flushing output buffer."); |
|
$self->flap_put(); |
|
} |
|
|
|
if($write && !$self->{connected}) { |
|
$self->log_print(OSCAR_DBG_NOTICE, "Connected."); |
|
$self->{connected} = 1; |
|
$self->{session}->callback_connection_changed($self, "read"); |
|
return 1; |
|
} elsif($read && !$self->{ready}) { |
|
$self->log_print(OSCAR_DBG_DEBUG, "Getting connack."); |
|
my $flap = $self->flap_get(); |
|
if(!defined($flap)) { |
|
$self->log_print(OSCAR_DBG_NOTICE, "Couldn't connect."); |
|
return 0; |
|
} else { |
|
$self->log_print(OSCAR_DBG_DEBUG, "Got connack."); |
|
} |
|
return $self->{session}->crapout($self, "Got bad connack from server") unless $self->{channel} == FLAP_CHAN_NEWCONN; |
|
|
|
if($self->{conntype} == CONNTYPE_LOGIN) { |
|
$self->log_print(OSCAR_DBG_DEBUG, "Got connack. Sending connack."); |
|
$self->flap_put(pack("N", 1), FLAP_CHAN_NEWCONN) unless $self->{session}->{svcdata}->{hashlogin}; |
|
$self->log_print(OSCAR_DBG_SIGNON, "Connected to login server."); |
|
$self->{ready} = 1; |
|
|
|
$self->log_print(OSCAR_DBG_SIGNON, "Sending screenname."); |
|
if(!$self->{session}->{svcdata}->{hashlogin}) { |
|
%tlv = ( |
|
0x17 => pack("C6", 0, 0, 0, 0, 0, 0), |
|
0x01 => $self->{session}->{screenname} |
|
); |
|
$self->flap_put(tlv_encode(\%tlv)); |
|
} else { |
|
%tlv = signon_tlv($self->{session}, $self->{auth}); |
|
$self->flap_put(pack("N", 1) . tlv_encode(\%tlv), FLAP_CHAN_NEWCONN); |
|
} |
|
} else { |
|
$self->log_print(OSCAR_DBG_NOTICE, "Sending BOS-Signon."); |
|
#%tlv = (0x06 =>$self->{auth}); |
|
#$self->flap_put(pack("N", 1) . tlv_encode(\%tlv), FLAP_CHAN_NEWCONN); |
|
$self->snac_put(family => 0, subtype => 1, |
|
flags2 => 0x6, |
|
reqid => 0x01000000 | (unpack("n", substr($self->{auth}, 0, 2)))[0], |
|
data => substr($self->{auth}, 2), |
|
channel => FLAP_CHAN_NEWCONN); |
|
} |
|
$self->log_print(OSCAR_DBG_DEBUG, "SNAC time."); |
|
return $self->{ready} = 1; |
|
} elsif($read) { |
|
if(!$self->{session}->{svcdata}->{hashlogin}) { |
|
$snac = $self->snac_get() or return 0; |
|
return Net::OSCAR::Callbacks::process_snac($self, $snac); |
|
} else { |
|
my $data = $self->flap_get() or return 0; |
|
$snac = {data => $data, reqid => 0, family => 0x17, subtype => 0x3}; |
|
if($self->{channel} == FLAP_CHAN_CLOSE) { |
|
$self->{conntype} = CONNTYPE_LOGIN; |
|
$self->{family} = 0x17; |
|
$self->{subtype} = 0x3; |
|
$self->{data} = $data; |
|
$self->{reqid} = 0; |
|
$self->{reqdata}->[0x17]->{pack("N", 0)} = ""; |
|
return Net::OSCAR::Callbacks::process_snac($self, $snac); |
|
} else { |
|
return Net::OSCAR::Callbacks::process_snac($self, $self->snac_decode($data)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
sub ready($) { |
|
my($self) = shift; |
|
|
|
return if $self->{sentready}++; |
|
$self->log_print(OSCAR_DBG_DEBUG, "Sending client ready."); |
|
if($self->{conntype} != CONNTYPE_BOS) { |
|
$self->snac_put(family => 0x1, subtype => 0x2, data => pack("n*", |
|
1, 3, 0x10, 0x47B, $self->{conntype}, 1, 0x10, 0x47B |
|
)); |
|
} else { |
|
$self->snac_put(family => 0x1, subtype => 0x2, data => pack("n*", |
|
1, 3, 0x110, 0x47B, 13, 1, 0x110, 0x47B, |
|
2, 1, 0x101, 0x47B, 3, 1, 0x110, 0x47B, |
|
4, 1, 0x110, 0x47B, 6, 1, 0x110, 0x47B, |
|
8, 1, 0x104, 1, 9, 1, 0x110, 0x47B, |
|
0xA, 1, 0x110, 0x47B, 0xB, 1, 0x104, 1, |
|
0xC, 1, 0x104, 1 |
|
)); |
|
} |
|
} |
|
|
|
sub session($) { return shift->{session}; } |
|
|
|
1;
|
|
|