]> git.vanrenterghem.biz Git - www.vanrenterghem.biz.git/blob - gmailsmtp.pl
7d3c2e333f7864f8c83f08877144fb809575fe96
[www.vanrenterghem.biz.git] / gmailsmtp.pl
1 #!/usr/bin/perl
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            #
6 #          account via HTTPS.                                                    #
7 #                                                                                #
8 # Copyright (C) 2005 Frederik Vanrenterghem                                      #
9 #                                                                                #
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.                         #
14 #                                                                                #
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.                                   #
19 #                                                                                #
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.#
23 #                                                                                #
24 # CREATED: 2005/05/21 by Frederik Vanrenterghem <frederik@vanrenterghem.biz>     #
25 #                                                                                #
26 # VERSION: 0.1.0                                                                 #
27 #                                                                                #
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                                             #
31 #                                                                                #
32 #--------------------------------------------------------------------------------#
34 use Mail::Webmail::Gmail;
35 use Net::SMTP::Server;
36 use Net::SMTP::Server::Client;
37 use IO::Socket::INET;
38 use IO::Socket::SSL;
39 use Net::SSLeay;
40 use Digest::HMAC_MD5 qw(hmac_md5_hex);
41 use MIME::Base64 qw(encode_base64 decode_base64);
42 use Getopt::Long;
43 use Term::ReadKey;
44 use Proc::Daemon;
45 use Sys::Syslog;
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,
51     $pidfile,$client);
53 $configfile="/usr/local/etc/gmailsmtp.conf";
54 $relayfile="/usr/local/etc/gmailsmtp.relay";
55 $pidfile="/usr/local/var/run/gmailsmtp.pid";
56 $verbose=0;
57 $daemon=0;
58 $version="0.1.0";
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.
72 if ($daemon >= 1) { 
73   Proc::Daemon::Init; 
74   # Since we are a daemon process log to syslog facility.
75   openlog('gmailsmtp','pid','mail'); 
76   syslog('info','gmailsmtp.pl daemon process loading');
77   $verbose=0;
78   # Write our pid out.
79   open(FPID, ">$pidfile");
80   print FPID "$$";
81   close(FPID);
82
84 $ehlo_ok = 1;
86 if ($gmailserver =~ /^(.*):(.*)$/) {
87   $gmailserver = $1;
88   $gmailport = $2;
89 }
91 if ($daemon >= 1) 
92   { syslog('info',"Gmail relay set to $gmailserver:$gmailport"); }
94 if (!defined ($gmailusername)) { 
95   dienice ("Missing \$gmailusername in $configfile!"); 
96 }
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!
102   if ($daemon >= 1) {
103     syslog('info',"Missing \$gmailpassword in $configile!");
104     syslog('info','Aborting startup!');
105     exit;
106   }
107   printf ("Enter password for %s : ", $gmailusername);
108   # Set echo off.
109   ReadMode (2);
110   $gmailpassword = <>;
111   # Restore echo.
112   ReadMode (0);
113   printf ("\n");
115   exit if (! defined ($gmailpassword));
116   chop ($gmailpassword);
119 # Build the relayIP table
120 BuildRelayIPTable();
122 &StartSMTPServer();
123 closelog();
124 exit();
126 sub StartSMTPServer 
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);
135     
136     my $pid = fork();
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]");
154       } 
155           
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");
161           }
162           printf("DATA:\n\n$client->{MSG}\n");
163         }
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}; 
170         if ($daemon >= 1) {
171           $size = length($msgsrc);
172           $nrcpt = $#to + 1;
173           syslog('info',"from=$from, size=$size, nrcpt=$nrcpt");
174           for ($i=0; $i <= $#to; $i++) 
175             { syslog('info',"to=$to[$i]"); }
176         }
178         printf("Relay via GMail account $gmailusername...\n") if ($verbose >= 1);
179         if ($daemon >= 1) 
180           { syslog('info',"relay via GMail account $gmailusername"); }
182         &RelayOurMSG();
183         #$client->shutdown;
184         exit 1;
185       }
186     }
187     undef $client;
188   }
191 sub RelayOurMSG
193 ###nieuw vanaf hier
194 if ($verbose >= 1) 
195         {printf("Connecting to GMail...\n");}
196     my ( $gmail ) = Mail::Webmail::Gmail->new(
197             username => $gmailusername, password => $gmailpassword, proxy_name=> $proxy_name, );
199 if ($verbose >= 1) 
200         {printf("Connection to Gmail established.\n");} 
202 if ($verbose >= 1) 
203         {printf("Attempting to send message.\n");}
204         
205         my $msgid = $gmail->send_message
206     ( to => $to[0],
207       subject => time(), 
208       msgbody => $msgsrc );
209     print "Msgid: $msgid\n";
210     if ( $msgid ) {
211         if ( $gmail->error() ) {
212                 if ($verbose >= 1) 
213                         {printf("We ran into a problem. Attempting to display the problem...\n");}
214             print $gmail->error_msg();
215         } else {
216             ### Create new label ###
217             if ($verbose >= 1) 
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();
223             } else {
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();
228                 } else {
229                     print "Added label: $test_label to message $msgid\n";
230                 }
231             }
232         }
233     }
234 ###tot hier
236   # Good bye...
237     if ($verbose >= 1) 
238         {printf("Message sent succesfully.\n");} 
239     if ($daemon >= 1) {
240       syslog('info',"Message relay successful");
241     }
244 # This is the main SMTP "engine".
245 sub run_smtp
249   # We can do a relay-test now if a recipient was set.
250   if ($#to >= 0) {
251     if (!defined ($from)) {
252       $from = "<$gmailusername>";
253       if ($verbose >= 1) 
254         {printf("From: address not set.  Using $gmailusername.\n");}
255       if ($daemon >= 1)
256         {syslog('info',"From: address not set. Using $gmailusername.");}
257     }
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"); }
262     my $i;
263     for ($i=0; $i <= $#to; $i++) {
264       &send_line ($sock, "RCPT TO: %s\n", $to[$i]);
265       ($code, $text, $more) = &get_line ($sock);
266       if ($code != 250) {
267         warnnice ("RCPT TO ".$to[$i]." "."failed: '$code $text'\n");
268       }
269     }
270   }
272   # Wow, we should even send something!
273   if (defined ($msgsrc))
274   {
275     &send_line ($sock, "DATA\n");
276     ($code, $text, $more) = &get_line ($sock);
277     if ($code != 354) { warnnice ("DATA failed: '$code $text'\n"); }
279     if ($verbose >= 1) {
280       printf("Sending the following.\n\n");
281       printf("$msgsrc\n");
282     }
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"); }
288   }
290   # Perfect. Everything succeeded!
291   return 1;
294 # Get one line of response from the server.
295 sub get_line ($)
297   my $sock = shift;
298   my ($code, $sep, $text) = ($sock->getline() =~ /(\d+)(.)([^\r]*)/);
299   my $more;
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
306 sub send_line ($@)
308   my $socket = shift;
309   my @args = @_;
310   
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.
328 sub say_hello ($$$$)
330   my ($sock, $ehlo_ok, $gmailhello, $featref) = @_;
331   my ($feat, $param);
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"); }
339   # Empty the hash
340   %{$featref} = ();
342   ($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
343   $featref->{$feat} = $param;
345   # Load all features presented by the server into the hash
346   while ($more == 1) {
347     ($code, $text, $more) = &get_line ($sock);
348     ($feat, $param) = ($text =~ /^(\w+)[= ]*(.*)$/);
349     $featref->{$feat} = $param;
350   }
352   return 1;
355 sub warnnice {
356   my ($txt) = @_;
358   if ( $daemon >= 1 ) { 
359     syslog('info', $txt); 
360     syslog('info', 'Connection terminated!'); 
361   }
362   else { 
363     printf("\n$txt\n"); 
364     printf("Connection terminated!\n"); 
365   }
366   if ($sock) {
367     printf("Shutdown!!!!!\n");
368     &send_line ($sock, "QUIT\n");
369   }
370   if ($client) { $client->shutdown;}
371   exit 1;
374 sub dienice {
375   my ($txt) = @_;
377   if ( $daemon >= 1 ) { 
378     syslog('info', $txt);
379     syslog('info',"Aborting");
380   }
381   else { 
382     printf("\n$txt\n"); 
383     printf("Aborting\n\n");
384   }
385   if ($client) {$client->shutdown;}
386   exit 1;
389 sub AllowRelay($) {
391   my $IP = shift;
392   my $i;
394   for ($i=0; $i <= $#relayIP; $i++) {
395     if ($IP =~ /^$relayIP[$i]/) { return "1";}
396   }
398   # Did not find a match.
399   return "0";  
402 sub BuildRelayIPTable {
404   my ($i);
406   $i=0;
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
415     chomp($line);
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;
420   }
421   close(INFILE);
424 sub usage ()
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/
431 Version: $version
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.           
443 Current Options:
445         \$gmailserver   =       [$gmailserver]
446         \$gmailport     =       [$gmailport]
447         \$gmailusername =       [$gmailusername]\n");
449   if ($gmailpassword ne "") {
450     printf ("   \$gmailpassword =       [is set]\n");
451   } else {
452     printf ("   \$gmailpassword =       [is NOT set]\n");
453   } 
455   printf ("     \$gmailhello    =       [$gmailhello]
456         \$localserver   =       [$localserver]
457         \$localport     =       [$localport]
458 ");
459         exit (0);
462