Username/Email:  Password: 
TwitterFacebookFlickrRSS

E-mail as a System Console, Part III

In today's final part of this series, the scripts are put together and made secure.

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.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Re: E-mail as a System Console, Part III

Anonymous's picture

Dec 18 16:09:30 fsfile1 sendmail[26935]: gBIL9UK26934: gBIL9UK26935: DSN: /home/mailcmd/.forward: line 1: |IFS=' ' && exec /usr/bin/procmail... Address is unsafe for mailing to programs

How do address this Sendmail version 8 problem????

Re: E-mail as a System Console, Part III

Anonymous's picture

One improvement that comes to mind is using the perl GPG modules instead of a system call.

Re: E-mail as a System Console, Part III

Anonymous's picture

Thanks for the great article! I adapted it to do a specific lookup application I had been pondering for a while. It's working great!

I found a little bug in the code relating to the way the From: is parsed.

Some mail clients like Outlook double quote the name on the From: line like "My Name" . This causes trouble later in the mailx command, since the variable $from is (properly) quoted to make sure names like John P. Doe work correctly. If $from already has quotes, the result is ""My Name"

My solution was to strip any quotes from the $from variable right after it is parsed:

if ($_ =~ m/^From: (.*)/) {

$from = $1;

$from =~ s/"//g; <--- add

}

Re: E-mail as a System Console, Part III

Anonymous's picture

Interesting job !!.

Are you try with Jabber ? . Maybe an automata with perl

....XML #cat /etc/group XML .... can do this work too.

Cheers, Enrique Meza//