Subject: Re: Gah! How do I...

From: [email-masked] (David W. Tamkin)
Date: Mon, 23 Jan 1995 13:56:39 -0600 (CST)

Mike Campbell asked,

| How do I write a recipe that would:
| 
| * Look for the subject of "command".  (this much I can do)
| * Execute the BODY of the message as a series of commands.
| * Mail the output back to the person who sent it.

First, I want to second Rick Busdiecker's security cautions.

Third, Mike's second specification is ambiguous: execute them under what
interpreter?  If the interpreter requires the commands on its own command
line instead of receiving it as standard input, that will change things.

  :0bfW
  * ^Subject: *command$
  | interpreter # which might be /bin/sh, for example

If the interpreter needs to receive command-line arguments instead of
standard input,

  :0bfW
  * ^Subject: *command$
  | xargs interpreter

Either way, we continue as follows:

 :0A # invert header, keep body but do not prefix it
 | formail -rtkb | $SENDMAIL -oi -t

David W. Tamkin  Box 3284  Skokie, Illinois  60076-6284
[email-masked]    MCI Mail: 426-1818   +1 312 714 5610

---

From: David Meyer <[email-masked]>
Date: Mon, 23 Jan 1995 16:57:05 -0500 (EST)

> line instead of receiving it as standard input, that will change things.

My solution was something like

:0
* ^subject: command$
{
        :0hwc
        FROM=|formail -zrx "To:"

        :0bwc
        | (echo $FROM; cat) | $MAILDIR/bin/command.pl | $SENDMAIL -t

        :0a
        { LASTFOLDER=command HOST=done }
}

The perl script command.pl takes the return address on the first line,
and the body of the message is commands.  My perl script is below -
beware, it's not all that secure.  Nor is it that well commented, and
it's fairly ugly perl.  On the other hand, it does work, and it's easy
enough to add security features like restricting who can run it and
what commands they can run.  I just haven't bothered.

#!/usr/local/bin/perl
$tmpinp="/tmp/command.inp.$$";
open(SAVEINP,">$tmpinp") || die "Can't open $tmpinp" ;
print SAVEINP <<'DONE';
PATH=/usr/local/bin:/bin:/usr/ucb:/usr/bin
set -x
DONE

chop($retpath=<>);

while (<>) {
    if ((!defined($password)) && (/^(password:\s*)(\S*)(.*)/i)) { 
        $password=$2;
	$passecho=$1.'*' x length($2).$3."\n";
	next;
    }
    last if (substr($_, 0, 2) eq '--');	# assume anything after -- is .sig
    print SAVEINP $_ ;
}
close(SAVEINP);

# redirect stderr to stdout, and make both unbuffered
open(STDERR,">&STDOUT");
select(STDERR); $| = 1;
select(STDOUT); $| = 1;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
	localtime(time);
$header="command output from $mon/$mday/$year at $hour:$min:$sec";
print "To: $retpath\nSubject: $header\n\n";
if ($password eq "geronimo") {		# REPLACE PASSWORD!!!
    print $passecho;
    system "/bin/sh $tmpinp";
} else {
    if (length($password) gt 0) {
        print "bad password - cannot run command\n";
    } else {
        print "no password given - cannot run command\n";
    }
}
unlink $tmpinp;

-- 
David M. Meyer                             Mechanical & Aerospace Engineering
[email-masked]          University of Virginia
NeXTmail ok

---

From: [email-masked] (Mike Campbell)
Date: Tue, 24 Jan 1995 07:56:51 +0500

>>>>> David W Tamkin writes:

David>  First, I want to second Rick Busdiecker's security cautions.

David>  Third, Mike's second specification is ambiguous: execute them under
David>  what interpreter?  If the interpreter requires the commands on its own
David>  command line instead of receiving it as standard input, that will
David>  change things.

I understand.  My thought was to somehow also specify the interpreter in the
body of the message (like: SHELL /bin/ksh, or SHELL /usr/local/bin/perl or
something); or in a X-Interpreter: header.

:0bfW 
* ^Subject: *command$ 
| interpreter # which might be /bin/sh,

David>  Either way, we continue as follows:

Ok, this lost me.  Am I to assume here that the first recipe generates some
data to stdout, and it's captured by the second?  (I understand how the f and A
switches work, I am confused on the keeping/deleting of stdout between
recipes.)

+-----------------------------------------------------------------------------+
| Michael Campbell - [email-masked] :: I speak only for myself :: |
| America: So strong we can punish the productive and reward the lazy.        |
+-----------------------------------------------------------------------------+
| If 50 million people say a foolish thing, it is still a foolish thing. -    |
| Anatole France                                                              |
+-----------------------------------------------------------------------------+

---

From: [email-masked] (David W. Tamkin)
Date: Tue, 24 Jan 1995 16:57:55 -0600 (CST)

Michael Campbell wrote,

[I had suggested] ...

- :0bfW 
- * ^Subject: *command$ 
- | interpreter # which might be /bin/sh,

- :0A # invert header, keep body but do not prefix it 
- | formail -rtkb | $SENDMAIL -oi -t

Michael responded,

: Ok, this lost me.  Am I to assume here that the first recipe generates some
: data to stdout, and it's captured by the second?

No, the first recipe performs both of those functions: it filters (`f') the
body (`b') through the program, feeding the original text to it as stdin and
replacing it with the stdout of the program.

`A' means "if the condition(s) matched on the most recently preceding recipe
that doesn't have an `A' flag or an `a' flag, try this recipe; otherwise
skip it."  By the time procmail gets to the second recipe, the first one
has replaced the original body with the stdout of whatever program you fill
in where I wrote "interpreter".