# -*-Perl-*-
################################################################
###
### Smtp.pm
###
### Author: Internet Message Group
### Created: Apr 23, 1997
### Revised: Jul 4, 2004
###
my $PM_VERSION = "IM::Smtp.pm version 20031028(IM146)";
package IM::Smtp;
require 5.003;
require Exporter;
use IM::Config;
use IM::Util;
use IM::Log;
use IM::Message qw(message_size put_header put_body put_mimed_bcc
put_mimed_partial put_mimed_error_notify set_crlf);
use IM::TcpTransaction;
use integer;
use strict;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(smtp_open smtp_close smtp_transaction
smtp_transaction_for_error_notify);
use vars qw(@Status $Smtp_opened *SMTPd $SmtpErrTitle
$Esmtp_flag %ESMTP);
##### SMTP SESSION OPENING #####
#
# smtp_open(server, server_list, log_flag)
# server: current server
# server_list:
# log_flag: conversations are saved in $Session_log if true
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub smtp_open($$$) {
my($server, $server_list, $logging) = @_;
local $_;
my $rc;
my $svr = &get_cur_server_original_form();
@Status =();
if ($Smtp_opened) {
if (grep {$svr eq $_} @$server_list) {
im_notice("resetting SMTP session.\n");
return 0 unless (&tcp_command(\*SMTPd, 'RSET', ''));
}
&smtp_close;
# return 1;
}
&tcp_logging($logging);
my @s = ($server);
*SMTPd = &connect_server(\@s, 'smtp', 0);
return 1 if ($SMTPd eq '');
$SmtpErrTitle = "(while talking to " . &get_cur_server() . " with smtp)\n";
return $rc if ($rc = &tcp_command(\*SMTPd, '', ''));
$Esmtp_flag = 0;
my(@resp) = &command_response;
if (join('/', @resp) =~ /ESMTP/) {
$Esmtp_flag = 1;
}
$main::Client_name = 'localhost' unless ($main::Client_name);
if ($Esmtp_flag) {
unless (&tcp_command(\*SMTPd, "EHLO $main::Client_name", '')) {
# ESMTP OK
my(@resp) = &command_response;
foreach (@resp) {
if (/^250[ \-]([A-Z0-9]+)$/) {
$ESMTP{$1} = 1;
}
}
$Smtp_opened = 1;
&tcp_command(\*SMTPd, 'VERB', '')
if ($ESMTP{'VERB'} && &debug('smtp'));
return 0;
}
$Esmtp_flag = 0;
}
# fall back to traditional SMTP
$rc = &tcp_command(\*SMTPd, "HELO $main::Client_name", '');
return $rc if ($rc);
$Smtp_opened = 1;
&tcp_command(\*SMTPd, 'VERB', '') if (&debug('smtp'));
return 0;
}
##### SMTP SESSION CLOSING #####
#
# smtp_close()
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub smtp_close() {
# @Status =();
return 0 unless ($SMTPd);
return 0 unless ($Smtp_opened);
$Smtp_opened = 0;
im_notice("closing SMTP session.\n");
return 1 if (&tcp_command(\*SMTPd, 'QUIT', ''));
close(SMTPd);
return 0;
}
##### SMTP TRANSACTION MANAGEMENT #####
#
# smtp_transaction(server_list, bcc_flag, part, total)
# server_list:
# bcc_flag: send message in "bcc" style
# part: part number to be sent in partial message mode
# total: total number of partial messages
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub smtp_transaction($$$$$$) {
my($servers, $Header, $Body, $bcc, $part, $total) = @_;
my $rc;
my $fatal_error = 0;
for (my $i = 0; $i <= $#$servers; $i++) {
$rc = smtp_transact_sub(@$servers[$i], $servers, $Header, $Body,
$bcc, $part, $total);
return 0 if ($rc == 0);
if (($rc < 0 && !$main::Smtp_fatal_next) || $#$servers == $i) {
# fatal error or the last server
$fatal_error = 1;
}
# close and try the next server if TEMPFAIL
smtp_close() unless ($fatal_error);
log_action($Esmtp_flag ? 'esmtp' : 'smtp', get_cur_server(),
join(',', @main::Recipients),
$fatal_error ? 'failed' : 'skipped', command_response());
im_warn($SmtpErrTitle . join("\n", command_response()) . "\n");
$SmtpErrTitle = '';
return $rc if ($fatal_error);
}
return $rc;
}
##### SMTP TRANSACTION MANAGEMENT SUBROUTUNE #####
#
# smtp_transact_sub(server, server_list, header, body, bcc_flag, part, total)
# server: current server
# server_list:
# header:
# body:
# bcc_flag: send message in "bcc" style
# part: part number to be sent in partial message mode
# total: total number of partial messages
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub smtp_transact_sub($$$$$$$) {
my($server, $server_list, $Header, $Body, $bcc, $part, $total) = @_;
my($i, $rc, $fail, @fatal, $msg_size, $btype);
return $rc if ($rc = smtp_open($server, $server_list, 1));
if ($ESMTP{'8BITMIME'} && $main::Has_8bit_body && !$main::do_conv_8to7) {
$btype = ' BODY=8BIT';
} else {
$btype = '';
}
if ($ESMTP{'SIZE'}) {
$msg_size = &message_size($Header, $Body, $part);
$rc = &tcp_command(\*SMTPd,
"MAIL FROM:<$main::Sender> SIZE=$msg_size$btype", '');
} else {
$rc = &tcp_command(\*SMTPd, "MAIL FROM:<$main::Sender>$btype", '');
}
return $rc if ($rc);
$fail = 0;
my($rec);
for ($i = 0; $i <= $#main::Recipients; $i++) {
$rec = $main::Recipients[$i];
if ($bcc) {
next if ($rec =~ /<.+>/);
if ($ESMTP{'DSN'} && $main::Dsn_success_report) {
$rc = &tcp_command(\*SMTPd,
"RCPT TO:<$rec> NOTIFY=SUCCESS", '');
} else {
$rc = &tcp_command(\*SMTPd, "RCPT TO:<$rec>", '');
}
my(@resp) = &command_response;
if ($rc) {
push(@fatal, @resp);
}
$fail = $rc if ($fail != -1 && $rc);
$Status[$i] = $resp[0];
} else {
next if ($rec !~ /<.+>/);
if ($ESMTP{'DSN'} && $main::Dsn_success_report) {
$rc = &tcp_command(\*SMTPd, "RCPT TO:$rec NOTIFY=SUCCESS", '');
} else {
$rc = &tcp_command(\*SMTPd, "RCPT TO:$rec", '');
}
my(@resp) = &command_response;
if ($rc) {
push(@fatal, @resp);
}
$fail = $rc if ($fail != -1 && $rc);
$Status[$i] = $resp[0];
}
}
if ($fail) {
&set_command_response(@fatal);
return $fail;
}
return $rc if ($rc = &tcp_command(\*SMTPd, 'DATA', ''));
select (SMTPd); $| = 0; select (STDOUT);
&set_crlf("\r\n");
if ($bcc) {
return 1 if (&put_mimed_bcc(\*SMTPd, $Header, $Body, 'smtp', 1,
$part, $total) < 0);
} else {
if ($part == 0) {
return 1 if (&put_header(\*SMTPd, $Header, 'smtp', 'all') < 0);
return 1 if (&put_body(\*SMTPd, $Body, 1, 0) < 0);
} else {
return 1 if (&put_mimed_partial(\*SMTPd, $Header, $Body,
'smtp', 1, $part, $total) < 0);
}
}
select (SMTPd); $| = 1; select (STDOUT);
return $rc if ($rc = &tcp_command(\*SMTPd, '.', ''));
my(@resp) = &command_response;
&log_action($Esmtp_flag ? 'esmtp' : 'smtp', &get_cur_server(),
join(',', @main::Recipients), 'sent', @resp);
$main::Info .= "Delivery successful for the following recipient(s):\n";
for ($i = 0; $i <= $#main::Recipients; $i++) {
if ($Status[$i] =~ /^2/) {
$main::Info .= "\t$main::Recipients[$i]\n";
}
}
return 0;
}
##### SMTP TRANSACTION MANAGEMENT FOR RETURN ERROR NOTIFY #####
#
# smtp_transaction_for_error_notify(server, server_list, header, body)
# server: current server
# server_list:
# header:
# body:
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub smtp_transaction_for_error_notify($$$$) {
my($server, $servers, $Header, $Body) = @_;
my($rc, @prev_rcpt, @prev_stat);
@prev_rcpt = @main::Recipients;
@prev_stat = @Status;
@main::Recipients = ($main::Sender);
return $rc if ($rc = &smtp_open($server, $servers, 0));
return $rc if ($rc = &tcp_command(\*SMTPd, "MAIL FROM:<>", ''));
return $rc if ($rc = &tcp_command(\*SMTPd, "RCPT TO:<$main::Sender>", ''));
return $rc if ($rc = &tcp_command(\*SMTPd, 'DATA', ''));
select (SMTPd); $| = 0; select (STDOUT);
&set_crlf("\r\n");
&put_mimed_error_notify(\*SMTPd, $Header, $Body, \@prev_rcpt, \@prev_stat,
$Esmtp_flag ? 'esmtp' : 'smtp', &get_cur_server, 1, &get_session_log);
select (SMTPd); $| = 1; select (STDOUT);
return $rc if ($rc = &tcp_command(\*SMTPd, '.', ''));
my(@resp) = &command_response;
&log_action($Esmtp_flag ? 'esmtp' : 'smtp', &get_cur_server(),
join(',', @main::Recipients), 'sent', @resp);
return 0;
}
1;
__END__
=head1 NAME
IM::Smtp - SMTP handler
=head1 SYNOPSIS
use IM::Smtp;
$return_code = &smtp_open(current_server, server_list, log_flag);
$return_code = &smtp_close(socket, savehist_flag);
$return_code = &smtp_transaction(bcc_flag, part_current, part_total);
$return_code = &smtp_transaction_for_error_notify;
=head1 DESCRIPTION
The I module handles SMTP.
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.