E-mail as a System Console, Part III

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.
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>......</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):
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.
Trending Topics
| You Need A Budget | Feb 10, 2012 |
| The Linux powered LAN Gaming House | Feb 08, 2012 |
| Creating a vDSO: the Colonel's Other Chicken | Feb 06, 2012 |
| Your CMS Is Not Your Web Site | Feb 01, 2012 |
| Casper, the Friendly (and Persistent) Ghost | Jan 31, 2012 |
| Razor-qt 0.4 - Qt based Desktop Environment | Jan 30, 2012 |
- Fun with ethtool
- Parallel Programming with NVIDIA CUDA
- 100% disappointed with the decision to go all digital.
- Readers' Choice Awards 2011
- Linux-Based X Terminals with XDMCP
- Validate an E-Mail Address with PHP, the Right Way
- You Need A Budget
- Why Python?
- The Linux powered LAN Gaming House
- Python for Android
- BeOS was the best
46 min 22 sec ago - I use Wireshark on a daily
5 hours 16 min ago - buena información
10 hours 23 min ago - One important "bucket" that I didn't note (désolé si qqun deja d
11 hours 24 min ago - Gnome3 is such a POS. No one
20 hours 51 min ago - Gnome 3 is the biggest POS
21 hours 2 min ago - I didn't knew this thing by
1 day 3 hours ago - Author's reply
1 day 6 hours ago - Link to modlys
1 day 7 hours ago - I use YNAB because of the
1 day 7 hours ago






Comments
Re: E-mail as a System Console, Part III
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
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
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
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//