[GRLUG] PHP -> SASL?
Bill Littlejohn
billl at mtd-inc.com
Thu Nov 4 10:54:15 EDT 2010
I'm not sure I understand your process exactly, but I had similar
issues when we migrated to Google Apps.
We no longer had a way for equipment to send email alerts since Google
only allows SMTP with TLS, which most printers, etc. didn't support.
My solution was to use a relay perl script called GSR. I've been using
it for a couple years now.
I'll attach it so you can review the code if you like since I cannot
find a current download location.
The author has some other updated utilities that may be more to your liking.
http://www.logix.cz/michal/devel/smtp-cli/
I realize this is Perl and you asked for PHP.
If you DO find a solution in PHP for sending emails through Google, I
would love to hear about it. I'm far more comfortable with PHP myself.
-Bill
On Wed, Nov 3, 2010 at 6:42 PM, L. V. Lammert <lvl at omnitec.net> wrote:
> We have a need to send emails that are then forwarded by gmail to a
> remote recipient. Gmail seems to be refusing to forward the messages
> [the filter does not work] so I assume it's because the sender cannot
> be verified?
>
> There is no valid mail server onsite [that's why they use gmail], so
> it seems like the best approach would be to send the email to gmail
> using SASL, but most of the php libraries seem to require a local MTA?
>
> Is there a simple way with php to send email with SASL?
>
> Lee
>
>
> --
> This message has been scanned for viruses and
> dangerous content by MailScanner, and is
> believed to be clean.
>
> _______________________________________________
> grlug mailing list
> grlug at grlug.org
> http://shinobu.grlug.org/cgi-bin/mailman/listinfo/grlug
>
--
This message has been scanned for viruses and
dangerous content by MailScanner, and is
believed to be clean.
-------------- next part --------------
#!/usr/bin/perl
#----------------------------------------------------------------------------#
# PROGRAM: gsr.pl #
# CREATED: 01/12/2005 by James C. Specht, Jr. <james.specht at gmail.com> #
# VERSION: 0.5.0 #
# REMARKS: This program was designed to act as a proxy SMTP server on a #
# linux system that relays its outbound mail through a gmail #
# account. #
# #
# CREDITS: Michal Ludvig <michal at logix.cz> #
# For his simple SMTP client with STARTTLS and AUTH #
# support. #
# See http://www.logix.cz/michal/devel/smtp for more. #
# #
# TODO: #
# 1) Turn this into a daemon process. #
# 2) Find a good way to encrypt the password in the config file. #
# 3) Add some kind of logging. Syslog would be a good place. #
# #
# LICENSE: This program is free to use without restrictions. #
#----------------------------------------------------------------------------#
# REVISED: 02/01/2005 by James C. Specht, Jr. #
# VERSION: 0.6.0 #
# REMARKS: 1. Added the ability to start as a daemon process. #
# 2. Added logging to syslog when a daemon process. #
# #
# TODO: #
# 1) Add the ability to block IPs from using the relay. #
# 2) Find a good way to encrypt the password in the config file. #
#----------------------------------------------------------------------------#
# REVISED: 02/12/2005 by James C. Specht, Jr. #
# VERSION: 0.7.0 #
# REMARKS: 1. Added logic to better handle error messages. #
# 2. Added relay control. #
#----------------------------------------------------------------------------#
# REVISED: 02/20/2005 by James C. Specht, Jr. #
# VERSION: 0.8.0 #
# REMARKS: 1. Added logic to record the PID for the init script. #
# 2. Change logic so if/when no from address is sent we set it to #
# the $gmailusername variable from the config file. #
# BUG FIX: 1. Fixed the bug where I wasn't disconnecting from the client #
# properly. #
# 2. Fixed the bug where I wasn't handling gmail's error codes #
# properly. #
#----------------------------------------------------------------------------#
# REVISED: 03/24/2005 by James C. Specht, Jr. #
# VERSION: 0.9.0 #
# REMARKS: 1. Added logic to handle queuing and resending of mail when gmail #
# does not answer or is refusing connections. #
# 2. Added logic to restrict running more than two instances of #
# this program so that gmail connection failures are not #
# encountered. (JBeck) #
# #
#----------------------------------------------------------------------------#
use Net::SMTP::Server;
use Net::SMTP::Server::Client;
use IO::Socket::INET;
use IO::Socket::SSL;
use Net::SSLeay;
use Digest::HMAC_MD5 qw(hmac_md5_hex);
use MIME::Base64 qw(encode_base64 decode_base64);
use Getopt::Long;
use Term::ReadKey;
use Proc::Daemon;
use Sys::Syslog;
use Fcntl qw(:DEFAULT :flock);
use File::stat;
use Time::localtime;
# Don't care about the children...let them all die!
$SIG{CHLD} = 'IGNORE';
my ($configfile,$ehlo_ok,$from, at to,$msgsrc,$dohelp,$sock,$code,$text,$more,
%features,$server,$conn,$verbose,$version,$daemon,$relayfile, at relayIP,
$pidfile,$client,$queuedir,$flushqueue,$listqueue,$qfile,$resendqueue);
$configfile="/usr/local/etc/gsr.conf";
$relayfile="/usr/local/etc/gsr.relay";
$pidfile="/usr/local/var/run/gsr.pid";
$queuedir="/usr/local/var/gsr-queue";
$verbose=0;
$daemon=0;
$resendqueue=0;
$ehlo_ok = 1;
$version="0.9.0";
# Get command line options.
GetOptions ('config-file=s' => \$configfile,
'relay-file=s' => \$relayfile,
'verbose:1' => \$verbose,
'daemon:1' => \$daemon,
'flush-queue:1' => \$flushqueue,
'list-queue:1' => \$listqueue,
'help:1' => \$dohelp );
require "$configfile";
if ($dohelp == 1 ) {usage();}
if ($listqueue == 1) {ListQueue();}
if ($flushqueue == 1) {FlushQueue();}
# Force This process out as a daemon.
if ($daemon >= 1) {
Proc::Daemon::Init;
# Since we are a daemon process log to syslog facility.
openlog('GSR','pid','mail');
syslog('info','gsr.pl daemon process loading');
$verbose=0;
# Write our pid out.
open(FPID, ">$pidfile");
print FPID "$$";
close(FPID);
}
if ($gmailserver =~ /^(.*):(.*)$/) {
$gmailserver = $1;
$gmailport = $2;
}
if ($daemon >= 1)
{ syslog('info',"Gmail relay set to $gmailserver:$gmailport"); }
if (!defined ($gmailusername)) {
dienice ("Missing \$gmailusername in $configfile!");
}
syslog('info',"Gmail user account is $gmailusername") if ($daemon >= 1);
if ($gmailpassword eq "") {
# We are a daemon so we can't asked for the gmail account password!
if ($daemon >= 1) {
syslog('info',"Missing \$gmailpassword in $configile!");
syslog('info','Aborting startup!');
exit;
}
printf ("Enter password for %s : ", $gmailusername);
# Set echo off.
ReadMode (2);
$gmailpassword = <>;
# Restore echo.
ReadMode (0);
printf ("\n");
exit if (! defined ($gmailpassword));
chop ($gmailpassword);
}
# Build the relayIP table
BuildRelayIPTable();
&StartSMTPServer();
closelog();
exit();
sub StartSMTPServer
{
my $i, $IPaddr, $size, $nrcpt;
$server = new Net::SMTP::Server($localserver,$localport) ||
dienice ("Unable to start SMTP Server on $localserver:$localport!");
while($conn = $server->accept()) {
printf("\nNew connection!\n\n") if ($verbose >= 1);
my $pid = fork();
if ($pid) {undef($conn);} # this is the parent process
dienice ("Can't create child process!") unless defined $pid;
unless($pid) { # $pid == 0 means we're the child
$client = new Net::SMTP::Server::Client($conn) ||
dienice ("Unable to start SMTP client connection!");
# Capture the IP address of our client.
$IPaddr = $client->{SOCK}->peerhost();
syslog('info',"Incoming connection from [$IPaddr]") if ($daemon >= 1);
printf("\nConnection from [$IPaddr]\n") if ($verbose >= 1);
# Do we allow this IP to relay through us?
if (AllowRelay($IPaddr) == 0) {
warnnice("Connection refused to [$IPaddr]");
}
if ($client->process) {
if ($verbose >= 1 ) {
printf("MAIL FROM: $client->{FROM}\n");
for ($i=0; $i <= $#client->{TO}; $i++) {
printf("RCPT TO: $client->{TO}[i]\n");
}
printf("DATA:\n\n$client->{MSG}\n");
}
# Write our queue files
$qfile = GenFileName();
open(FQUEI, ">$queuedir/$qfile.gsri") || dienice("Cannot open $qfile.gsri!");
open(FQUED, ">$queuedir/$qfile.gsrx") || dienice("Cannot open $qfile.gsrx!");
# Now relay the msg on.
$from = $client->{FROM};
print FQUEI "$from\n";
for ($i=0; $i <= $#client->{TO}; $i++) {
$to[$i] = $client->{TO}[$i];
print FQUEI "$to[i]\n";
}
$msgsrc = $client->{MSG};
print FQUED "$msgsrc";
close (FQUEI);
close (FQUED);
if ($daemon >= 1) {
$size = length($msgsrc);
$nrcpt = $#to + 1;
syslog('info',"from=$from, size=$size, nrcpt=$nrcpt, qfile=$qfile");
for ($i=0; $i <= $#to; $i++)
{ syslog('info',"to=$to[$i]"); }
}
printf("Relay to $gmailserver:$gmailport...\n") if ($verbose >= 1);
if ($daemon >= 1)
{ syslog('info',"relay to=$gmailserver:$gmailport:$gmailusername"); }
&RelayOurMSG();
# removed: 0.9.0
#$client->shutdown;
exit 1;
}
}
undef $client;
}
}
sub RelayOurMSG
{
sysopen(FH, "/tmp/gsr.lck", O_WRONLY | O_CREAT) or die "can't open file $!";
printf("Waiting for file lock.\n") if ($verbose >= 1);
if ($daemon >= 1)
{ syslog('info',"Waiting for file lock."); }
flock(FH, LOCK_EX) or die "can't lock file $!";
sleep(1);
printf("File lock acquired.\n") if ($verbose >= 1);
if ($daemon >= 1)
{ syslog('info',"File lock acquired."); }
close(FH) or die "can't close $path: $!";
# Connect to the SMTP server.
$sock = IO::Socket::INET->new(
PeerAddr => $gmailserver,
PeerPort => $gmailport,
Proto => 'tcp',
Timeout => 5) or dieandqueue ("Connect failed: $@");
# Wait for the welcome message of the server.
($code, $text, $more) = &get_line ($sock);
if ($code != 220) {
dieandqueue ("Unknown welcome string: '$code $text'");
}
$ehlo_ok = 0 if ($text !~ /ESMTP/);
# Send EHLO
&say_hello ($sock, $ehlo_ok, $gmailhello, \%features) or exit (1);
# Run the SMTP session
&run_smtp();
# Good bye...
&send_line ($sock, "QUIT\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 221 and $code != 0) {
dieandqueue ("Unknown QUIT response '$code'.");
}
else {
system("rm $queuedir/$qfile.gsri");
system("rm $queuedir/$qfile.gsrx");
if (-e "$queuedir/$qfile.gsrm") {system("rm $queuedir/$qfile.gsrm");}
if ($daemon >= 1) {
syslog('info',"Message relay successful");
syslog('info',"Remove $queuedir/$qfile.gsri");
syslog('info',"Remove $queuedir/$qfile.gsrx");
}
if ($resendqueue != 1) {
if ($daemon >= 1) {
syslog('info',"Attempting to resend any queued messages");
}
&ResendQueue();
}
}
}
# This is the main SMTP "engine".
sub run_smtp
{
# See if we could start encryption
if ((defined ($features{'STARTTLS'}) || defined ($features{'TLS'}))) {
printf ("Starting TLS...\n") if ($verbose >= 1);
# Do Net::SSLeay initialization
Net::SSLeay::load_error_strings();
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();
&send_line ($sock, "STARTTLS\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 220) {
dieandqueue ("Unknown STARTTLS response '$code'.");
}
if (! IO::Socket::SSL::socket_to_SSL($sock,SSL_version => 'SSLv3 TLSv1')) {
dieandqueue ("STARTTLS: ".IO::Socket::SSL::errstr()."");
}
if ($verbose >= 1) {
printf ("Using cipher: %s\n", $sock->get_cipher ());
printf ("%s", $sock->dump_peer_certificate());
}
# Send EHLO again (required by the SMTP standard).
&say_hello ($sock, $ehlo_ok, $gmailhello, \%features) or return 0;
}
# See if we should authenticate ourself
if (defined ($features{'AUTH'})) {
printf ("AUTH method (%s): ", $features{'AUTH'}) if ($verbose >= 1);
# Try CRAM-MD5 if supported by the server
if ($features{'AUTH'} =~ /CRAM-MD5/i ) {
printf ("using CRAM-MD5\n") if ($verbose >= 1);
&send_line ($sock, "AUTH CRAM-MD5\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 334) { dieandqueue ("AUTH failed '$code $text'.\n"); }
my $response = &encode_cram_md5 ($text, $gmailusername, $gmailpassword);
&send_line ($sock, "%s\n", $response);
($code, $text, $more) = &get_line ($sock);
if ($code != 235) { dieandqueue ("AUTH failed: '$code'.\n"); }
}
# Or try LOGIN method
elsif ($features{'AUTH'} =~ /LOGIN/i ) {
printf ("using LOGIN\n") if ($verbose >= 1);
&send_line ($sock, "AUTH LOGIN\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 334) { dieandqueue ("AUTH failed '$code $text'.\n"); }
&send_line ($sock, "%s\n", encode_base64 ($gmailusername, ""));
($code, $text, $more) = &get_line ($sock);
if ($code != 334) { dieandqueue ("AUTH failed '$code $text'.\n"); }
&send_line ($sock, "%s\n", encode_base64 ($gmailpassword, ""));
($code, $text, $more) = &get_line ($sock);
if ($code != 235) { dieandqueue ("AUTH failed '$code $text'.\n"); }
}
# Or finally PLAIN if nothing else was supported.
elsif ($features{'AUTH'} =~ /PLAIN/i ) {
printf ("using PLAIN\n") if ($verbose >= 1);
&send_line ($sock, "AUTH PLAIN %s\n",
encode_base64 ("$gmailusername\0$gmailusername\0$gmailpassword", ""));
($code, $text, $more) = &get_line ($sock);
if ($code != 235) { dieandqueue ("AUTH failed '$code $text'.\n"); }
}
# Complain otherwise.
else {
dieandqueue ("No supported authentication method advertised by the server.\n");
}
if ($daemon >= 1)
{ syslog('info',"Authentication of $gmailusername succeeded"); }
printf ("Authentication of $gmailusername succeeded\n") if ($verbose >= 1);
}
# We can do a relay-test now if a recipient was set.
if ($#to >= 0) {
if (!defined ($from)) {
$from = "<$gmailusername>";
if ($verbose >= 1)
{printf("From: address not set. Using $gmailusername.\n");}
if ($daemon >= 1)
{syslog('info',"From: address not set. Using $gmailusername.");}
}
&send_line ($sock, "MAIL FROM: %s\n", $from);
($code, $text, $more) = &get_line ($sock);
if ($code != 250) { dieandqueue ("MAIL FROM failed: '$code $text'\n"); }
my $i;
for ($i=0; $i <= $#to; $i++) {
&send_line ($sock, "RCPT TO: %s\n", $to[$i]);
($code, $text, $more) = &get_line ($sock);
if ($code != 250) {
dieandqueue ("RCPT TO ".$to[$i]." "."failed: '$code $text'\n");
}
}
}
# Wow, we should even send something!
if (defined ($msgsrc))
{
&send_line ($sock, "DATA\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 354) { dieandqueue ("DATA failed: '$code $text'\n"); }
if ($verbose >= 1) {
printf("Sending the following.\n\n");
printf("$msgsrc\n");
}
$sock->printf ("$msgsrc\r\n.\r\n");
($code, $text, $more) = &get_line ($sock);
if ($code != 250) { dieandqueue ("DATA not sent: '$code $text'\n"); }
}
# Perfect. Everything succeeded!
return 1;
}
# Get one line of response from the server.
sub get_line ($)
{
my $sock = shift;
my ($code, $sep, $text) = ($sock->getline() =~ /(\d+)(.)([^\r]*)/);
my $more;
if ($sep eq "-") { $more = 1; } else { $more = 0; }
printf ("[%d] '%s'\n", $code, $text) if ($verbose >= 1);
return ($code, $text, $more);
}
# Send one line back to the server
sub send_line ($@)
{
my $socket = shift;
my @args = @_;
printf ("> ") if ($verbose >= 1);
printf (@args) if ($verbose >= 1);
$socket->printf (@args);
}
# Helper function to encode CRAM-MD5 challenge
sub encode_cram_md5 ($$$)
{
my ($ticket64, $username, $password) = @_;
my $ticket = decode_base64($ticket64) or
dieandqueue ("Unable to decode Base64 encoded string '$ticket64'");
my $password_md5 = hmac_md5_hex($ticket, $password);
return encode_base64 ("$username $password_md5", "");
}
# Store all server's ESMTP features to a hash.
sub say_hello ($$$$)
{
my ($sock, $ehlo_ok, $gmailhello, $featref) = @_;
my ($feat, $param);
my $hello_cmd = $ehlo_ok ? "EHLO" : "HELO";
&send_line ($sock, "$hello_cmd $gmailhello\n");
my ($code, $text, $more) = &get_line ($sock);
if ($code != 250) { dieandqueue ("$hello_cmd failed: '$code $text'\n"); }
# Empty the hash
%{$featref} = ();
($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
$featref->{$feat} = $param;
# Load all features presented by the server into the hash
while ($more == 1) {
($code, $text, $more) = &get_line ($sock);
($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
$featref->{$feat} = $param;
}
return 1;
}
sub warnnice {
my ($txt) = @_;
if ( $daemon >= 1 ) {
syslog('info', $txt);
syslog('info', 'Connection terminated!');
}
else {
printf("\n$txt\n");
printf("Connection terminated!\n");
}
if ($sock) {
printf("Shutdown!!!!!\n");
&send_line ($sock, "QUIT\n");
}
# removed: 0.9.0
#if ($client) { $client->shutdown;}
exit 1;
}
sub dienice {
my ($txt) = @_;
if ( $daemon >= 1 ) {
syslog('info', $txt);
syslog('info',"Aborting");
}
else {
printf("\n$txt\n");
printf("Aborting\n\n");
}
# removed: 0.9.0
#if ($client) {$client->shutdown;}
exit 1;
}
sub dieandqueue {
my ($txt) = @_;
if ( $daemon >= 1 ) {
syslog('info', $txt);
syslog('info',"Message $qfile queued for later delivery.");
}
else {
printf("\n$txt\n");
printf("Message $qfile queued for later delivery.\n\n");
}
open(FQUEM, ">>$queuedir/$qfile.gsrm") || dienice("Cannot open $qfile.gsrm!");
print FQUEM "$txt\n";
close(FQUEM);
exit 1;
}
sub AllowRelay($) {
my $IP = shift;
my $i;
for ($i=0; $i <= $#relayIP; $i++) {
if ($IP =~ /^$relayIP[$i]/) { return "1";}
}
# Did not find a match.
return "0";
}
sub BuildRelayIPTable {
my ($i);
$i=0;
if ($verbose >= 1) { printf("Loading relay table from $relayfile\n");}
if ($daemon >= 1) { syslog('info',"Loading relay table from $relayfile");}
open(INFILE, "$relayfile") || dienice("Cannot open relay file $relayfile!");
while($line = <INFILE>) {
$line =~ s/#.*//; # ignore comments by erasing them
next if $line =~ /^(\s)*$/; # skip blank lines
chomp($line);
if ($verbose >= 1) { printf("Adding [$line] to relay table\n");}
if ($daemon >= 1) {syslog('info',"Adding [$line] to relay table");}
$relayIP[$i++] = $line;
}
close(INFILE);
}
sub GenFileName {
my $chars;
my @chars = ( "A" .. "Z", 0 .. 9, "a" .. "z" );
my $QFN;
do {
$QFN = join '', map { $chars[ rand @chars ] } 1..8;
} while (-e "$queuedir/$QFN.gsri");
return $QFN;
}
sub ListQueue {
opendir(DIR, "$queuedir");
@files = grep(/\.gsrm$/,readdir(DIR));
closedir(DIR);
printf ("\nGSR\n\nVersion: $version\n\n");
$cnt = @files;
print "$cnt files currently queued.\n\n";
foreach $file (@files) {
($qfile) = split(/\./,$file);
$qsize = stat("$queuedir/$qfile.gsrx")->size;
$qdate = ctime(stat("$queuedir/$qfile.gsrx")->mtime);
print "=" x 70;
printf "\nQueue ID: %8s Size: %-8d Queued on: %s\n\n",$qfile,$qsize,$qdate;
open(FQUEI,"<$queuedir/$qfile.gsri") || dienice("Cannot open $qfile.gsri!");;
@lines = <FQUEI>;
close(FQUEI);
print " From: $lines[0]";
for ($i = 1; $i+1 <= scalar @lines; $i++) {
print " To: $lines[$i]";
}
print "\n";
open(FQUEM,"<$queuedir/$file");
@lines = <FQUEM>;
close(FQUEM);
foreach $line (@lines) {
print " " x 10;
print $line;
}
}
exit (0);
}
sub ResendQueue {
# Set a marker say we are already resending queued mail
$resendqueue=1;
opendir(DIR, "$queuedir");
@files = grep(/\.gsrm$/,readdir(DIR));
closedir(DIR);
foreach $file (@files) {
($qfile) = split(/\./,$file);
open(FQUEI,"<$queuedir/$qfile.gsri") || dienice("Cannot open $qfile.gsri!");;
@lines = <FQUEI>;
close(FQUEI);
$from = $lines[0];
chomp($from);
for ($i = 1; $i+1 <= scalar @lines; $i++) {
$to[$i-1] = $lines[$i];
chomp($to[$i-1]);
}
open(FQUED,"<$queuedir/$qfile.gsrx") || dienice("Cannot open $qfile.gsrx!");;
@lines = <FQUED>;
close(FQUED);
$msgsrc = "";
foreach $line (@lines) {
$msgsrc = $msgsrc . $line;
}
RelayOurMSG();
}
}
sub FlushQueue {
opendir(DIR, "$queuedir");
@files = grep(/\.gsrm$/,readdir(DIR));
closedir(DIR);
printf ("\nGSR\n\nVersion: $version\n\n");
$cnt = @files;
if ($cnt >= 0) {
print "Attempting to resend $cnt queued files.\n\n";
ResendQueue();
}
else { print "Nothing to do. No messages in queue.\n"; }
ListQueue();
exit (0);
}
sub usage ()
{
printf ("\nA simple SMTP server that relays local email through a gmail account.
Author: James C. Specht, Jr. <james.specht\@gmail.com>
http://sourceforge.net/projects/gsr/
Version: $version
Usage: gsr.pl [--options]
--config-file= The location of the gsr.conf file.
(default: /usr/local/etc/gsr.conf)
--relay-file= The location of the gsr.relay file.
(default: /usr/local/etc/gsr.relay)
--verbose Be verbose instead of silent.
--daemon Start as daemon process.
--list-queue List the contents of the queue.
--flush-queue Reprocess all messages in the queue.
--help Shows you this output.
Current Options:
\$gmailserver = [$gmailserver]
\$gmailport = [$gmailport]
\$gmailusername = [$gmailusername]\n");
if ($gmailpassword ne "") {
printf (" \$gmailpassword = [is set]\n");
} else {
printf (" \$gmailpassword = [is NOT set]\n");
}
printf (" \$gmailhello = [$gmailhello]
\$localserver = [$localserver]
\$localport = [$localport]
");
exit (0);
}
More information about the grlug
mailing list