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.The ScriptBefore 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):
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 TogetherThe 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 AuthorizationsOne 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 ResultsSo 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 TogetherBoth 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
SummaryHere 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.
email: mtl@multitool.net
email: mtl@multitool.net










This week 5 lucky Members will receive a copy of The Official Ubuntu Server Book by Benjamin Mako Hill and Linux Journal's very own Kyle Rankin. No entry necessary. Check back here early next week to find out who the lucky Online Members are.




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//
Post new comment