From 82f1c9ac9721ec101b1537574e7efde31b74fe8e Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Mon, 27 Jan 2014 11:04:41 -0800 Subject: [PATCH] sendmail: support sending mail directly to an SMTP relay Allow mail to be sent by connecting directly to an SMTP relay and delivering the message. Make this the default delivery mechanism. Include lots of help in the comments. --- Girocco/Config.pm | 45 +++++++- bin/sendmail.pl | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+), 3 deletions(-) create mode 100755 bin/sendmail.pl diff --git a/Girocco/Config.pm b/Girocco/Config.pm index 8f436be..3b1a069 100644 --- a/Girocco/Config.pm +++ b/Girocco/Config.pm @@ -24,8 +24,17 @@ our $git_http_backend_bin = undef; # Path to the sendmail instance to use. It should understand the -f , -i and -t # options as well as accepting a list of recipient addresses in order to be used here. -# You MUST set this, even if to /usr/sbin/sendmail! -our $sendmail_bin = '/usr/sbin/sendmail'; +# You MUST set this, even if to '/usr/sbin/sendmail'! +# Setting this to 'sendmail.pl' is special and will automatically be expanded to +# a full path to the ../bin/sendmail.pl executable in this Girocco installation. +# sendmail.pl is a sendmail-compatible script that delivers the message directly +# using SMTP to a mail relay host. This is the recommended configuration as it +# minimizes the information exposed to recipients (no sender account names or uids), +# can talk to an SMTP server on another host (eliminating the need for a working +# sendmail and/or SMTP server on this host) and avoids any unwanted address rewriting. +# By default it expects the mail relay to be listening on localhost port 25. +# See the sendmail.pl section below for more information on configuring sendmail.pl. +our $sendmail_bin = 'sendmail.pl'; # E-mail of the site admin our $admin = 'admin@example.org'; @@ -41,7 +50,10 @@ our $admin = 'admin@example.org'; # else such as 'admin-noreply@example.org' and then the 'admin-noreply' address # may be redirected to /dev/null. Setting this to '' or '<>' is not # recommended because that will likely cause the emails to be marked as SPAM -# by the receiver's SPAM filter. +# by the receiver's SPAM filter. If $sendmail_bin is set to 'sendmail.pl' this +# value must be acceptable to the receiving SMTP server as a 'MAIL FROM:' value. +# If this is set to undef and 'sendmail.pl' is used, the 'MAIL FROM:' value will +# be the user the mail is sent as (either $cgi_user or $mirror_user). our $sender = $admin; # Copy $admin on failure/recovery messages? @@ -341,6 +353,30 @@ our $permission_control = 'Group'; # Path to alternate screen multiuser acl file (see screen/README, undef for none) our $screen_acl_file = undef; + +## sendmail.pl configuration + +# Full information on available sendmail.pl settings can be found by running +# ../bin/sendmail.pl -v -h + +# These settings will only used if $sendmail_bin is set to 'sendmail.pl' + +# sendmail.pl host name +#$ENV{'SENDMAIL_PL_HOST'} = 'localhost'; # localhost is the default + +# sendmail.pl port name +#$ENV{'SENDMAIL_PL_PORT'} = '25'; # port 25 is the default + +# sendmail.pl nc executable +#$ENV{'SENDMAIL_PL_NCBIN'} = "$chroot/bin/nc.openbsd"; # default is nc found in $PATH + +# sendmail.pl nc options +# multiple options may be included, e.g. '-4 -X connect -x 192.168.100.10:8080' +#$ENV{'SENDMAIL_PL_NCOPT'} = '-4'; # force IPv4, default is to allow IPv4 & IPv6 + + +## Sanity checks & defaults + # Couple of sanity checks and default settings (do not change these) use Digest::MD5 qw(md5); use MIME::Base64 qw(encode_base64); @@ -348,6 +384,9 @@ $nickname = (split(/[.]/, $name))[0] unless $nickname; our $tmpsuffix = substr(encode_base64(md5($name.':'.$nickname)),0,6); $tmpsuffix =~ tr,+/,=_,; ($mirror_user) or die "Girocco::Config: \$mirror_user must be set even if to current user"; +($basedir) or die "Girocco::Config: \$basedir must be set"; +($sendmail_bin) or die "Girocco::Config: \$sendmail_bin must be set"; +$sendmail_bin = "$basedir/bin/sendmail.pl" if $sendmail_bin eq "sendmail.pl"; $screen_acl_file = "$basedir/screen/giroccoacl" unless $screen_acl_file; $jailreporoot =~ s,^/+,,; ($reporoot) or die "Girocco::Config \$reporoot must be set"; diff --git a/bin/sendmail.pl b/bin/sendmail.pl new file mode 100755 index 0000000..4580c68 --- /dev/null +++ b/bin/sendmail.pl @@ -0,0 +1,300 @@ +#!/usr/bin/perl + +# sendmail.pl - sendmail to SMTP bridge +# Copyright (c) 2014 Kyle J. McKay. All rights reserved. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +use strict; +use warnings; +use bytes; + +use IPC::Open2; +use Net::Domain qw(hostfqdn); + +exit(&main()||0); + +our $VERSION; +my $VERSIONMSG; +my $HELP; +my $USAGE; + +BEGIN { + *VERSION = \'1.0'; + $VERSIONMSG = "sendmail.pl version $VERSION\n" . + "Copyright (c) 2014 Kyle J. McKay. All rights reserved.\n" . + "License GPLv2+: GNU GPL version 2 or later.\n" . + "http://gnu.org/licenses/gpl.html\n" . + "This is free software: you are free to change and redistribute it.\n" . + "There is NO WARRANTY, to the extent permitted by law.\n"; + $USAGE = <[ \t]*$//; + $opt_f =~ s/^[ \t]+//; $opt_f =~ s/[ \t]+$//; + $opt_f .= '@'.$hn if $opt_f && $opt_f !~ /\@/; # some servers require @domain + $opt_f = '<' . $opt_f . '>'; + foreach my $rcpt (split(/,/, join(',', @ARGV))) { + $rcpt =~ s/^[ \t]*[ \t]*$//; + $rcpt =~ s/^[ \t]+//; $rcpt =~ s/[ \t]+$//; + $rcpt .= '@'.$hn if $rcpt && $rcpt !~ /\@/; # some servers require @domain + push(@rcpts, '<' . $rcpt . '>') if $rcpt; + } + @ARGV = (); + + die "sendmail.pl: error: no recipients specified\n$USAGE\n" + unless @rcpts || $opt_t; + + my @headers = (); + my $lasthdr = ''; + my $extraline = ''; + for (;;) { + my $line = <>; + $line = undef if !$opt_i && $line =~ /^[.][\r\n]*$/; + $done = 1, last unless defined($line); + $line =~ s/(?:\r\n|\r|\n)$//os; + $line =~ s/[ \t]+$//; + if ($lasthdr && $line =~ /^[ \t]+(.*)$/) { + # Unfold + $lasthdr .= ' ' . $1; + next; + } + push(@headers, $lasthdr) if $lasthdr; + $lasthdr = ''; + if ($line =~ /^[\x21-\x39\x3b-\x7e]+:/) { + $lasthdr = $line; + next; + } + $extraline = $line; + last; + } + push(@headers, $lasthdr) if $lasthdr; + + if ($opt_t) { + foreach my $hdr (@headers) { + if ($hdr =~ /^(?:To|Cc|Bcc):[ \t]*(.*)$/osi) { + my $alist = $1; + # Very crude parsing here + $alist =~ s/[(].*?[)]//go; # Dump comments + $alist =~ s/["].*?["]//go; # Dump quoted + $alist =~ s/[ \t]+,/,/go; # Kill extra + $alist =~ s/,[ \t]+/,/go; # spaces + foreach my $adr (split(/,/, $alist)) { + my $rcpt = ''; + if ($adr =~ /<([^ \t>]+)>/) { + $rcpt = $1; + } elsif ($adr =~ /^([^ \t]+)$/) { + $rcpt = $1; + } + $rcpt .= '@'.$hn if $rcpt && $rcpt !~ /\@/; # some servers require @domain + push(@rcpts, '<' . $rcpt . '>') if $rcpt; + } + } + } + } + + my $ncbin = $ENV{'SENDMAIL_PL_NCBIN'} || 'nc'; + my $ncopt = $ENV{'SENDMAIL_PL_NCOPT'} || ''; + my @ncopts = (); + @ncopts = split(' ', $ncopt) if $ncopt; + my $nchost = $ENV{'SENDMAIL_PL_HOST'} || 'localhost'; + my $ncport = $ENV{'SENDMAIL_PL_PORT'} || '25'; + my @cmd = (); + push(@cmd, $ncbin, '-w', '30', @ncopts, $nchost, $ncport); + + die "sendmail.pl: error: no recipients specified\n" unless @rcpts; + + my ($send, $recv); + (my $pid = open2($recv, $send, @cmd)) + or die "sendmail.pl: error: nc failed: $!\n"; + + my $resp; + defined($resp = <$recv>) && $resp =~ /^220 / + or die "sendmail.pl: error: failed to receive initial SMTP 220 response\n"; + print $send "HELO localhost\r\n"; + defined($resp = <$recv>) && $resp =~ /^250 / + or die "sendmail.pl: error: failed to receive SMTP HELO 250 response\n"; + + print $send "MAIL FROM: $opt_f\r\n"; + defined($resp = <$recv>) && $resp =~ /^250 / + or die "sendmail.pl: error: SMTP MAIL FROM: $opt_f failed\n"; + foreach my $rcpt (@rcpts) { + print $send "RCPT TO: $rcpt\r\n"; + defined($resp = <$recv>) && $resp =~ /^250 / + or die "sendmail.pl: error: SMTP RCPT TO: $rcpt failed\n"; + } + + print $send "DATA\r\n"; + defined($resp = <$recv>) && $resp =~ /^354 / + or die "sendmail.pl: error: SMTP DATA failed\n"; + foreach my $hdr (@headers) { + print $send "$hdr\r\n" unless $opt_t && $hdr =~ /^Bcc:/i; + } + print $send "\r\n"; + print $send protect($extraline), "\r\n" if $extraline; + + if (!$done) { + while (my $line = <>) { + $line =~ s/(?:\r\n|\r|\n)$//os; + last if !$opt_i && $line =~ /^[.]$/; + print $send protect($line), "\r\n"; + } + } + + print $send ".\r\n"; + defined($resp = <$recv>) && $resp =~ /^250 / + or die "sendmail.pl: error: SMTP message not accepted (@{[cleanup($resp)]})\n"; + + print $send "QUIT\r\n"; + $resp = <$recv>; # Should be /^221 / for goodbye, but we don't really care + close $send; + close $recv; + exit 0; +} -- 2.11.4.GIT