E-mail as a System Console, Part III

by Michael Schwarz, Curtis

Editors' Note: The following is a chapter from the book Multitool Linux, written by Michael Schwarz, Jeremy Anderson, Peter Curtis and Steven Murphy. Consult the book's web site for links, updates and errata.

In Part II, we talked about procmail, fetchmail and their roles in our project, which is finding a way to control our home system's internet access when we are somewhere else.

The Script

Before we get to the script, let's define some requirements. Let's say that in order for procmail to pick up and pass an e-mail containing a command to execute to our Perl script, the subject line must contain the following phrase:

     <console/>

Simple enough. I'm using an XML-style tag here. I could use any word but that leaves me open to accidents. I should be okay, so long as no one sends me an e-mail with a subject line like:

     <console/> is the XML tag I was talking about...let's talk

Of course, once you become a procmail guru, you'll find even better ways to obfuscate the subject line. Moving on, let's also decide that the e-mail body should contain zero or more commands to execute. The Perl script should not barf on an empty e-mail and also should be capable of handling more than one command. Each command output should be separated by some kind of horizontal rule:

     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Another requirement is every command must be surrounded by exec tags. For example:

     <exec>......&lt/exec>

This will make parsing the file easier. To make life really easy on us, let's also require that commands must be on a single line. Parsing multiple lines for a single command block would complicate parsing too much for this basic example. Lastly, we e-mail the results back to the sender, either using the From: or Reply-To: fields. We'll use xmail to accomplish this small part, so make sure it's installed on your system. Here's an example e-mail file that I sent from my web e-mail account, digitalman@musician.org (hosted by www.mail.com):

Figure 1. E-mail Header from Digitalman

Lots of stuff in there, but we're almost done. In this e-mail, I ask the e-mail console to execute three different command blocks: the free command; a set of three commands (cd, pwd and ls); and the netstat command. It's the job of the Perl script to make sense of this mess. Luckily, this is one of the last things we have to do. That is, we have to build the Perl script that parses the e-mail. Here's the code:

#!/usr/bin/perl
     use IPC::Open3;
     use Symbol;
     #-----------------------
     # setup vars for open3
     #-----------------------
     $WTR = gensym();
     $RDR = gensym();
     $ERR = gensym();
     #---------------------------
     # some handy variable defs
     #---------------------------
     $logfile = "/home/stmurphy/.e-mail_console.log";
     $line_sep = 0;
     $separator = "\n" . "~" x 72 . "\n";
     $theOutput = "\n\n";
     $reply_subject = "E-mail Console Results";
     $signature_line =  "\n--\nOutput generated by the E-mail Console\n";
     $no_commands_mesg = "No commands found to execute.";
     $from = "";
     $reply_to = "";
     #---------------
     # open log file
     #---------------
     open LOG, "<<$logfile";
     #--------------------------
     # our parse/execution loop
     #--------------------------
     while(<>) {
         #------------------------
         # get the sender address
         #------------------------
         if ( $_ =~ m/From: (.*)/) {
             $from = $1;
         }
         #---------------------------------
         # get the sender address override
         #---------------------------------
         if ( $_ =~ m/Reply-To: (.*)/) {
 $reply_to = $1;
         }
         #---------------------------
         # look for <exec>...</exec>
         # and process the command
         #---------------------------
         if ( $_ =~ m/^<exec>(.*)<\/exec>/ ) {
             undef $cmd_output;
             if ($line_sep) {
                 $theOutput .= $separator;
             } else {
                 $line_sep = 1;
             }
             $who = ($reply_to ne "") ? "F:$from R:$reply_to" : $from;
             print LOG scalar localtime() . " $who executed [$1]\n";
             $theOutput .= "Executing [$1]\n\n";
             #-----------------------
             # execute the command(s)
             #-----------------------
             open3($WTR, $RDR, $ERR, $1);
             close($WTR);
             while (<$RDR>) {
                 $cmd_output .= $_;
             }
             while (<$ERR>) {
                 $cmd_output .= $_;
             }
             $theOutput .= $cmd_output;
         }
     }
     #-----------------------------
     # report if there were
     # no command executed at all
     #-----------------------------
     if (!$line_sep) {
         $theOutput .= $no_commands_mesg;
     }
     #---------------------------
     # tack on a handy dandy
     # signature line
     #---------------------------
 $theOutput .= $signature_line;
     #-----------------------------
     # override the from address
     # with the reply-to address
     #-----------------------------
     if ($reply_to ne "") {
         $from = $reply_to;
     }
     #----------------------
     # close the log file
     #----------------------
     close LOG;
     #-----------------------
     # send the reply e-mail
     # with the execution
     # results
     #-----------------------
     exec "echo \"$theOutput\" | mailx -s \"$reply_subject\" \"$from\"";
     exit;

