#! @im_path_perl@
################################################################
###
### imput
###
### Author: Internet Message Group
### Created: Aug 31, 1995
### Revised: Feb 28, 2000
###
BEGIN {
@im_my_siteperl@
@im_src_siteperl@
};
$VERSION = "imput version 20000228(IM140)";
$Prog = 'imput';
# Usage:
# o With UCB Mail
# define "sendmail" environment variable with value "imput".
# o With Mew
# eval (setq mew-prog-imput "imput")
# o With MH
# define sendproc in ~/.mh_profile like "sendproc: imput"
# o With sendmail.el (if you are using VM or ....)
# eval (setq sendmail-program "imput")
# o With mh-e (4 or later)
# eval (setq mh-send-prog "imput")
# o With pine
# define "sendmail-path" in ~/.pinerc, for example:
# sendmail-path=/usr/local/bin/imput -Report -ObeyHeader -IgnoreDot
# o With applications which use sendmail
# replace sendmail to this program but if daemon sendmail is required,
# original sendmail should be renamed to sendmail.bin or something else
# for starting daemon mode sendmail and for newaliasing
# o With applications which use inews
# header generation by command line option not supported
require 5.003;
use IM::Address;
use IM::Alias;
use IM::Config;
use IM::Folder qw(touch_folder);
use IM::GetPass;
use IM::History;
use IM::Iso2022jp;
use IM::Japanese;
use IM::Log;
use IM::Message;
use IM::MsgStore;
use IM::Nntp;
use IM::Recipient;
use IM::Smtp;
use IM::TcpTransaction;
use IM::Util;
use integer;
use strict 'refs';
use strict 'subs';
use vars qw($DebugAll $QueueStatus $Subject $Ignore_Dot $Fcc_partial
$News_severe_check $PreserveMessage $Queuing $Comment_Name
$Help $JustQueuing $Me_too $Dcc_Address $PGP_Sign);
$EXPLANATION = "
$Prog :: Put Messages into Networks
$VERSION
Usage: $Prog [options] msg
";
# table of environment variables
@EnvConfig = (
# env. var. name option name
'NAME;s;;Name' => '',
'SIGNATURE;s;;Name' => '',
'SMTPSERVERS;s;;Smtp_servers' => '',
'NNTPSERVERS;s;;Nntp_servers' => '',
'ORGANIZATION;s;;Org' => '',
'HOSTALIASES;s;;hostAliases' => '',
'MSGIDDOMAIN;s;;MsgIdDomain' => '',
'FROMDOMAIN;s;;FromDomain' => '',
'TODOMAIN;s;;ToDomain' => '',
'mhaltmsg;s;;Dist_file' => '',
'mhdist;s;;Dist_flag' => '',
);
@OptConfig = (
# table of commandline options
'Help;b;;Help'
=> 'Show this message.',
'Debug;b;;DebugAll'
=> 'Set all debug options.',
'DebugFlag;s;;DebugFlag'
=> "Set specific debug options (separated with ',').",
'Verbose;b;;opt_verbose'
=> 'Set verbose mode.',
## late evaluated options
'Require;s;;User_require'
=> 'User defined perl script to be required.',
'SMTPservers;s;localhost;Smtp_servers'
=> "List of SMTP servers (separated with ',')
Each element should be server[/remote_port][%local_port]",
'EmgSMTPsvrs;s;;Emg_Smtp_servers'
=> 'List of SMTP servers for Emergency Use (i.e. error reporting).',
'TryNextOnFatal;b;;Smtp_fatal_next'
=> 'Try next SMTP server evenif permanent failure.',
'NNTPservers;s;localhost;Nntp_servers'
=> "List of NNTP servers (separated with ',').
Each element should be server[/remote_port][%local_port]",
'ClientName;s;localhost;Client_name'
=> 'Name as a SMTP client (used for SMTP HELO).',
'SSHServer;s;localhost;SSH_server'
=> 'SSH port relay server.',
'ObeyMTAdomain;b;;Obey_MTA_domain'
=> 'Do not qualify local addresses with default domain.',
'FromDomain;s;;Default_from_domain_name'
=> 'Default domain name for sender.',
'ToDomain;s;;Default_to_domain_name'
=> 'Default domain name for recipients.',
'MsgIdDomain;s;;Message_id_domain_name'
=> 'Default domain name for Message-Id generation.',
'NoMsgIdForNews;s;;NoMsgIdForNews'
=> 'Strip Message-Id when posting to news system.',
'User;s;;User_name'
=> "Local part of the sender's address.",
'Address;s;;Mail_Address'
=> 'Address used in From: header; equivalent to User@FromDomain',
'Name;s;;Sender_name'
=> 'Commentary name for sender',
'NameInComment;b;;Comment_Name'
=> 'Show commentary name in () on From: header.',
'Org;s;;Organization'
=> 'Name of organization for news posting.',
'Subj;s;;Subject'
=> 'A string for subject field.',
'NScmpl;b;;Cmpl_with_gethostbyname'
=> 'Use domain-part completion with nameserver.',
'ShowRcpts;b;1;Show_Rcpts_Header'
=> 'Allow to show recipients in header as To: if no To: in original.',
'MeToo;b;;Me_too'
=> 'Request DCC to me.',
'Dcc;s;;Dcc_Address'
=> 'Supplemental DCC address.',
'Fcc;s;;Fcc_folder'
=> 'Folder name to save FCC.',
'Receipt;b;;Dsn_success_report'
=> 'Need a report of successful delivery.',
'Group;s;;Newsgroups'
=> 'Newsgroup names to be posted in.',
## if ISO2022JP
'JPconv;b;;Iso2022jp_code_conversion'
=> 'Convert japanese character encoding from EUC/SJIS to JIS.',
'DefCode;s;8BIT;Default_code'
=> 'Default classification of japanese character code (EUC/SJIS/8BIT).',
'JPheader;b;1;Iso2022jp_header_mime_conv'
=> 'Encode japanese JIS characters to MIME style at header.',
'HdrQEncoding;b;;HdrQEncoding'
=> 'Header encoding type: 0 is B; 1 is Q.',
## endif
'NoHdrFolding;b;;NoFolding'
=> 'Do not fold long header lines.',
'SortHeader;s;;HeaderSeq'
=> "Header sequence for sorting (labels separated with ',').",
'8to7;b;;Conv_8to7'
=> 'Convert 8bit body to 7bit by base64/quoted-printable encoding.',
'8BitLabel;s;unknown-8bit;Unknown8bit_label'
=> 'Sub-type label for unknown 8bit body on Content-Type:.',
'Lines;i;;Lines_to_partial' # XXX
=> 'Line numbers for splitting into partial messages.',
'Sleep;i;10;Partial_sleep'
=> 'Sleep interval for dispatching each splitted messages.',
'Log;s;putlog;Log_file' # XXX
=> 'File name to write delivery log.',
'Syslog;b;;'
=> 'Use SYSLOG feature instead of writing to a file directly.',
'Report;b;1;Error_report_by_mail'
=> 'Report errors via mail.',
'MsgId;b;1;Generate_message_id'
=> 'Generate Message-Id header line.',
'PidMsgId;b;;Message_id_PID'
=> 'Generate Message-Id with Process ID.',
'UidMsgId;b;;Message_id_UID'
=> 'Generate Message-Id with User ID instead of login name.',
'Date;b;1;Generate_date'
=> 'Generate Date header line.',
'NewsGMTdate;b;;NewsGMTdate'
=> 'Generate date field in GMT for posting news.',
'UseLines;b;1;UseLines'
=> 'Generate Lines header line.',
'AliasesFile;s;;Mail_aliases' # XXX
=> 'List of files for mail address aliasing.',
'Addrbook;s;;addrbooks'
=> 'List of Addrbook files.',
'HostAliases;s;;Host_aliases' # XXX
=> 'List of files for domain part completion.',
'FccPartial;b;;Fcc_partial'
=> 'Save FCC with partial format.',
'Dead;s;dead.letter;Dead_letter' # XXX
=> 'Path of file to save unsent message as a deadletter.',
'JustQueuing;b;;JustQueuing'
=> 'Just store message into queue without attempt of delivery.',
'Queuing;b;;Queuing'
=> 'Store message into queue on delivery failure.',
'ProcessQueue;b;;ProcessQueue'
=> 'Process queued messages.',
'QueueStatus;b;;QueueStatus'
=> 'Show list of queued messages.',
'Folder;s;;Draft_folder'
=> 'Path of draft folder directory.',
'MIMEbcc;b;1;Mime_bcc'
=> 'Use MIME (message/rfc822) style BCC.',
'TrashMark;s;#;Trashmark'
=> 'Prefix character for draft message file renaming.',
'Preserve;b;;PreserveMessage'
=> 'Preserve draft message as is even if dispatched successfully.',
'Message;s;;Draft_message'
=> 'Path/name of draft message.',
'PGPsign;b;;PGP_Sign'
=> 'Generate PGP signature for body-part.',
'MultipartAdd;s@;;Mulipart_messages'
=> 'Path/name of message to be added as a part of multipart message.',
'FilenameAdd;b;;Filename_Add'
=> 'Add filename field at Content-Disposition header.',
'SMTP;b;;Smtp_input_mode'
=> 'Perform SMTP style input mode.',
'Annotate;b;;Anno_flag'
=> 'Annotate on the parent message (MsgDB required).',
'Dist;b;;Dist_flag'
=> 'Redistribution mode (using Resent-*:).',
'DistMsg;s;;Dist_file'
=> 'Path of message for redistribution.',
'ObeyHeader;b;;Obey_header'
=> 'Collect recipients from message header.',
'IgnoreDot;b;;Ignore_Dot'
=> 'Ignore DOT for message termination.',
'NewsCheck;b;;News_severe_check'
=> 'No news posting if To, Cc header found evenif Newsgroups: exists.',
'ESMTP;b;;Esmtp_flag'
=> 'Enforce ESMTP (always begins with EHLO).',
'NewsPost;b;1;News_flag'
=> 'Enable NNTP.',
'AddHeader;s@;;Add_headers'
=> 'Header lines to be added.',
);
@CmpConfig = (
## for compatibility with send/MH
'mime;B;;Mime_bcc' => '',
'watch;b;;opt_verbose' => '',
'draftmessage;s;;Draft_message' => '',
'draftf;s;;Draft_folder' => '',
'verbose;b;;opt_verbose' => '',
'draftfolder;s;;Draft_folder' => '',
'nowatch;B;;opt_verbose' => '',
'nodraftfolder;s;;Dummy' => '',
'draftm;s;;Draft_message' => '',
'draft;B;;Dummy' => '',
'alias;s;;Mail_aliases' => '',
'aliasesfile;s;;Mail_aliases' => '',
'filter;s;;Dummy' => '',
'nofilter;B;;Dummy' => '',
'format;b;;Dummy' => '',
'noformat;B;;Dummy' => '',
'forward;b;;Dummy' => '',
'noforward;B;;Dummy' => '',
'push;b;;Error_report_by_mail' => '',
'nopush;B;;Error_report_by_mail' => '',
'width;i;;Dummy' => '',
'library;s;;Dummy' => '',
## if ISO2022JP
'hencode;b;;Iso2022jp_header_mime_conv' => '',
'nohencode;B;;Iso2022jp_header_mime_conv' => '',
## endif
'split;i;;Partial_sleep' => '',
'server;s;;Smtp_servers' => '',
'client;s;;Client_name' => '',
## temporary solution for compatibility with sendmail
'bs;b;;Smtp_input_mode' => '',
'f;s;;Dummy' => '',
't;b;;Obey_header' => '',
'v;b;;opt_verbose' => '',
'odb;B;;Dummy' => '',
'odi;B;;Dummy' => '',
'oem;B;;Error_report_by_mail' => '',
'oi;B;;Ignore_Dot' => '',
'i;B;;Ignore_Dot' => '',
'om;b;;Me_too' => '',
'm;b;;Me_too' => '',
'odq;b;;JustQueuing' => '',
'q;b;;ProcessQueue' => '',
'bp;b;;QueueStatus' => '',
## compatibility for sendmail on Sony NEWS
'J;b;;Iso2022jp_code_conversion' => '',
## temporary solution for compatibility with inews
'h;b;;Obey_header' => '',
## temporary solution for compatibility with ucbmail
's;s;;Subject' => '',
);
##
## Profile and option processing
##
$selector = read_cfg_selector(\@ARGV);
init_opt(\@OptConfig, \@CmpConfig);
read_env(\@EnvConfig);
read_cfg();
if ($selector ne '') {
$error = 1 if (set_selector($selector) < 0);
}
read_opt(\@ARGV);
help($EXPLANATION) && exit $EXIT_SUCCESS if $Help;
##
## Main
##
&initialize;
@arg_rest = @ARGV;
$DebugFlag = 'all' if ($DebugAll);
&debug_option($DebugFlag);
if ($ProcessQueue || $QueueStatus) {
&init_final;
&process_queue(queue_path(), $ProcessQueue);
exit $EXIT_SUCCESS;
}
alias_read($Mail_aliases, $addrbooks);
hosts_read($Host_aliases);
if ($Draft_message eq '' && $#arg_rest == 0 && $arg_rest[0] !~ /\@/ &&
($arg_rest[0] =~ /^\// || $arg_rest[0] =~ /^\w:\//)) {
# absolute path expression: a draft message
$Draft_message = $arg_rest[0];
}
$Obey_header = 1 if ($Draft_message);
if (!$Obey_header && !$Smtp_input_mode) {
foreach $arg (@arg_rest) {
if ($arg !~ /\@/ && ($arg =~ /^\// || $arg =~ /^\w:\//)) {
# bad address list
$error = 1;
last;
# &error_exit;
} else {
# otherwise: a mail address (not a news group)
if (&parse_rcpt(0, $arg) < 0) {
$error = 1;
last;
# &error_exit;
}
$News_flag = 0; # XXX
}
}
} else {
# a draft message is specified or SMTP input mode
# XXX arguments ignored
}
unless ($Obey_header || $Draft_message || $Smtp_input_mode) {
# recipients should be specified at command line
if ($#Recipients < 0 && $Newsgroups eq '') {
im_info("message was not delivered.\n");
im_info("Recipient names must be specified.\n");
exit $EXIT_ERROR;
# &error_exit;
}
}
##### GET A MESSAGE #####
if ($Draft_message || $Dist_file) {
# read whole message from draft message file
if ($Dist_flag && $Dist_file) {
&read_message(1); # read altmsg to resend
} else {
&read_message(0); # read normal message
}
} elsif ($Smtp_input_mode) {
# read message with SMTP
&smtp_get_mail;
} else {
# read message from STDIN
if (&read_header('STDIN', \@Header, 0) < 0) {
$error = 1;
} else {
&read_body('STDIN', \@Body, 0, !$Ignore_Dot);
}
}
im_notice("message accepted.\n");
##### SET SIGNAL HANDLING FUNCTIONS #####
# $SIG{'ALRM'} = \&alarm_func;
$SIG{'TERM'} = \&term_func;
$SIG{'INT'} = \&int_func;
##### OPTIONAL CONFIGURATION #####
if ($Obey_header && ($selector = &header_value(\@Header, 'Config')) ne '') {
$selector =~ s/\s+//g;
if ($selector ne '') {
$error = 1 if (set_selector($selector) < 0);
}
&kill_header(\@Header, 'Config', 0);
alias_read($Mail_aliases, $addrbooks);
hosts_read($Host_aliases);
}
&init_final;
my ($h);
foreach $h (@Add_headers) {
if ($h =~ /^([\w\-]+):\s*(.*)/s) {
&add_header(\@Header, 0, $1, $2);
}
}
##### HEADER PROCESSING #####
if ($Obey_header && &header_value(\@Header, 'Return-Receipt-To')) {
$Dsn_success_report = 1;
&kill_header(\@Header, 'Return-Receipt-To', 0);
}
# Resent-*: checking
if ($Newsgroups eq ''
&& (($Dist_flag && $Dist_file)
|| ($Obey_header
&& (&header_value(\@Header, 'Resent-To')
|| &header_value(\@Header, 'Resent-Cc'))))) {
$resend_mode = 1;
$Resend_prefix = 'Resent-';
$News_flag = 0;
im_notice("NNTP disabled (resend is only for mailing).\n");
} else {
$resend_mode = 0;
$Resend_prefix = '';
}
# verify invalid headers for posting news
if ($resend_mode == 0 && $Newsgroups ne '') {
&add_header(\@Header, 1, 'Newsgroups', $Newsgroups);
&kill_header(\@Header, 'Path', 0);
&kill_header(\@Header, 'Followup-To', 0);
&kill_header(\@Header, 'Received', 0);
&kill_header(\@Header, 'Return-Path', 0);
&kill_header(\@Header, 'NNTP-Posting-Host', 0);
&kill_header(\@Header, 'Xref', 0);
&kill_header(\@Header, 'Resent-To', 0);
&kill_header(\@Header, 'Resent-Cc', 0);
&kill_header(\@Header, 'Resent-From', 0);
$News_flag = 1;
} elsif ($News_flag
&& (&header_value(\@Header, 'Newsgroups')
|| &header_value(\@Header, 'BNewsgroups'))
&& !&header_value(\@Header, 'Path')
&& !&header_value(\@Header, 'Received')
&& !&header_value(\@Header, 'Return-Path')
&& !&header_value(\@Header, 'NNTP-Posting-Host')
&& !&header_value(\@Header, 'Xref')
&& (!$News_severe_check
|| !&header_value(\@Header, 'Apparently-To')
&& !&header_value(\@Header, 'To')
&& !&header_value(\@Header, 'Cc'))) {
# $News_flag = 1;
# im_notice("NNTP will performed.\n");
} else {
$News_flag = 0;
im_notice("NNTP disabled (header format is not fit).\n");
}
if ($Dist_flag && $Dist_file) {
&rewrite_resend_header;
&append_dist_header;
}
## if ISO2022JP
# hook before convert header
&$Hook_PreHeaderconv if ($Hook_PreHeaderconv);
if ($Iso2022jp_header_mime_conv) {
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== before header_iso2022jp_conv ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("====================================\n");
# close(NULL);
# }
if (&header_iso2022jp_conv(\@Header, $Iso2022jp_code_conversion) < 0) {
$error = 1;
}
}
## endif
# XXX just for NetNews ?
if ($News_flag && $Organization && !&header_value(\@Header, 'Organization'))
{
## if ISO2022JP
if ($Iso2022jp_code_conversion) {
# $c = &code_check($Organization);
# if ($c eq 'sjis' || $c eq 'euc' || $c eq 'sORe') { # XXX
$Organization = &conv_iso2022jp($Organization);
# }
}
$Organization = &line_iso2022jp_mimefy($Organization)
if ($Iso2022jp_header_mime_conv);
## endif
&add_header(\@Header, 0, 'Organization', $Organization);
}
if ($Generate_message_id
&& !&header_value(\@Header, $Resend_prefix.'Message-Id')) {
$Cur_mid = &gen_message_id(0);
&add_header(\@Header, 0, $Resend_prefix.'Message-Id', $Cur_mid);
}
if ($Generate_date && !&header_value(\@Header, $Resend_prefix.'Date')) {
&add_header(\@Header, 0, $Resend_prefix.'Date', &gen_date(!$News_flag));
}
if ($Sender_name) {
if ($Comment_Name) {
$Sender_line = "$Sender ($Sender_name)";
} else {
$Sender_line = "$Sender_name <$Sender>";
}
## if ISO2022JP
if ($Iso2022jp_code_conversion) {
# $c = &code_check($Sender_line);
# if ($c eq 'sjis' || $c eq 'euc' || $c eq 'sORe') { # XXX
$Sender_line = &conv_iso2022jp($Sender_line)
# }
}
$Sender_line = &struct_iso2022jp_mimefy($Sender_line)
if ($Iso2022jp_header_mime_conv);
## endif
} else {
$Sender_line = $Sender;
}
im_debug("Sender: $Sender_line\n") if (&debug('from'));
unless ($from = &header_value(\@Header, $Resend_prefix.'From')) {
&add_header(\@Header, 0, $Resend_prefix.'From', $Sender_line);
} else {
my $rc = &parse_rcpt(-1, $from);
if ($rc < 0) {
$error = 1;
} elsif ($rc != 1 || &extract_addr($from) ne $Sender) {
&add_header(\@Header, 1, $Resend_prefix.'Sender', $Sender_line);
# &add_header(\@Header, 1, 'Originator', $Sender_line)
# if ($News_flag);
}
}
if (&header_value(\@Header, 'Subject') eq '') {
&add_header(\@Header, 1, 'Subject', $Subject);
}
##### BODY PROCESSING #####
unless (&header_value(\@Header, 'Mime-Version')) {
$Body_code = code_check_body(\@Body);
im_debug("Body code is $Body_code\n") if (&debug('code'));
$do_conv_8to7 = 0;
if ($Body_code eq '8BIT') {
$Need_mime_version_header = 1;
$Has_8bit_body = 1;
$do_conv_8to7 = 1 if ($Conv_8to7);
}
## if ISO2022JP
elsif ($Body_code eq 'JIS') {
$Need_mime_version_header = 1;
$Has_iso2022jp_body = 1;
} elsif ($Body_code eq 'SJIS' || $Body_code eq 'EUC') {
$Need_mime_version_header = 1;
if ($Iso2022jp_code_conversion) {
$Has_iso2022jp_body = 1;
convert_iso2022jp_body(\@Body, $Body_code);
if ($Has_Hankaku_kana) {
$Body_code = '8BIT';
$Has_8bit_body = 1;
$do_conv_8to7 = 1 if ($Conv_8to7);
}
} else {
$Has_8bit_body = 1;
$do_conv_8to7 = 1 if ($Conv_8to7);
}
}
if ($do_conv_8to7) {
if ($Need_base64_encoded) {
&body_base64_encode(\@Body);
$Body_encoding = 'base64';
} else {
&body_qp_encode(\@Body);
$Body_encoding = 'quoted-printable';
}
}
## endif
}
##### HEADER REWRITING #####
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== before rewrite_header ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("=============================\n");
# close(NULL);
# }
if (&rewrite_header(\@Header) < 0) {
$error = 1;
}
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== after rewrite_header ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("============================\n");
# close(NULL);
# }
&error_exit if ($error);
##### GET RECIPIENTS #####
if ($Obey_header || $Draft_message) {
if (&rcpt_pickup(\@Header, $resend_mode, 0) < 0) {
&error_exit;
}
}
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== after rcpt_pickup ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("=========================\n");
# close(NULL);
# }
##### VERIFY FORMAT OF THE MESSAGE #####
if ($News_flag) {
if (&header_value(\@Header, 'Newsgroups') eq ''
&& &header_value(\@Header, 'BNewsgroups') eq '') {
im_err("Bad message format (no Newsgroups:).\n");
&error_exit;
}
if (&header_value(\@Header, 'Subject') eq '') {
im_err("Bad message format (no Subject:).\n");
&error_exit;
}
if ($#Body < 0) {
im_err("No message body.\n");
&error_exit;
}
} else {
if ($#Recipients < 0) {
im_err("No recipients collected.\n");
&error_exit;
}
}
if ($Me_too) {
&error_exit if (&add_to_rcpt(0, $Sender) < 0);
}
my ($d);
foreach $d (split(',', $Dcc_Address)) {
&error_exit if (&add_to_rcpt(0, $d) < 0);
}
# hook before final header processing
&$Hook_PreFinalHeaderProc if ($Hook_PreFinalHeaderProc);
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== before final header processing ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("======================================\n");
# close(NULL);
# }
##### FINAL HEADER PROCESSING #####
&add_header(\@Header, 1, 'X-'.$Resend_prefix.'Dispatcher', $VERSION);
&kill_header(\@Header, 'Bcc', 0);
&kill_header(\@Header, 'Dcc', 0);
&kill_header(\@Header, 'Fcc', 0);
&kill_header(\@Header, 'Resent-Bcc', 0);
&kill_header(\@Header, 'Resent-Dcc', 0);
&kill_header(\@Header, 'Resent-Fcc', 0);
if ($resend_mode) {
&kill_header(\@Header, 'Resent-Sender', 1);
&kill_header(\@Header, 'Resent-From', 1);
&kill_header(\@Header, 'Resent-Message-Id', 1);
} else {
&kill_header(\@Header, 'Sender', 1);
&kill_header(\@Header, 'From', 1);
&kill_header(\@Header, 'Message-Id', 1);
}
if ($Draft_message) {
# annotation headers
&kill_header(\@Header, 'Replied', 0);
&kill_header(\@Header, 'Forwarded', 0);
&kill_header(\@Header, 'Resent', 0);
}
if ($Need_mime_version_header && !&header_value(\@Header, 'Mime-Version')) {
&add_header(\@Header, 1, 'Mime-Version', '1.0');
if ($Has_8bit_body) {
&add_header(\@Header, 1, 'Content-Type',
"Text/plain; charset=$Unknown8bit_label");
## if ISO2022JP
} elsif ($Has_iso2022jp_body) {
if ($Body_code eq 'SJIS' || $Body_code eq 'EUC') {
&add_header(\@Header, 1, 'Content-Type',
"Text/plain; charset=iso-2022-jp"
." (auto-converted from $Body_code)");
} else {
&add_header(\@Header, 1, 'Content-Type',
'Text/plain; charset=iso-2022-jp');
}
## endif
} else {
&add_header(\@Header, 1, 'Content-Type',
'Text/plain; charset=us-ascii');
}
if ($Body_encoding) {
&add_header(\@Header, 1, 'Content-Transfer-Encoding',
$Body_encoding);
}
}
# if (debug{'header'}) {
# open(NULL, '>/dev/null');
# &im_debug("=== after final header processing ===\n");
# &put_header(\*NULL, \@Header, 'smtp', 'original');
# &im_debug("=====================================\n");
# close(NULL);
# }
##### PGP HANDLING #####
if ($PGP_Sign) {
&pgp_process;
}
##### MULTIPART HANDLING #####
&add_multipart if (@Mulipart_messages);
##### SIZE OF MESSAGE BODY FIXED #####
if ($UseLines && &header_value(\@Header, 'Lines') eq '') {
if ($#Body >= 0) {
&add_header(\@Header, 1, 'Lines', $#Body);
} else {
&add_header(\@Header, 1, 'Lines', '0');
}
}
if ($Lines_to_partial > 0 && $#Body > $Lines_to_partial) {
$partial_total = int(($#Body+$Lines_to_partial-1) / $Lines_to_partial);
} else {
$partial_total = 0;
}
&kill_empty_header(\@Header);
&sort_header(\@Header, $HeaderSeq) if ($HeaderSeq);
##### SAVE INTO FOLDER #####
if ($Fcc_folder) {
&set_command_response(); # XXX
foreach $f (split(',', $Fcc_folder)) {
$f = '+' . $f if ($f !~ /^\+/);
my $p = &save_fcc($f, 1, $partial_total);
next if ($p ne '');
im_err("folder carbon copy (into $f) failed.\n");
&log_action('fcc', &get_cur_server(), $f, 'failed');
&error_exit;
}
}
##### QUEUING IF NEEDED #####
if ($JustQueuing) {
if (&queue_message(queue_path(), \@Header, \@Body) == 0) {
if ($Draft_message) {
&trash_message;
}
im_notice("message queued.\n");
exit $EXIT_SUCCESS;
}
im_err("Queuing failed.\n");
&error_exit;
}
##### DISPATCH THE MESSAGE #####
$rcode = &send_message($News_flag, $partial_total);
if ($rcode == 0) {
if ($Smtp_input_mode) {
&smtp_get_mail_final(0) unless ($Error_report_by_mail);
} elsif (&verbose) {
if ($Info) {
print STDERR "\n";
print STDERR $Info;
}
my $session_log = &get_session_log();
if ($session_log) {
print STDERR "\n";
print STDERR $session_log;
}
}
if ($Draft_message) {
&trash_message;
}
&exit_($EXIT_SUCCESS);
} else {
if ($rcode > 0 && $Queuing) {
if (&queue_message(queue_path(), \@Header, \@Body) == 0) {
if ($Draft_message) {
&trash_message;
}
im_info("message queued.\n");
exit $EXIT_SUCCESS;
}
}
im_err("delivery failed.\n");
&error_exit;
}
# end of main
##### INITIALIZATION #####
#
# initialize()
# return value: none
#
sub initialize {
# Constants
$Folding_length = 72;
# Variables
# @Del_headers_on_mail = ('Originator');
@Del_headers_on_news = ('To', 'Cc');
&set_crlf("\n");
# user's information
if (unixp()) {
my ($pw_name, $pw_passwd, $pw_uid, $pw_gid, $pw_quota,
$pw_comment, $pw_gcos, $pw_dir, $pw_shell) = getpwuid($<);
$Home = $pw_dir;
local (@cap) = unpack('aa*', $pw_name);
$cap[0] =~ tr/a-z/A-Z/;
local ($cap) = join('', @cap);
if (!$Sender_name) {
($Sender_name = $pw_gcos) =~ s/,.*$//;
$Sender_name =~ s/&/$cap/g;
}
}
$Login = im_getlogin();
$Home = $ENV{'HOME'} if ($ENV{'HOME'});
srand(time+$$);
# binmode(STDIN);
}
##### SIGNAL HANDLERS #####
#sub alarm_func {
## no operation
#}
sub int_func {
im_info("Terminated by interrupt (SIGINT).\n");
&error_exit;
}
sub term_func {
im_info("Terminated by interrupt (SIGTERM).\n");
&error_exit;
}
##### FINAL INITIALIZATION #####
#
# init_final()
# return value: none
#
sub init_final {
# sender information
if ($Mail_Address ne '' && $Default_from_domain_name eq '') {
my $a = &extract_addr($Mail_Address);
if ($a =~ /(.*)\@(.*)/s) {
my ($u, $h) = ($1, $2);
$Default_from_domain_name = $h;
$User_name = $u unless ($User_name);
}
}
$Login = $User_name if ($User_name); # XXX
unless ($Sender) {
if ($Default_from_domain_name && !$Obey_MTA_domain) {
$Sender = "$Login\@$Default_from_domain_name";
} else {
$Sender = $Login;
}
}
unless ($Message_id_domain_name) {
if ($Default_from_domain_name) {
$Message_id_domain_name = $Default_from_domain_name;
} else {
$Message_id_domain_name = 'unknown-domain';
}
}
@Smtp_servers = split(',', $Smtp_servers);
@Nntp_servers = split(',', $Nntp_servers);
&log_transaction;
# user's require file
if($User_require) {
require $User_require;
}
}
##### SEND A MESSAGE WITH SMTP/NNTP #####
#
# send_message(news_flag, split_flag)
# news_flag: news mode if true
# split_flag: splitting into multiple messages with "partial" format
# is required if true
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub send_message {
local ($news_flag, $split) = @_;
local ($normal, $bcc, $i, $rcode);
# hook before dispatching the message
&$Hook_PreDispatching if ($Hook_PreDispatching);
if ($news_flag) {
if ($split) {
for ($i = 1; $i <= $split; $i++) {
sleep($Partial_sleep) if ($i > 1);
$rcode = &nntp_transaction(\@Nntp_servers, \@Header, \@Body,
$Newsgroups, $i, $split);
return $rcode if ($rcode);
}
} else {
$rcode = &nntp_transaction(\@Nntp_servers, \@Header, \@Body,
$Newsgroups, 0, 0);
return $rcode if ($rcode);
}
# XXX should be controlable? --- yes, of course!
# &add_header(\@Header, 0, 'X-NNTP-Posting-Status',
# "posting successful via " . &get_cur_server");
im_notice("posting succeeded.\n");
}
# header management only for SMTP message
if (!&header_value(\@Header, 'To')
&& !&header_value(\@Header, 'Cc')
&& !&header_value(\@Header, 'Resent-To')
&& !&header_value(\@Header, 'Resent-Cc')
&& !&header_value(\@Header, 'Apparently-To')) {
unless ($Show_Rcpts_Header) {
&add_header(\@Header, 0, 'To', 'undisclosed-recipients:;');
# } else {
# foreach $rec (@Recipients) {
# if ($rec =~ /<(.+)>/) {
# &add_header(\@Header, 0, 'To', $1);
# }
# }
}
}
foreach $rec (@Recipients) {
if ($rec =~ /<.+>/) {
$normal = 1;
} else {
$bcc = 1;
}
}
if ($normal) {
if ($split) {
for ($i = 1; $i <= $split; $i++) {
sleep($Partial_sleep) if ($i > 1);
$rcode = &smtp_transaction(\@Smtp_servers, \@Header, \@Body,
0, $i, $split);
return $rcode if ($rcode);
}
} else {
$rcode = &smtp_transaction(\@Smtp_servers, \@Header, \@Body,
0, 0, 0);
return $rcode if ($rcode);
}
}
if ($bcc) {
if ($split) {
for ($i = 1; $i <= $split; $i++) {
sleep($Partial_sleep) if ($i > 1);
$rcode = &smtp_transaction(\@Smtp_servers, \@Header, \@Body,
1, $i, $split);
return $rcode if ($rcode);
}
} else {
$rcode = &smtp_transaction(\@Smtp_servers, \@Header, \@Body,
1, 0, 0);
return $rcode if ($rcode);
}
}
im_notice("delivery succeeded.\n");
if ($Anno_flag) {
my $ref;
if ($Dist_flag) {
$ref = &header_value(\@Header, 'Message-Id');
&annotate('Resent', $ref) if ($ref ne '');
} elsif (&header_value(\@Header, 'Subject') =~ /Forward:/) { # XXX
my $rfc822;
foreach (@Body) {
if (/^--/) {
$ref = '';
$rfc822 = 0;
} elsif (/^Message-Id:\s*(.*)/i) {
chomp($ref = $1);
} elsif (/^Content-Type:\s*Message\/rfc822/i) {
$rfc822 = 1;
} elsif (/^\n$/ && $ref ne '' && $rfc822) {
&annotate('Forwarded', $ref);
$ref = '';
$rfc822 = 0;
}
}
} else {
$ref = &header_value(\@Header, 'References');
if ($ref ne '') {
if ($ref =~ /(<[\w\%_\/\-\.\@]+>)\s+$/) {
$ref = $1;
}
} else {
$ref = &header_value(\@Header, 'In-Reply-To');
if ($ref =~ /(<[\w\%_\/\-\.\@]+>)/) {
$ref = $1;
} else {
$ref = '';
}
}
&annotate('Replied', $ref) if ($ref ne '');
}
}
return 0;
}
##### SERVER SIDE SMTP PROCESSING (BEFORE GETTING MAIL) #####
#
# smtp_get_mail()
# return value: none
#
sub smtp_get_mail {
local ($state) = 0;
print STDOUT "220 Server ready ($VERSION)\r\n";
while () {
if (/^NOOP\s/i) {
print STDOUT "250 OK\r\n";
} elsif (/^QUIT\s/i) {
print STDOUT "221 Closing connection\r\n";
close(STDOUT);
last;
} elsif (/^HELO\s/i) {
print STDOUT "250 Hello, pleased to meet you\r\n";
$state = 1;
} elsif (/^MAIL FROM:(.*)/i) {
$Sender = &extract_addr($1);
if ($Default_from_domain_name ne ''
&& !$Obey_MTA_domain # XXX
&& $Sender !~ /[\@%!:]/o) {
$Sender .= "\@$Default_from_domain_name";
}
$Sender_name = '';
print STDOUT "250 Sender ok\r\n";
$state = 2;
} elsif (/^RCPT TO:(.*)/i) {
if ($state != 2) {
print STDOUT "503 Need MAIL before RCPT\r\n";
next;
}
&error_exit if (&add_to_rcpt(0, $1) < 0);
print STDOUT "250 Recipient ok\r\n";
$state = 3;
} elsif (/^DATA\s/i) {
if ($state != 3) {
print STDOUT "503 No recipient\r\n";
next;
}
print STDOUT "354 End with '.' on a line by itself\r\n";
if (&read_header('STDIN', \@Header, 1) < 0) {
&error_exit;
}
&read_body("STDIN", \@Body, 1, 0);
unless ($Error_report_by_mail) {
last;
} else {
print STDOUT "250 Message accepted\r\n";
}
} elsif (/^RSET\s/i) {
print STDOUT "503 Not supported\r\n";
} elsif (/^HELP\s/i) {
print STDOUT "250 No information\r\n";
} elsif (/^VRFY\s/i || /^EXPN\s/i) {
print STDOUT "503 Not supported\r\n";
} elsif (/^ONEX\s/i) {
print STDOUT "250 Treated as NOOP\r\n";
} elsif (/^VERB\s/i) {
print STDOUT "250 Verbose mode\r\n";
&set_verbose(1);
} else {
print STDOUT "500 Command unrecognized\r\n";
}
}
}
##### SERVER SIDE SMTP PROCESSING (AFTER GETTING MAIL) #####
#
# smtp_get_mail_final(error_status)
# error_status: status of delivery to be reported
# return value: none
#
sub smtp_get_mail_final {
local ($error) = @_;
return unless (fileno(STDOUT));
unless ($error) {
print STDOUT "250 Message accepted for delivery\n";
}
while () {
if (/^QUIT\s/i) {
print STDOUT "221 Closing connection\r\n";
close(STDOUT);
last;
} elsif (/^HELO\s/i || /^EHLO\s/i) {
print STDOUT "250 Hello\r\n";
} elsif (/^MAIL\s/i || /^RCPT\s/i || /^DATA\s/i) {
print STDOUT "503 Invalid sequence\r\n";
} elsif (/^NOOP\s/i) {
print STDOUT "250 OK\r\n";
} elsif (/^RSET\s/i) {
print STDOUT "503 Not supported\r\n";
} elsif (/^HELP\s/i) {
print STDOUT "250 No information\r\n";
} elsif (/^VRFY\s/i || /^EXPN\s/i) {
print STDOUT "503 Not supported\r\n";
} elsif (/^ONEX\s/i) {
print STDOUT "250 Treated as NOOP\r\n";
} elsif (/^VERB\s/i) {
print STDOUT "250 Verbose mode\r\n";
&set_verbose(1);
} else {
print STDOUT "500 Command unrecognized\r\n";
}
}
}
##### READ MESSAGE FROM MESSAGE FILE #####
#
# read_message(dist_mode)
# dist_mode: redistribution mode if ture
# return value: none
#
sub read_message {
local ($dist_mode) = @_;
local ($message_file);
local (*READ);
if ($dist_mode) {
$message_file = &expand_path($Dist_file);
} else {
my $d;
if ($Draft_folder ne '') {
$d = "$Draft_folder/$Draft_message";
} else {
$d = $Draft_message;
}
$message_file = &expand_path($d);
}
unless (im_open(\*READ, "<$message_file")) {
im_err("Can not open: $message_file\n");
&error_exit;
}
if (&read_header(\*READ, \@Header, 0) < 0) {
&error_exit;
}
&read_body(\*READ, \@Body, 0, 0);
close(READ);
}
##### READ MESSAGE FROM MESSAGE FILE FOR MULTIPART ADDITIONALS #####
#
# read_mp_message(file, content)
# file: message file name to be read
# return value: none
#
sub read_mp_message {
(my $file, local *content) = @_;
local ($message_file);
local (*MP_READ);
if ($file eq '-') {
$message_file = '/dev/tty'; # get from STDIN
} else {
$message_file = &expand_path($file);
}
unless (im_open(\*MP_READ, "<$message_file")) {
im_err("Can not open: $message_file\n");
&error_exit;
}
@content = ();
while () {
push (@content, $_);
}
close(MP_READ);
}
##### ENCAPSULATE MESSAGE BODY #####
#
# encapsulate_body(boundary)
# boundary: boundary of multipart message
# return value: none
#
sub encapsulate_body {
local ($boundary) = @_;
local ($l);
# unshift(@Body, "\n");
# pull down Content-* header lines into body part
foreach $l (@Header) {
if ($l =~ /^(Content-[\w-]+):/i) {
unshift(@Body, $l);
&kill_header(\@Header, $1, 0);
}
}
foreach $l (@Body) {
if ($l !~ /^\n?$/) {
unshift(@Body, "--$boundary\n");
last;
}
}
unshift(@Body, "\n");
}
##### ADDING EXTRA MESSAGES AS MULTIPART #####
#
# add_multipart()
# return value: none
#
sub add_multipart {
local ($part_code);
local($mp_boundary) = &gen_message_id(0);
$mp_boundary =~ y/<@>/-_-/;
&encapsulate_body($mp_boundary);
&add_header(\@Header, 1, 'Mime-Version', '1.0')
if (!&header_value(\@Header, 'Mime-Version'));
&add_header(\@Header, 1, 'Content-Type', "Multipart/mixed;\n"
."\tboundary=\"$mp_boundary\"");
foreach $mp_msg (@Mulipart_messages) {
im_debug("adding message: $mp_msg\n") if (&debug('multipart'));
push(@Body, "--$mp_boundary\n");
&read_mp_message($mp_msg, \@Part);
$part_code = code_check_body(\@Part);
im_debug("code = $part_code\n") if (&debug('multipart'));
# add internal header
if ($part_code eq 'ASCII') {
push(@Body, "Content-Type: Text/plain; charset=us-ascii\n");
## if ISO2022JP
} elsif ($part_code eq 'JIS') {
push(@Body, "Content-Type: Text/plain; charset=iso-2022-jp\n");
} elsif ($part_code eq 'SJIS' || $part_code eq 'EUC') {
if ($Iso2022jp_code_conversion) {
convert_iso2022jp_body(\@Part, $part_code);
if ($Has_Hankaku_kana) {
$part_code = '8BIT';
} else {
push(@Body, "Content-Type: Text/plain; charset=iso-2022-jp"
. " (auto-converted from $part_code)\n");
}
} else {
$part_code = '8BIT';
}
## endif
}
if ($part_code eq '8BIT') {
local ($part_encoding);
push(@Body, "Content-Type: Text/plain; "
. "charset=$Unknown8bit_label\n");
if ($Conv_8to7) {
if ($Need_base64_encoded) {
&body_base64_encode(\@Part);
$part_encoding = 'base64';
} else {
&body_qp_encode(\@Part);
$part_encoding = 'quoted-printable';
}
push(@Body, "Content-Transfer-Encoding: $part_encoding\n");
}
}
if ($Filename_Add) {
($mp_msg_name = $mp_msg) =~ s/^.*\///;
push(@Body, "Content-Disposition: attachment; " .
"filename=$mp_msg_name\n");
}
push(@Body, "\n");
push(@Body, @Part);
}
push(@Body, "--$mp_boundary--\n");
}
##### PGP HANDLING #####
#
# pgp_process()
# return value: none
#
sub pgp_process {
local($b, $flg, @Sign);
$flg = 0;
foreach $b (@Body) {
if ($b !~ /^\n?$/) {
$flg = 1;
last;
}
}
if ($flg == 0) {
# no message body to be signed
return;
}
local($mp_boundary) = &gen_message_id(0);
$mp_boundary =~ y/<@>/-_-/;
&encapsulate_body($mp_boundary);
&add_header(\@Header, 1, 'Mime-Version', '1.0')
if (!&header_value(\@Header, 'Mime-Version'));
&add_header(\@Header, 1, 'Content-Type', "Multipart/signed;\n"
."\tprotocol=\"application/pgp-signature\";\n"
."\tmicalg=\"pgp-md5\";\n"
."\tboundary=\"$mp_boundary\"");
local($got_sign, $retry);
$retry = 0;
do {
pipe('ReadHandle1', 'WriteHandle1');
pipe('ReadHandle2', 'WriteHandle2');
pipe('ReadPassPhrase', 'WritePassPhrase');
$ENV{'PGPPASSFD'} = fileno(ReadPassPhrase);
im_debug("FD $ENV{'PGPPASSFD'} is allocated "
. "for PGP PassPhrase passing\n")
if (&debug('pgp'));
local ($f) = fork;
if ($f < 0) {
im_err("Can not fork to exec PGP program.\n");
&error_exit;
}
if ($f > 0) {
# parents
close('ReadHandle1');
close('WriteHandle2');
close('ReadPassPhrase');
select('ReadHandle2'); $| = 1;
select('WriteHandle1'); $| = 1;
select('WritePassPhrase'); $| = 1;
select(STDOUT);
} else {
# child
close('WriteHandle1');
close('ReadHandle2');
close('WritePassPhrase');
close (STDIN);
open(STDIN, "<&ReadHandle1");
close (STDOUT);
open(STDOUT, ">&WriteHandle2");
close (STDERR) unless (&debug('pgp'));
select('ReadHandle1'); $| = 1;
select('WriteHandle2'); $| = 1;
select('ReadPassPhrase'); $| = 1;
exec ('pgp', '-saf', '+batchmode');
exit $EXIT_SUCCESS;
}
$flg = 2;
foreach $b (@Body) {
if ($flg > 1 && $b =~ /^--$mp_boundary\n$/) {
$flg = 1;
next;
}
next if ($flg > 1);
if ($flg > 0 && $b =~ /^\n$/) {
$flg = 0;
next;
}
next if ($flg > 0);
last if ($b =~ /^--$mp_boundary\n$/);
print WriteHandle1 $b;
}
close('WriteHandle1');
# passphrase required by child process
local($phrase) = &getpass_intract('PGP passphrase: ');
print WritePassPhrase "$phrase\n";
# get the result
$got_sign = 0;
while () {
$got_sign = 1;
push(@Sign, $_);
}
close(ReadHandle2);
close(WritePassPhrase);
} while ($got_sign == 0 && ++$retry < 3);
push(@Body, "--$mp_boundary\n");
push(@Body, "Content-Type: Application/Pgp-Signature\n");
push(@Body, "Content-Transfer-Encoding: 7bit\n");
push(@Body, "\n");
push(@Body, @Sign);
push(@Body, "--$mp_boundary--\n");
unless ($got_sign) {
im_err("Bad pass-phrase\n");
&error_exit;
}
}
##### SAVE MESSAGE FOR FCC #####
#
# save_fcc(folder_name, save_style, partial_total)
# folder_name: folder name to be saved in
# save_style:
# 0 = messages in a file
# 1 = separated messages in a directory
# partial_total: total number of partial messages
# return value:
# NULL: failed
# path_of_file: success
#
sub save_fcc {
my ($folder, $dir_style, $total) = @_;
my ($fcc_dir, $err_remove, $i, $msg, $dead);
my ($FCC);
&set_crlf("\n");
$total = 0 unless ($Fcc_partial);
if ($folder eq '') {
$folder = $Dead_letter;
$dead = 1;
}
unless ($total) {
im_debug("FCC with no spliting.\n") if (&debug('fcc'));
($FCC, $fcc_dir, $msg, $err_remove) = &open_fcc($folder, $dir_style);
unless (defined($FCC)) {
im_debug("FCC open failed.\n") if (&debug('fcc'));
unlink($err_remove) if ($err_remove);
return '';
}
if (&put_header($FCC, \@Header, 'internal', 'original') < 0
|| &put_body($FCC, \@Body, 0, 0) < 0) {
im_err("FCC write failed ($!).\n");
close($FCC);
unlink($err_remove) if ($err_remove);
return '';
}
unless ($fcc_dir) {
unless (print $FCC "\n") {
im_err("FCC write failed ($!).\n");
close($FCC);
return '';
}
}
if (&unixp() && !&no_sync()) {
if (fsync(fileno($FCC)) < 0) {
im_err("FCC write failed ($!).\n");
close($FCC);
return '';
}
}
unless (close($FCC)) {
im_err("FCC write failed ($!).\n");
return '';
}
if ($dead) {
&log_action('dead-letter', &get_cur_server(), $d, 'sent');
} else {
&log_action('fcc', &get_cur_server(), $msg, 'sent');
unless ($Cur_mid ne '' && history_open(1) < 0) {
history_store($Cur_mid, $msg);
history_close();
}
touch_folder($folder);
}
return $msg;
}
im_debug("FCC with spliting into $total.\n") if (&debug('fcc'));
for ($i = 1; $i <= $total; $i++) {
($FCC, $fcc_dir, $msg, $err_remove) = &open_fcc($folder, $dir_style);
unless (defined($FCC)) {
im_debug("FCC open failed.\n") if (&debug('fcc'));
unlink($err_remove) if ($err_remove);
return '';
}
if (&put_mimed_partial($FCC, \@Header, \@Body, 0, $i, $total) < 0) {
im_err("FCC write failed ($!).\n");
close($FCC);
unlink($err_remove) if ($err_remove);
return '';
}
unless ($fcc_dir) {
unless (print $FCC "\n") {
im_err("FCC write failed ($!).\n");
close($FCC);
unlink($err_remove) if ($err_remove);
return '';
}
}
if (&unixp() && !&no_sync()) {
if (fsync(fileno($FCC)) < 0) {
im_err("FCC write failed ($!).\n");
close($FCC);
unlink($err_remove) if ($err_remove);
return '';
}
}
unless (close($FCC)) {
im_err("FCC write failed ($!).\n");
unlink($err_remove) if ($err_remove);
return '';
}
if ($dead) {
&log_action('dead-letter', &get_cur_server(), $d, 'sent');
} else {
&log_action('fcc', &get_cur_server(), $msg, 'sent');
unless ($Cur_mid ne '' && history_open(1) < 0) {
history_store($Cur_mid, $msg);
history_close();
}
touch_folder($folder);
}
}
return $msg;
}
##### MAKE THE INPUT MESSAGE TRASH #####
#
# trash_message()
# return value: none
#
sub trash_message {
return if ($PreserveMessage);
my $d;
if ($Draft_folder ne '') {
$d = "$Draft_folder/$Draft_message";
} else {
$d = $Draft_message;
}
my $message_file = &expand_path($d);
if ($message_file =~ /^(.*\D)(\d+)$/) {
my ($dir, $file) = ($1, $2);
if ($Trashmark) {
if (-f "$dir$Trashmark$file") {
unlink ("$dir$Trashmark$file");
}
if (!rename ("$dir$file", "$dir$Trashmark$file")) {
im_err("Can not rename: $dir$file as $dir$Trashmark$file\n");
&error_exit;
}
im_notice("$dir$file was renamed to $dir$Trashmark$file");
} else {
unlink ("$dir$file");
}
}
}
##### APPEND DIST HEADER #####
#
# append_dist_header()
# return value: none
#
sub append_dist_header {
my $d;
if ($Draft_folder ne '') {
$d = "$Draft_folder/$Draft_message";
} else {
$d = $Draft_message;
}
my ($message_file) = &expand_path($d);
local (*DIST);
unless (im_open(\*DIST, "<$message_file")) {
im_err("Can not open: $message_file\n");
&error_exit;
}
if (&read_header(\*DIST, \@Header, 1) < 0) {
&error_exit;
}
close(DIST);
}
##### MH ANNOTATION #####
#
# annotate()
# return value: none
#
sub annotate {
my ($label, $ref) = @_;
local $_;
im_notice("annotating messages ($ref).\n");
return if (history_open(0) < 0);
my $msg = history_lookup($ref, LookUpMsg);
history_close();
return -1 if ($msg eq '');
my $first = "$label: Date: " . &gen_date(1) . "\n";
my $second = '';
$second = "$label: To: " . join(',', @Recipients) . "\n"
unless ($#Recipients < 0);
$second .= "$label: Newsgroups: $Newsgroups\n"
if ($Newsgroups ne '');
$second .= "$label: Message-Id: $Cur_mid\n" if ($Cur_mid ne '');
EACH: foreach (split(',', $msg)) {
im_notice("annotating $_.\n");
my $cur = &expand_path($_);
my $tmp = &conf_dir . "/anno$$\_"
. substr($cur,rindex($cur,"/")+1,-1) . ".tmp";
# copy to temporary file
unless (im_open(\*ORIGINAL, "<$cur")) {
im_notice("anno: open failed(1): $cur ($!).\n");
next;
}
unless (im_open(\*TMP, ">$tmp")) {
im_err("anno: creation failed: $tmp ($!).\n");
close(ORIGINAL);
return -1;
}
print TMP $first . $second;
my $checked = 0;
my $inheader = 1;
while () {
# check first line
unless ($checked) {
if ($_ eq $first) {
im_debug("already annotated.\n")
if (&debug('anno'));
# already annotated
close(ORIGINAL);
close(TMP);
unlink($tmp);
next EACH;
}
$checked = 1;
}
$inheader = 0 if ($inheader && /^\r?\n$/);
if ($inheader && /^Message-Id:\s*(<.*>)/i) {
if ($ref ne $1) {
im_debug("message-id mismatch.\n")
if (&debug('anno'));
# Message-ID mismatch
close(ORIGINAL);
close(TMP);
unlink($tmp);
next EACH;
}
}
unless (print TMP $_) {
im_err("anno: write failed: $tmp ($!).\n");
close(ORIGINAL);
close(TMP);
unlink($tmp);
return -1;
}
}
close(ORIGINAL);
unless (close(TMP)) {
im_err("anno: write failed: $tmp ($!).\n");
unlink($tmp);
return -1;
}
# copy back to original file (in case of links)
unless (im_open(\*TMP, "<$tmp")) {
im_err("anno: open failed(2): $tmp ($!).\n");
unlink($tmp);
return -1;
}
unless (im_open(\*ORIGINAL, "+<$cur")) {
im_err("anno: open failed(3): $cur ($!).\n");
close(TMP);
unlink($tmp);
return -1;
}
my $size = -s ORIGINAL;
seek(ORIGINAL, 0, 2);
unless (print ORIGINAL $first . $second) {
im_err("anno: can't expand file size: $cur ($!).\n");
truncate(ORIGINAL, $size);
close(ORIGINAL);
close(TMP);
unlink($tmp);
return -1;
}
# seek(ORIGINAL, 0, 0);
unless (close(ORIGINAL)) {
im_err("anno: can't expand file size: $cur ($!).\n");
close(TMP);
unlink($tmp);
return -1;
}
unless (im_open(\*ORIGINAL, "+<$cur")) {
im_err("anno: open failed(4): $cur ($!).\n");
close(TMP);
unlink($tmp);
return -1;
}
while () {
unless (print ORIGINAL $_) {
close(TMP);
close(ORIGINAL);
im_err("anno: $cur may be broken ($!). recover from $tmp!\n");
return -1;
}
}
unless (close(ORIGINAL)) {
close(TMP);
im_err("anno: $cur may be broken ($!). recover from $tmp!\n");
return -1;
}
close(TMP);
unlink ($tmp);
}
return 0;
}
##### PROCESS QUEUED MESSAGES #####
#
# process_queue(dir, deliver)
# dir: queue directory
# deliver: try delivery
# reutrn value: none
#
sub process_queue {
my ($queue_dir, $deliver) = @_;
my ($q, $found);
unless ($deliver) {
require IM::EncDec && import IM::EncDec;
}
unless (-d $queue_dir) {
im_warn("no queue directory\n");
return;
}
unless (opendir(QUEUEDIR, $queue_dir)) {
im_warn("can't read $queue_dir\n");
return;
}
foreach $q (sort {$a <=> $b} readdir(QUEUEDIR)) {
next unless ($q =~ /^\d+$/);
# $QUEUE = 'QUEUE';
rename ("$queue_dir/$q", "$queue_dir/$q.wrk");
unless (im_open(\*QUEUE, "<$queue_dir/$q.wrk")) {
im_err("can't open $queue_dir/$q.wrk\n");
rename ("$queue_dir/$q.wrk", "$queue_dir/$q");
return;
}
im_notice("processing $queue_dir/$q.wrk ...\n");
while () {
chomp;
last if (/^$/);
im_debug("ENV>$_\n") if (&debug('queue'));
if (/^AF:(.*)/) { $Anno_flag = $1; next; }
if (/^NF:(.*)/) { $News_flag = $1; next; }
if (/^PS:(.*)/) { $Partial_sleep = $1; next; }
if (/^SRH:(.*)/) { $Show_Rcpts_Header = $1; next; }
if (/^SFN:(.*)/) { $Smtp_fatal_next = $1; next; }
if (/^DSR:(.*)/) { $Dsn_success_report = $1; next; }
if (/^MID:(.*)/) { $Cur_mid = $1; next; }
if (/^CFG:(.*)/) { $Config_opt = $1; next; }
if (/^PT:(.*)/) { $partial_total = $1; next; }
if (/^S:(.*)/) { $Sender = $1; next; }
if (/^SSV:(.*)/) { @Smtp_servers = split(',', $1); next; }
if (/^NSV:(.*)/) { @Nntp_servers = split(',', $1); next; }
if (/^SSH:(.*)/) { $SSH_server = $1; next; }
if (/^R:(.*)/) { @Recipients = split(',', $1); next; }
if (/^RQ:(.*)/) { $User_require = $1; next; }
im_warn("unknown environment: $_\n");
}
im_debug("reading message\n") if (&debug('queue'));
&read_header(\*QUEUE, \@Header, 0);
&read_body(\*QUEUE, \@Body, 0, 0) if ($deliver);
close(QUEUE);
if ($deliver) {
im_debug("sending message\n") if (&debug('queue'));
$rcode = &send_message($News_flag, $partial_total);
if ($rcode == 0) {
unlink("$queue_dir/$q.wrk");
im_info("$queue_dir/$q: sent\n");
} elsif ($rcode > 0) {
rename ("$queue_dir/$q.wrk", "$queue_dir/$q");
im_info("$queue_dir/$q: preserved\n");
} else {
unlink("$queue_dir/$q.wrk");
im_warn("$queue_dir/$q: delivery failed\n");
&error_report;
}
} else {
my ($r, $t);
rename ("$queue_dir/$q.wrk", "$queue_dir/$q");
print "Message queued in $queue_dir/$q";
if ($Config_opt ne '') {
print " (Config: $Config_opt)\n";
} else {
print "\n";
}
if ($t = &header_value(\@Header, 'Message-ID')) {
print " Message-ID: $t\n";
}
if ($t = &header_value(\@Header, 'Date')) {
print " Date: $t\n";
}
if (($t = &header_value(\@Header, 'Subject')) ne '') {
## if ISO2022JP
$t =~ s/\?=\s*=\?/\?==?/;
$t = &mime_decode_string($t);
$t =~ s/\n\s*//;
## endif
print " Subject: $t\n";
}
if ($News_flag) {
my $ng = &header_value(\@Header, 'Newsgroups');
$ng = &header_value(\@Header, 'BNewsgroups')
if ($ng eq '');
print " Will be posted in $ng\n";
}
print " Recipients:\n";
foreach $r (@Recipients) {
print "\t$r\n";
}
}
@Header = ();
@Body = ();
$found = 1;
}
closedir(QUEUEDIR);
unless ($found) {
im_notice("no messages found in queue\n");
}
return;
}
##### QUEUE THE MESSAGE #####
#
# queue_message(dir)
# dir: queue directory
# reutrn value:
# -1: failure
# 0: success
#
sub queue_message {
local ($queue_dir, *Header, *Body) = @_;
my ($q);
local (*QUEUE);
unless (-d $queue_dir) {
unless (mkdir($queue_dir, &folder_mode(0))) {
im_err("can't create directory $queue_dir\n");
return -1;
}
}
unless (opendir(QUEUEDIR, $queue_dir)) {
im_err("can't read $queue_dir\n");
return -1;
}
$max = 0;
foreach $q (readdir(QUEUEDIR)) {
$q =~ s/\.wrk$//;
if ($q =~ /^\d+$/) {
$max = $q if ($max < $q);
}
}
closedir(QUEUEDIR);
$max++;
my ($fail_cnt) = 0;
im_notice("QUEUE creating $queue_dir/$max\n");
msg_mode(1);
while (&excl_create(\*QUEUE, "$queue_dir/$max") < 0) {
$max++;
if ($fail_cnt++ > 10) {
im_err("too many failures creating QUEUE\n");
return -1;
}
}
# dumping variables
print QUEUE "AF:$Anno_flag\n";
print QUEUE "NF:$News_flag\n";
print QUEUE "PS:$Partial_sleep\n";
print QUEUE "SRH:$Show_Rcpts_Header\n";
print QUEUE "SFN:$Smtp_fatal_next\n";
print QUEUE "DSR:$Dsn_success_report\n";
print QUEUE "MID:$Cur_mid\n";
print QUEUE "CFG:$Config_opt\n";
print QUEUE "PT:$partial_total\n";
print QUEUE "S:$Sender\n";
print QUEUE "RQ:$User_require\n";
print QUEUE "SSV:$Smtp_servers\n";
print QUEUE "NSV:$Nntp_servers\n";
print QUEUE "SSH:$SSH_server\n";
print QUEUE "R:".join(',', @Recipients)."\n";
print QUEUE "\n";
&set_crlf("\n");
if (&put_header(\*QUEUE, \@Header, 'internal', 'all') < 0
|| &put_body(\*QUEUE, \@Body, 0, 0) < 0) {
close(QUEUE);
im_err("queuing to $queue_dir/$max failed ($!).\n");
unlink("$queue_dir/$max");
return -1;
}
unless (close (QUEUE)) {
im_err("queuing to $queue_dir/$max failed ($!).\n");
unlink("$queue_dir/$max");
return -1;
}
im_notice("queued in $queue_dir/$max\n");
&log_action('queue', &get_cur_server(), $max, 'queued');
touch_folder($queue_dir);
return 0;
}
##### ERROR REPORT #####
#
# error_report()
#
sub error_report {
my ($rc);
local ($errlog) = &im_saved_errors();
# reset the server list
@Smtp_servers = split(',', $Smtp_servers);
push(@Smtp_servers, split(',', $Emg_Smtp_servers));
for (my $i = 0; $i <= $#Smtp_servers; $i++) {
$rc = smtp_transaction_for_error_notify($Smtp_servers[$i],
\@Smtp_servers, \@Header, \@Body);
return 0 if ($rc == 0);
smtp_close();
log_action($Esmtp_flag ? 'esmtp' : 'smtp', get_cur_server(),
join(',', @Recipients), 'skipped', command_response());
}
if ($rc) {
im_warn($errlog);
&set_command_response;
&set_cur_server('');
my $d = &save_fcc($Dead_letter, 0, 0); # XXX
&log_action("dead-letter", '', $d, 'sent');
im_warn("Message was saved in $d\n");
return -1;
}
return 0;
}
##### EXIT WITH ERROR REPORT #####
#
# error_exit()
#
sub error_exit {
local ($rc);
# if (!$Error_report_by_mail && !$Smtp_input_mode) {
# $Error_report_by_mail = 1 unless (-t STDERR); # unless TTY
# }
local ($errlog) = &im_saved_errors();
if ($Error_report_by_mail) {
# reset the server list
@Smtp_servers = split(',', $Smtp_servers);
push(@Smtp_servers, split(',', $Emg_Smtp_servers));
for (my $i = 0; $i <= $#Smtp_servers; $i++) {
$rc = &smtp_transaction_for_error_notify($Smtp_servers[$i],
\@Smtp_servers, \@Header, \@Body);
last if ($rc == 0);
smtp_close();
log_action($Esmtp_flag ? 'esmtp' : 'smtp', get_cur_server(),
join(',', @Recipients), 'skipped', command_response());
}
if ($rc) {
# im_warn($errlog);
&set_command_response;
&set_cur_server('');
if ($Draft_message eq '') {
my $d = &save_fcc('', 0, 0); # XXX
im_warn("Message was saved in $d\n");
} else {
&log_action("draft", '', $Draft_message, 'preserved');
im_warn("Draft file $Draft_message preserved.\n");
}
&exit_($EXIT_ERROR);
}
&exit_($EXIT_SUCCESS);
}
if ($Smtp_input_mode) {
while ($errlog) {
$errlog =~ /^([^\n]+)/;
print STDOUT "554-$1\n";
$errlog =~ s/^[^\n]+\n//;
}
while ($Info) {
$Info =~ /^([^\n]+)/;
print STDOUT "554- $1\n";
$Info =~ s/^[^\n]+\n//;
}
my $session_log = &get_session_log();
while ($session_log) {
$session_log =~ /^([^\n]+)/;
print STDOUT "554- $1\n";
$session_log =~ s/^[^\n]+\n//;
}
print STDOUT "554 Message was not accepted\n";
&smtp_get_mail_final(1);
&exit_($EXIT_ERROR);
} else {
# notify to the terminal
# im_err("delivery failed.\n");
# if ($errlog) {
# print STDERR "\n";
# print STDERR "Reason:\n";
# print STDERR "$errlog\n";
# }
if ($Info) {
print STDERR "\n";
print STDERR $Info;
}
my $session_log = &get_session_log();
if ($session_log) {
print STDERR "\n";
print STDERR $session_log;
}
&set_command_response;
&set_cur_server('');
if ($Draft_message eq '') {
# save in dead_letter unless the message was prepared
# as a draft file.
my $d = &save_fcc('', 0, 0); # XXX
im_warn("the message was saved in $d\n");
} else {
&log_action('draft', '', $Draft_message, 'preserved');
im_warn("Draft file $Draft_message preserved.\n");
}
&exit_($EXIT_ERROR);
}
}
##### EXIT #####
#
# exit_(stat)
# stat: exit status
#
sub exit_ {
local ($stat) = @_;
&smtp_close;
&nntp_close;
exit $stat;
}
##### END OF SCRIPT #####
### ToDo list
# PGP encription
# MCI for error respose
# 75 char len (1522)
# select SMTP server on each addresses
# connection timed out (signal)
# refusing large size messgae
# deliverability
# header MIME full encoding
# file locking for appending
# sender name config
# draft all
# @HOSTNAME@
# sgid mail
### 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.
### Local Variables:
### mode: perl
### End: