2 #--------------------------------------------------------------------------------#
3 # PROGRAM: gmailsmtp.pl #
4 # This program was designed to act as a proxy SMTP server on a #
5 # linux system that relays its outbound mail through a gmail #
8 # Copyright (C) 2005 Frederik Vanrenterghem #
10 # This program is free software; you can redistribute it and/or #
11 # modify it under the terms of the GNU General Public License #
12 # as published by the Free Software Foundation; either version 2 #
13 # of the License, or (at your option) any later version. #
15 # This program is distributed in the hope that it will be useful, #
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
18 # GNU General Public License for more details. #
20 # You should have received a copy of the GNU General Public License #
21 # along with this program; if not, write to the Free Software #
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.#
24 # CREATED: 2005/05/21 by Frederik Vanrenterghem <frederik@vanrenterghem.biz> #
28 # CREDITS: James C. Specht, Jr. <james.specht@gmail.com> #
29 # For his gsr.pl v.0.8.0, which supplied the majority #
30 # of the code for v.0.1.0 #
32 #--------------------------------------------------------------------------------#
34 use Mail::Webmail::Gmail;
35 use Net::SMTP::Server;
36 use Net::SMTP::Server::Client;
40 use Digest::HMAC_MD5 qw(hmac_md5_hex);
41 use MIME::Base64 qw(encode_base64 decode_base64);
47 $SIG{CHLD} = 'IGNORE';
49 my ($configfile,$ehlo_ok,$from,@to,$msgsrc,$dohelp,$sock,$code,$text,$more,
50 %features,$server,$conn,$verbose,$version,$daemon,$relayfile,@relayIP,
53 $configfile="/usr/local/etc/gmailsmtp.conf";
54 $relayfile="/usr/local/etc/gmailsmtp.relay";
55 $pidfile="/usr/local/var/run/gmailsmtp.pid";
60 # Get command line options.
61 GetOptions ('config-file=s' => \$configfile,
62 'relay-file=s' => \$relayfile,
63 'verbose:1' => \$verbose,
64 'daemon:1' => \$daemon,
65 'help:1' => \$dohelp );
67 require "$configfile";
69 if ($dohelp == 1 ) {&usage();}
71 # Force this process out as a daemon.
74 # Since we are a daemon process log to syslog facility.
75 openlog('gmailsmtp','pid','mail');
76 syslog('info','gmailsmtp.pl daemon process loading');
79 open(FPID, ">$pidfile");
86 if ($gmailserver =~ /^(.*):(.*)$/) {
92 { syslog('info',"Gmail relay set to $gmailserver:$gmailport"); }
94 if (!defined ($gmailusername)) {
95 dienice ("Missing \$gmailusername in $configfile!");
98 syslog('info',"Gmail user account is $gmailusername") if ($daemon >= 1);
100 if ($gmailpassword eq "") {
101 # We are a daemon so we can't asked for the gmail account password!
103 syslog('info',"Missing \$gmailpassword in $configile!");
104 syslog('info','Aborting startup!');
107 printf ("Enter password for %s : ", $gmailusername);
115 exit if (! defined ($gmailpassword));
116 chop ($gmailpassword);
119 # Build the relayIP table
128 my $i, $IPaddr, $size, $nrcpt;
130 $server = new Net::SMTP::Server($localserver,$localport) ||
131 dienice ("Unable to start SMTP Server on $localserver:$localport!");
133 while($conn = $server->accept()) {
134 printf("\nNew connection!\n\n") if ($verbose >= 1);
138 if ($pid) {undef($conn);} # this is the parent process
139 dienice ("Can't create child process!") unless defined $pid;
140 unless($pid) { # $pid == 0 means we're the child
142 $client = new Net::SMTP::Server::Client($conn) ||
143 dienice ("Unable to start SMTP client connection!");
145 # Capture the IP address of our client.
146 $IPaddr = $client->{SOCK}->peerhost();
148 syslog('info',"Incoming connection from [$IPaddr]") if ($daemon >= 1);
149 printf("\nConnection from [$IPaddr]\n") if ($verbose >= 1);
151 # Do we allow this IP to relay through us?
152 if (AllowRelay($IPaddr) == 0) {
153 warnnice("Connection refused to [$IPaddr]");
156 if ($client->process) {
157 if ($verbose >= 1 ) {
158 printf("MAIL FROM: $client->{FROM}\n");
159 for ($i=0; $i <= $#client->{TO}; $i++) {
160 printf("RCPT TO: $client->{TO}[i]\n");
162 printf("DATA:\n\n$client->{MSG}\n");
164 # Now relay the msg on.
165 $from = $client->{FROM};
166 for ($i=0; $i <= $#client->{TO}; $i++)
167 { $to[$i] = $client->{TO}[$i];}
168 $msgsrc = $client->{MSG};
171 $size = length($msgsrc);
173 syslog('info',"from=$from, size=$size, nrcpt=$nrcpt");
174 for ($i=0; $i <= $#to; $i++)
175 { syslog('info',"to=$to[$i]"); }
178 printf("Relay via GMail account $gmailusername...\n") if ($verbose >= 1);
180 { syslog('info',"relay via GMail account $gmailusername"); }
195 {printf("Connecting to GMail...\n");}
196 my ( $gmail ) = Mail::Webmail::Gmail->new(
197 username => $gmailusername, password => $gmailpassword, proxy_name=> $proxy_name, );
200 {printf("Connection to Gmail established.\n");}
203 {printf("Attempting to send message.\n");}
205 my $msgid = $gmail->send_message
208 msgbody => $msgsrc );
209 print "Msgid: $msgid\n";
211 if ( $gmail->error() ) {
213 {printf("We ran into a problem. Attempting to display the problem...\n");}
214 print $gmail->error_msg();
216 ### Create new label ###
218 {printf("Creating new label\n");}
219 my $test_label = "tl_" . time();
220 $gmail->edit_labels( label => $test_label, action => 'create' );
221 if ( $gmail->error() ) {
222 print $gmail->error_msg();
224 ### Add this label to our new message ###
225 $gmail->edit_labels( label => $test_label, action => 'add', 'msgid' => $msgid );
226 if ( $gmail->error() ) {
227 print $gmail->error_msg();
229 print "Added label: $test_label to message $msgid\n";
238 {printf("Message sent succesfully.\n");}
240 syslog('info',"Message relay successful");
244 # This is the main SMTP "engine".
249 # We can do a relay-test now if a recipient was set.
251 if (!defined ($from)) {
252 $from = "<$gmailusername>";
254 {printf("From: address not set. Using $gmailusername.\n");}
256 {syslog('info',"From: address not set. Using $gmailusername.");}
258 &send_line ($sock, "MAIL FROM: %s\n", $from);
259 ($code, $text, $more) = &get_line ($sock);
260 if ($code != 250) { warnnice ("MAIL FROM failed: '$code $text'\n"); }
263 for ($i=0; $i <= $#to; $i++) {
264 &send_line ($sock, "RCPT TO: %s\n", $to[$i]);
265 ($code, $text, $more) = &get_line ($sock);
267 warnnice ("RCPT TO ".$to[$i]." "."failed: '$code $text'\n");
272 # Wow, we should even send something!
273 if (defined ($msgsrc))
275 &send_line ($sock, "DATA\n");
276 ($code, $text, $more) = &get_line ($sock);
277 if ($code != 354) { warnnice ("DATA failed: '$code $text'\n"); }
280 printf("Sending the following.\n\n");
284 $sock->printf ("$msgsrc\r\n.\r\n");
286 ($code, $text, $more) = &get_line ($sock);
287 if ($code != 250) { warnnice ("DATA not sent: '$code $text'\n"); }
290 # Perfect. Everything succeeded!
294 # Get one line of response from the server.
298 my ($code, $sep, $text) = ($sock->getline() =~ /(\d+)(.)([^\r]*)/);
300 if ($sep eq "-") { $more = 1; } else { $more = 0; }
301 printf ("[%d] '%s'\n", $code, $text) if ($verbose >= 1);
302 return ($code, $text, $more);
305 # Send one line back to the server
311 printf ("> ") if ($verbose >= 1);
312 printf (@args) if ($verbose >= 1);
313 $socket->printf (@args);
316 # Helper function to encode CRAM-MD5 challenge
317 sub encode_cram_md5 ($$$)
319 my ($ticket64, $username, $password) = @_;
320 my $ticket = decode_base64($ticket64) or
321 dienice ("Unable to decode Base64 encoded string '$ticket64'");
323 my $password_md5 = hmac_md5_hex($ticket, $password);
324 return encode_base64 ("$username $password_md5", "");
327 # Store all server's ESMTP features to a hash.
330 my ($sock, $ehlo_ok, $gmailhello, $featref) = @_;
332 my $hello_cmd = $ehlo_ok ? "EHLO" : "HELO";
334 &send_line ($sock, "$hello_cmd $gmailhello\n");
335 my ($code, $text, $more) = &get_line ($sock);
337 if ($code != 250) { warnnice ("$hello_cmd failed: '$code $text'\n"); }
342 ($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
343 $featref->{$feat} = $param;
345 # Load all features presented by the server into the hash
347 ($code, $text, $more) = &get_line ($sock);
348 ($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
349 $featref->{$feat} = $param;
358 if ( $daemon >= 1 ) {
359 syslog('info', $txt);
360 syslog('info', 'Connection terminated!');
364 printf("Connection terminated!\n");
367 printf("Shutdown!!!!!\n");
368 &send_line ($sock, "QUIT\n");
370 if ($client) { $client->shutdown;}
377 if ( $daemon >= 1 ) {
378 syslog('info', $txt);
379 syslog('info',"Aborting");
383 printf("Aborting\n\n");
385 if ($client) {$client->shutdown;}
394 for ($i=0; $i <= $#relayIP; $i++) {
395 if ($IP =~ /^$relayIP[$i]/) { return "1";}
398 # Did not find a match.
402 sub BuildRelayIPTable {
408 if ($verbose >= 1) { printf("Loading relay table from $relayfile\n");}
409 if ($daemon >= 1) { syslog('info',"Loading relay table from $relayfile");}
411 open(INFILE, "$relayfile") || dienice("Cannot open relay file $relayfile!");
412 while($line = <INFILE>) {
413 $line =~ s/#.*//; # ignore comments by erasing them
414 next if $line =~ /^(\s)*$/; # skip blank lines
416 if ($verbose >= 1) { printf("Adding [$line] to relay table\n");}
417 if ($daemon >= 1) {syslog('info',"Adding [$line] to relay table");}
419 $relayIP[$i++] = $line;
426 printf ("\nA simple SMTP server that relays local email through a gmail account via HTTPS.
428 Author: Frederik Vanrenterghem <frederik\@vanrenterghem.biz>
429 http://vanrenterghem.biz/
433 Usage: gmailsmtp.pl [--options]
435 --config-file= The location of the gmailsmtp.conf file.
436 (default: /usr/local/etc/gmailsmtp.conf)
437 --relay-file= The location of the gmailsmtp.relay file.
438 (default: /usr/local/etc/gmailsmtp.relay)
439 --verbose Be verbose instead of silent.
440 --daemon Start as daemon process.
441 --help Shows you this output.
445 \$gmailserver = [$gmailserver]
446 \$gmailport = [$gmailport]
447 \$gmailusername = [$gmailusername]\n");
449 if ($gmailpassword ne "") {
450 printf (" \$gmailpassword = [is set]\n");
452 printf (" \$gmailpassword = [is NOT set]\n");
455 printf (" \$gmailhello = [$gmailhello]
456 \$localserver = [$localserver]
457 \$localport = [$localport]