I won't go into the code line by line because it's so small. However, I will point out there are many other possible ways to write this code. I kept this code rather simple because it gets the point across. Even so, a nice improvement would be to put in a sigalarm-catching procedure, in case you tell the e-mail console to run an interactive application like top. If you did execute top, the e-mail_console.pl process for that session would simply get stuck waiting for input from you--which you could never give. Continuing on, the code accepts multiple <exec>....</exec> lines and executes whatever the command is in the context of the user running the fetchmail command. This could be root if you so desired, but I highly recommend getting some common sense before allowing the world to execute commands on your system as root via e-mail.

This script also allows you to chain commands using && || and ; if the shell that Perl is running under supports it. At this point, I can safely say the end of this project is nearing and things should be coming together real soon.

Gluing It All Together

The final task is to create your .forward file in your home folder. This is how procmail actually gets executed by your MDA.

     |IFS=' '  && exec /usr/bin/procmail

Make sure the first character is the vertical bar, or pipe, and there are no spaces after it and before the next command. This can cause ugly problems. (From the procmail FAQ: "IFS=' ' sets the shell's "internal field separator" to a single space. This is mostly done to prevent some old sendmail hacks that were based on setting the IFS to something else.") So there you have it. You should be able to send yourself an e-mail with <console/>as the subject and the free command encapsulated with the XML-style tags,

<exec>......</exec>,

With this, your e-mail console should come to life. Of course, at this point, even I could execute commands on your system if I knew your e-mail address.

Right now, the e-mail console is totally open and unsecured. Was that redundant? In order to keep unwanted people from sending e-mail console commands to your system, you need to have a way to authenticate who is sending the command and determine if they are allowed to do this. Additionally, the information being e-mailed back to you is in plain text. If you don't want your command results returned to you in plain text, you have to encrypt the results. For these two tasks, encryption and authentication/authorization, we'll use another handy Linux tool, GnuPG.

Using GnuPG to Handle Authorizations

One small problem with this version of the e-mail console is that anyone is able to send an e-mail to your system and execute commands from your user account. To prevent this, we'll modify the Perl script to include support for digital signature detection and verification. When an e-mail arrives for the e-mail console, it will check for a digital signature and then check it against your special e-mail console keyring to see if it's a valid signature. This means only people you trust can send e-mail console commands to your system.

You could keep on locking it down using other techniques. For this example, we'll keep it simple and assume your friends won't try to erase all your files from your home folder. In addition to authorization, our results should be kept private until the recipient receives the results. To do this, we'll use encryption.

Using GnuPG to Encrypt the Results

So long as the digital signature verifies, we can get down to executing the commands contained in the e-mail and placing the results in an encrypted e-mail back to the sender. We'll use GnuPG to encrypt the execution results. We'll do this by using the e-mail address in the From: or Reply-to: field as the public key identifier when encrypting the results. Sounds easy enough.

Putting It All Together

Both the digital signature verification and the encryption of the results require significant changes to the e-mail_console.pl script. Here is the new version of the script:

#!/usr/bin/perl
     use IPC::Open3;
     use Symbol;
     #-----------------------
     # setup vars for open3
     #-----------------------
     $WTR = gensym();
     $RDR = gensym();
     #---------------------------
     # some handy variable defs
     #---------------------------
     $logfile = "/home/stmurphy/.e-mail_console.log";
     $line_sep = 0;
     $separator = "\n" . "~" x 72 . "\n";
     $theOutput = "\n\n";
     $reply_subject = "E-mail Console Results";
     $signature_line =  "\n--\nOutput generated by the E-mail Console\n";
     $no_commands_mesg = "No commands found to execute.";
     $from = "";
     $reply_to = "";
     #---------------
     # open log file
     #---------------
     open LOG, ">>$logfile";
     #-----------------------------
     # read in the entire file
     #-----------------------------
     @e-mail = <>;
     #----------------------------
     # search for a sender to
     # put as out recipient for
     # the reply back
     #----------------------------
     foreach $_ (@e-mail) {
         #------------------------
         # get the sender address
#------------------------
         if ($_ =~ m/^From: (.*)/) {
             $from = $1;
         }
         #---------------------------------
         # get the sender address override
         #---------------------------------
         if ($_ =~ m/^Reply-To: (.*)/) {
             $reply_to = $1;
         }
     }
     $who = ($reply_to ne "") ? "F:$from R:$reply_to" : $from;
     #--------------------------------------
     # check message for a valid signature
     #--------------------------------------
     open GPG, "|/usr/bin/gpg --batch --verify --keyring ec-keyring
