# -*-Perl-*-
################################################################
###
### TcpTransaction.pm
###
### Author: Internet Message Group
### Created: Apr 23, 1997
### Revised: Jul 4, 2004
###
my $PM_VERSION = "IM::TcpTransaction.pm version 20031028(IM146)";
package IM::TcpTransaction;
require 5.003;
require Exporter;
use IM::Config qw(dns_timeout connect_timeout command_timeout rcv_buf_siz);
use Socket;
BEGIN {
eval 'use Socket6' unless (eval '&AF_INET6'); # IPv6 patched Perl
}
use IM::Util;
use IM::Ssh;
use integer;
use strict;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(log_transaction
connect_server tcp_command send_command next_response send_data
command_response set_command_response tcp_logging
get_session_log set_cur_server get_cur_server get_cur_server_original_form
pool_priv_sock);
use vars qw($Cur_server $Cur_server_original_form $Session_log $TcpSockName
$SOCK @Response $Logging @SockPool @Sock6Pool);
BEGIN {
$Cur_server = '';
$Session_log = '';
$TcpSockName = 'tcp00';
}
sub log_transaction() {
use IM::Log;
}
##### MAKE TCP CONNECTION TO SPECIFIED SERVER #####
#
# connect_server(server_list, protocol, root)
# server_list: comma separated server list
# protocol: protocol name to be used with the servers
# root: privilidge port required
# return value: handle if success
#
sub connect_server($$$) {
my($servers, $serv, $root) = @_;
if ($#$servers < 0) {
im_err("no server specified for $serv\n");
return '';
}
$SIG{'ALRM'} = \&alarm_func;
no strict 'refs'; # XXX
local(*SOCK) = \*{$TcpSockName};
$SOCK = $serv;
@Response = ();
my(@he_infos);
my($s, $localport, $remoteport);
foreach $s (@$servers) {
$Cur_server_original_form = $s;
my($r) = ($#$servers >= 0) ? 'skipped' : 'failed';
# manage server[/remoteport]%localport
if ($s =~ s/\%(\d+)$//) {
$localport = $1;
$Cur_server = $s;
if ($s =~ s/\/(\d+)$//) {
$remoteport = $1;
} else {
next unless ($remoteport = getserv($serv, 'tcp'));
}
if ($main::SSH_server eq 'localhost') {
im_warn("Don't use port-forwarding to `localhost'.\n");
$Cur_server = "$s/$remoteport";
} else {
if ($remoteport = &ssh_proxy($s,$remoteport,$localport,$main::SSH_server)) {
$s = 'localhost';
$Cur_server = "$Cur_server%$remoteport";
} else { # Connection failed.
im_warn("Can't login to $main::SSH_server\n");
if ($serv eq 'smtp') {
&log_action($serv, $Cur_server,
join(',', @main::Recipients), $r, @Response);
} else { # NNTP
&log_action($serv, $Cur_server,
$main::Newsgroups, $r, @Response);
}
next;
}
}
}
# manage server[/remoteport] notation
elsif ($s =~ /([^\/]*)\/(\d+)$/) {
$remoteport = $2;
$s = $1;
$Cur_server = "$s/$remoteport";
} else {
$remoteport = $serv;
$Cur_server = $s;
}
$0 = progname() . ": im_getaddrinfo($s)";
@he_infos = im_getaddrinfo($s, $remoteport, AF_UNSPEC, SOCK_STREAM);
if ($#he_infos < 1) {
im_warn("address unknown for $s\n");
@Response = ("address unknown for $s");
if ($serv eq 'smtp') {
&log_action($serv, $Cur_server,
join(',', @main::Recipients), $r, @Response);
} else { # NNTP
&log_action($serv, $Cur_server,
$main::Newsgroups, $r, @Response);
}
next;
}
while ($#he_infos >= 0) {
my($family, $socktype, $proto, $sin, $canonname)
= splice(@he_infos, 0, 5);
if ($root && unixp()) {
my $name = priv_sock($family);
my $port;
if ($name eq '') {
im_err("privilege port pool is empty.\n");
return '';
}
if ($family == AF_INET) {
$port = (unpack_sockaddr_in($sin))[0];
} else {
$port = (unpack_sockaddr_in6($sin))[0];
}
*SOCK = \*{$name};
$SOCK = $port;
} else {
unless (socket(SOCK, $family, $socktype, $proto)) {
im_err("socket creation failed: $!.\n");
return '';
}
if (defined(rcv_buf_siz())) {
unless (setsockopt(SOCK, SOL_SOCKET, SO_RCVBUF, int(rcv_buf_siz()))) {
im_err("setsockopt failed: $!.\n");
return '';
}
}
}
im_notice("opening $serv session to $s($remoteport).\n");
alarm(connect_timeout()) unless win95p();
$0 = progname() . ": connecting to $s with $serv";
if (connect (SOCK, $sin)) {
alarm(0) unless win95p();
select (SOCK); $| = 1; select (STDOUT);
$Session_log .=
"Transcription of $serv session follows:\n" if ($Logging);
im_debug("handle $TcpSockName allocated.\n")
if (&debug('tcp'));
$TcpSockName++;
return *SOCK;
}
@Response = ($!);
alarm(0) unless win95p();
close(SOCK);
}
im_notice("$serv server $s($remoteport) did not respond.\n");
if ($serv eq 'smtp') {
&log_action($serv, $Cur_server,
join(',', @main::Recipients), $r, @Response);
} else { # NNTP
&log_action($serv, $Cur_server,
$main::Newsgroups, $r, @Response);
}
}
im_warn("WARNING: $serv connection was not established.\n");
return '';
}
##### CLIENT-SERVER HANDSHAKE #####
#
# tcp_command(channel, command, fake_message)
# channel: socket descriptor to send the command
# command: command string to be sent
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub tcp_command($$$) {
my($CHAN, $command, $fake) = @_;
my($resp, $stat, $rcode, $logcmd);
@Response = ();
$stat = '';
if ($fake) {
$logcmd = $fake;
} else {
$logcmd = $command;
}
if ($command) {
im_notice("<<< $logcmd\n");
$Session_log .= "<<< $logcmd\n" if ($Logging);
unless (print $CHAN "$command\r\n") {
# may be channel trouble
@Response = ($!);
return 1;
}
$0 = progname() . ": $logcmd ($Cur_server)";
} else {
## if you have mysterious TCP/IP bug on IRIX/SGI
# print $CHAN ' ';
## endif
$0 = progname() . ": greeting ($Cur_server)";
}
do {
alarm(command_timeout()) unless win95p();
$resp = <$CHAN>;
if (!defined($resp)) {
# may be channel trouble
@Response = ("$!");
}
alarm(0) unless win95p();
if (!defined($resp)) {
# may be channel trouble
return 1;
}
$resp =~ s/[\r\n]+$//;
if ($resp =~ /^([0-9][0-9][0-9])/) {
$rcode = $1;
if ($stat eq '' && $rcode !~ /^0/) {
$stat = $rcode;
}
push(@Response, $resp) if ($rcode !~ /^0/); # XXX
}
im_notice(">>> $resp\n");
$Session_log .= ">>> $resp\n" if ($Logging);
last if ($resp =~ /^\.$/);
} while ($resp =~ /^...-/ || $resp =~ /^[^1-9]/);
return 0 if ($stat =~ /^[23]../);
return 1 if ($stat =~ /^4../);
return -1;
}
##### CLIENT-SERVER HANDSHAKE #####
#
# send_command(channel, command, fake_message)
# return value: the first line of responses
#
sub send_command($$$) {
my($CHAN, $command, $fake) = @_;
my($resp, $logcmd);
if ($command) {
print $CHAN "$command\r\n";
if ($fake) {
$logcmd = $fake;
} else {
$logcmd = $command;
}
im_notice("<<< $logcmd\n");
$Session_log .= "<<< $logcmd\n" if ($Logging);
$0 = progname() . ": $logcmd ($Cur_server)";
} else {
$0 = progname() . ": greeting ($Cur_server)";
}
alarm(command_timeout()) unless win95p();
$resp = <$CHAN>;
if (!defined($resp)) {
# may be channel trouble
im_notice("$!\n");
}
alarm(0) unless win95p();
if (!defined($resp)) {
# may be channel trouble
return '';
}
$resp =~ s/[\r\n]+/\n/;
im_notice(">>> $resp");
$Session_log .= ">>> $resp" if ($Logging);
chomp $resp;
return $resp;
}
sub send_data($$$) {
my($CHAN, $data, $fake) = @_;
my($logdata);
$data =~ s/\r?\n?$//;
print $CHAN "$data\r\n";
if ($fake) {
$logdata = $fake;
} else {
$logdata = $data;
}
im_notice("<<< $logdata\n");
$Session_log .= "<<< $logdata\n" if ($Logging);
}
sub next_response($) {
my $CHAN = shift;
my $resp;
alarm(command_timeout()) unless win95p();
$resp = <$CHAN>;
if (!defined($resp)) {
# may be channel trouble
im_notice("$!\n");
}
alarm(0) unless win95p();
if (!defined($resp)) {
# may be channel trouble
return '';
}
$resp =~ s/[\r\n]+/\n/;
im_notice(">>> $resp");
$Session_log .= ">>> $resp" if ($Logging);
chomp $resp;
return $resp;
}
sub command_response() {
return @Response;
}
sub set_command_response(@) {
@Response = @_;
}
sub tcp_logging($) {
# conversations are saved in $Session_log if true
$Logging = shift;
}
sub get_session_log() {
return $Session_log;
}
sub set_cur_server($) {
$Cur_server = shift;
}
sub get_cur_server() {
return $Cur_server;
}
sub get_cur_server_original_form() {
return $Cur_server_original_form;
}
sub pool_priv_sock($) {
my $count = shift;
pool_priv_sock_af($count, AF_INET);
if (eval 'pack_sockaddr_in6(110, pack("N4", 0, 0, 0, 0))') {
no strict 'subs'; # XXX for AF_INET6
pool_priv_sock_af($count, AF_INET6);
}
}
sub pool_priv_sock_af($$) {
my($count, $family) = @_;
my $privport = 1023;
no strict 'refs'; # XXX
my($pe_name, $pe_aliases, $pe_proto);
($pe_name, $pe_aliases, $pe_proto) = getprotobyname ('tcp');
unless ($pe_name) {
$pe_proto = 6;
}
while ($count--) {
unless (socket(*{$TcpSockName}, $family, SOCK_STREAM, $pe_proto)) {
im_err("socket creation failed: $!.\n");
return -1;
}
while ($privport > 0) {
my($ANYADDR, $psin);
im_debug("binding port $privport.\n") if (&debug('tcp'));
if ($family == AF_INET) {
$ANYADDR = pack('C4', 0, 0, 0, 0);
$psin = pack_sockaddr_in($privport, $ANYADDR);
} else {
$ANYADDR = pack('N4', 0, 0, 0, 0);
$psin = pack_sockaddr_in6($privport, $ANYADDR);
}
last if (bind (*{$TcpSockName}, $psin));
im_warn("privileged socket binding failed: $!.\n")
if (&debug('tcp'));
$privport--;
}
if ($privport == 0) {
im_err("binding to privileged port failed: $!.\n");
return -1;
}
im_notice("pool_priv_sock: $TcpSockName got\n");
if ($family == AF_INET) {
push(@SockPool, $TcpSockName);
} else {
push(@Sock6Pool, $TcpSockName);
}
$TcpSockName++;
}
return 0;
}
sub priv_sock($) {
my($family) = shift;
my($sock_name);
if ($family == AF_INET) {
return '' if ($#SockPool < 0);
$sock_name = shift(@SockPool);
} else {
return '' if ($#Sock6Pool < 0);
$sock_name = shift(@Sock6Pool);
}
im_notice("priv_sock: $sock_name\n");
return $sock_name;
}
sub alarm_func {
im_die("connection error\n");
}
sub im_getaddrinfo($$;$$$$) {
return getaddrinfo(@_) if (defined &getaddrinfo);
my($node, $serv, $family, $socktype, $proto, $flags) = @_;
my($pe_name, $pe_aliases, $pe_proto, $se_port);
if (unixp()) {
$proto = 'tcp' unless ($proto);
($pe_name, $pe_aliases, $pe_proto) = getprotobyname($proto);
}
$pe_proto = 6 unless ($pe_name);
return unless ($se_port = getserv($serv, $proto));
my($he_name, $he_alias, $he_type, $he_len, @he_addrs);
if ($node =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
@he_addrs = (pack('C4', $1, $2, $3, $4));
} else {
alarm(dns_timeout()) unless win95p();
($he_name, $he_alias, $he_type, $he_len, @he_addrs)
= gethostbyname($node);
alarm(0) unless win95p();
return unless ($he_name);
}
my($he_addr, @infos);
foreach $he_addr (@he_addrs) {
push(@infos, AF_INET, $socktype, $pe_proto,
pack_sockaddr_in($se_port, $he_addr), $he_name);
}
@infos;
}
sub getserv($$) {
my($serv, $proto) = @_;
my($se_port);
if ($serv =~ /^\d+$/o) {
$se_port = $serv;
} else {
my($se_name, $se_aliases);
($se_name, $se_aliases, $se_port) = getservbyname($serv, $proto)
if (unixp());
unless ($se_name) {
if ($serv eq 'smtp') {
$se_port = 25;
} elsif ($serv eq 'http') {
$se_port = 80;
} elsif ($serv eq 'nntp') {
$se_port = 119;
} elsif ($serv eq 'pop3') {
$se_port = 110;
} elsif ($serv eq 'imap') {
$se_port = 143;
} else {
im_err("unknown service: $serv\n");
return undef;
}
}
}
$se_port;
}
1;
__END__
=head1 NAME
IM::TcpTransaction - TCP transaction processing interface for SMTP and NNTP
=head1 SYNOPSIS
use IM::TcpTransaction;
$socket = &connect_server(server_list, protocol, log_flag);
$return_code = &tcp_command(socket, command_string, log_flag);
@response = &command_response;
&set_command_response(response_string_list);
=head1 DESCRIPTION
The I module handles TCP transaction for SMTP and NNTP.
This modules is provided by IM (Internet Message).
=head1 COPYRIGHT
IM (Internet Message) is copyrighted by IM developing team.
You can redistribute it and/or modify it under the modified BSD
license. See the copyright file for more details.
=cut
### Copyright (C) 1997, 1998, 1999 IM developing team
### All rights reserved.
###
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions
### are met:
###
### 1. Redistributions of source code must retain the above copyright
### notice, this list of conditions and the following disclaimer.
### 2. Redistributions in binary form must reproduce the above copyright
### notice, this list of conditions and the following disclaimer in the
### documentation and/or other materials provided with the distribution.
### 3. Neither the name of the team nor the names of its contributors
### may be used to endorse or promote products derived from this software
### without specific prior written permission.
###
### THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS'' AND
### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
### PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TEAM OR CONTRIBUTORS BE
### LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
### CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
### SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
### BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
### WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
### OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
### IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.