--no-
     default-keyring  </dev/null 2>&1";
     foreach $_ (@e-mail) {
         print GPG $_;
     }
     close GPG;
     $val = $? / 256;
     #-------------------------
     # valid signature found
     #-------------------------
     if ($val == 0) {
         #--------------------------
         # our parse/execution loop
         #--------------------------
         foreach $_ (@e-mail) {
             #---------------------------
             # look for <exec>...</exec>
             # and process the command
             #---------------------------
             if ( $_ =~ m/^<exec>(.*)<\/exec>/ ) {
                 undef $cmd_output;
                 if ($line_sep) {
                     $theOutput .= $separator;
 } else {
                     $line_sep = 1;
                 }
                 print LOG scalar localtime() . " $who executed [$1]\n";
                 $theOutput .= "Executing [$1]\n\n";
                 #-----------------------
                 # execute the command(s)
                 #-----------------------
                 open3($WTR, $RDR, "", $1);
                 close($WTR);
                 while (<$RDR>) {
                     $cmd_output .= $_;
                 }
                 $theOutput .= $cmd_output;
             }
         }
     }
     #-------------------------
     # invalid signature found
     #-------------------------
     elsif ($val == 1) {
         $theOutput .= "Invalid digital signature!";
         print LOG scalar localtime() . " $who - Invalid digital
     signature\n";
     }
     #-------------------------
     # some other problem
     # or not a signed message
     #-------------------------
     elsif ($val <= 2) {
         $theOutput .= "Not a valid E-mail Console e-mail.\nA digital
     signature is required!";
         print LOG scalar localtime() . " $who - No digital signature
     found\n";
     }
     #-----------------------------
     # report if there were
     # no command executed at all
     #-----------------------------
     if (!$val && !$line_sep) {
$theOutput .= $no_commands_mesg;
         print LOG scalar localtime() . " $who - No commands in
e-mail\n";
     }
     #---------------------------
     # tack on a handy dandy
     # signature line
     #---------------------------
     $theOutput .= $signature_line;
     #-----------------------------
     # override the from address
     # with the reply-to address
     #-----------------------------
     if ($reply_to ne "") {
         $from = $reply_to;
     }
     #----------------------
     # close the log file
     #----------------------
     close LOG;
     #----------------------
     # encrypt the results
     #----------------------
     open3($WTR, $RDR, "",  "gpg --armor --output - --recipient
\"$from\"
     --quiet --batch --yes --encrypt");
     print $WTR $theOutput;
     close $WTR;
     undef $theOutput;
     while(<$RDR>) {
         $theOutput .= $_;
     }
     close $RDR;
     #-----------------------
     # send the reply e-mail
     # with the execution
     # results
     #-----------------------
exec "echo \"$theOutput\" | mailx -s \"$reply_subject\" \"$from\"";
     exit;

To simplify my testing of the two versions of the e-mail console, I decided to name the second version e-mail_console_gpg.pl. You should have noticed a few significant changes, such as the logging of requests. I placed the e-mail console log file in a hidden file, .e-mail_console.log, in my home folder. The biggest change is the addition of the GPG code for digital signature verification and execution results encryption.

Finally, I modified the .procmail file to include an additional recipe that sends e-mail with the subject line <gpg-console/> to the new e-mail_console_gpg.pl script. Under normal circumstances, you would not include both recipes and scripts, only the secured versions. If you leave both in there, your system is left open to attacks. It's only included here so you can see the differences. Here's the new .procmailrc file with the new recipe included:

     #------------------------
     # my .procmailrc file
     #-------------------------
     SHELL=/bin/bash
     MAILDIR=${HOME}/Mail
     LOGFILE=${MAILDIR}/procmail.log
     LOG="--- Logging ${LOGFILE} for ${LOGNAME}, "
     #-------------------------------------
     # Recipes
     #-------------------------------------
     :0
     * ^Subject: <console/>
     |/usr/bin/perl ~/bin/e-mail_console.pl
     :0
     * ^Subject: <gpg-console/>
     |/usr/bin/perl ~/bin/e-mail_console_gpg.pl
     #---------------------------------------------
     # catch all recipe
     #---------------------------------------------
     :0:
     ${DEFAULT}

This wraps up securing the e-mail console. However, this isn't everything you could do to secure the e-mail console utility. There are plenty of improvements waiting to be explored.

In case you were wondering if this thing even works, here are some screenshots of the e-mail console in action. This screenshot is of an e-mail being generated for the secured e-mail console:

Figure 2. Generating an E-mail

Here is the e-mail as it is received by my system (I diverted it from procmail for the screenshot):

Figure 3. Received E-mail

Finally, here is the result of the executions. Notice that KMail automatically decrypts the result. I like this feature!

Figure 4. Execution Results

Summary

Here we are at the end of the chapter, and with any luck I've shown you something that was not completely obvious to you. It's funny how ideas can cascade into new ideas and eventually end up coded and implemented on systems. I'm continuing to improve on the e-mail console, and I hope that you might do the same or possibly come up with other innovative ideas based on this project. Some improvements to the e-mail console you could explore include:

  • Improve how authorizations works

  • Use sudo to execute commands as other users on your system

  • Implement running commands as root with heavy duty authentication

Copyright 2002 by Addison-Wesley. All rights reserved. Reproduced by permission of Pearson Education, Inc.

Load Disqus comments