Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Sanity check: Tiny wrapper script for /bin/mail

by FloydATC (Deacon)
on Nov 29, 2013 at 09:02 UTC ( [id://1064926]=perlquestion: print w/replies, xml ) Need Help??

FloydATC has asked for the wisdom of the Perl Monks concerning the following question:

Problem: Many many crontab entries that used to send mail to root in case of problems automatically by crond must now be tagged with subject lines for classification. Output is generated only if the scripts detect problems so in most cases the pipeline will be empty, causing '/bin/mail' to complain. Obviously I don't want those empty messages. Some versions of '/bin/mail' support a setting 'nonullbody', mine unfortunately doesn't seem to.

Solution: Write a small wrapper to '/bin/mail' called '/usr/bin/mail-if', which only calls '/bin/mail' if the pipeline is non-empty:

#!/usr/bin/perl use strict; use warnings; my @lines = <STDIN>; if (@lines) { my $cmd = join(' ', '/bin/mail', @ARGV); open(my $ch, '|-', $cmd) || die "Error executing '$cmd': $!"; foreach my $line (@lines) { print $ch $line; } close $ch; }

Example use:

05 12 * * * root /home/atlas/tools/CRON_macscanner | mail-if -s "[Nettverk][Error] CRON_macscanner" email@address.here

Notice that if the cron job itself generates errors on STDERR those messages will still be sent to root, this is by design.

Question: This appears to work as intended. Does anyone see obvious problems with this approach, or perhaps a simpler solution?

-- FloydATC

Time flies when you don't know what you're doing

Replies are listed 'Best First'.
Re: Sanity check: Tiny wrapper script for /bin/mail
by Bloodnok (Vicar) on Nov 29, 2013 at 17:21 UTC
    Assuming that CRON_macscanner behaves POSIXly, would something like the following not do what you want? ...
    05 12 * * * root /home/atlas/tools/CRON_macscanner >/tmp/CRON_macscann +er 2>&1 || mail -s "[Nettverk][Error] CRON_macscanner" email@address. +here < /tmp/CRON_macscanner
    As per usual, just a thought :-)

    A user level that continues to overstate my experience :-))

      I don't think a single one of those cron scripts return proper exit codes since they were originally meant to produce text output if problems were detected and otherwise just be quiet. But if they did then I suppose this solution would work. As a bonus, this approach would leave traces in /tmp which could be useful for summarizing. Hmm.

      -- FloydATC

      Time flies when you don't know what you're doing

        In which case, this might be slightly more effective...
        05 12 * * * root /home/atlas/tools/CRON_macscanner >/tmp/CRON_macscann +er 2>&1; test -s /tmp/CRON_macscanner && mail -s "[Nettverk][Error] C +RON_macscanner" email@address.here < /tmp/CRON_macscanner

        Be very wary of leaving/using traces in /tmp - on most OS'es, this gets emptied at shutdown &/or startup. If you definitely want records, then I'd suggest creating & using a subdirectory of /var/log.

        A user level that continues to overstate my experience :-))
Re: Sanity check: Tiny wrapper script for /bin/mail
by RichardK (Parson) on Nov 29, 2013 at 15:30 UTC

    looks reasonable, but couldn't you just use exec on a one liner?

    perl -e "exec('/bin/mail',@ARGV) if @ARGV";

      I thought the same, but the idea is to see if input is available on STDIN, not @ARGV :-).

        Doh! Well spotted -- I completely missed that, sorry for the noise.
Re: Sanity check: Tiny wrapper script for /bin/mail
by aitap (Curate) on Nov 30, 2013 at 21:08 UTC
    my $cmd = join(' ', '/bin/mail', @ARGV);
    Isn't this unsafe argument handling? AFAIU, this should break if ran as mail-if root@localhost -s "innocent_message & touch /root/evilfile" (and all kinds of other wierd behaviour are possible with quoted arguments becoming unquoted). Also, checking for data present on STDIN is easier using eof. Therefore, a simplier solution might look like this: perl -e'exec "/bin/mail", @ARGV unless eof STDIN' (untested).

      Hmm...

      The wrapper script doesn't elevate privileges in any way so if you want to touch /root/evilfile then you need root privileges. In which case you can touch whatever you like. Or am I missing something?

      Also, I think in your proposal the data on STDIN would be lost, but I have not tested it either. Using eof() is a simpler way to check for data but in practice it wouldn't save anything in this case. Reading from an EOF handle shouldn't take long, and if it's not EOF then I need to read the data anyway. (Or pass it to /bin/mail some other way?)

      -- FloydATC

      Time flies when you don't know what you're doing

        But if you wanted to send an e-mail with "; touch /root/evilfile" as a subject, you will end up creating a file instead. Also, arguments containing spaces simply break, because, given @ARGV=("login@host", "-s", "some topic") you run /bin/mail login@host -s some topic - without quotes or (preferrably) stating array of command line arguments (multi-argument form of open/system/exec).

        Examples of bad behaviour which can be solved using open(my $ch, "|-", "/bin/mail", @ARGV):

        $ cat if-mail.pl #!/usr/bin/perl exit 0 unless (my @lines = <STDIN>); open(my $mail, "|-", join " ", "/usr/bin/mail", @ARGV) or die $!; print $mail @lines; $ LC_ALL=C ./if-mail.pl root@localhost -s "do not run echo; touch ~/zz +z && ls ~/zzz - it does not make sense" TEST ^D ls: cannot access -: No such file or directory ls: cannot access it: No such file or directory ls: cannot access does: No such file or directory ls: cannot access not: No such file or directory ls: cannot access make: No such file or directory ls: cannot access sense: No such file or directory /home/aitap/zzz $ ./if-mail.pl root@localhost -s "try running echo *" TEST ^D
        Trying to read the mail, I get:
        $ mail From: Krylov Ivan <aitap@tarkus> To: echo@tarkus, not@tarkus, root@localhost, run@tarkus Subject: do Message-ID: <E1VnDEf-0008HF-UG@Tarkus> Date: &#1055;&#1085;&#1076;, 02 &#1044;&#1077;&#1082; 2013 00:0 +3:21 +0400 ---------------------------------------------------------------- TEST ---------------------------------------------------------------- From: Mail Delivery System <Mailer-Daemon@tarkus> To: aitap@tarkus Subject: Mail delivery failed: returning message to sender Message-ID: <E1VnDEg-0008HS-5L@Tarkus> Date: &#1055;&#1085;&#1076;, 02 &#1044;&#1077;&#1082; 2013 00:0 +3:22 +0400 ---------------------------------------------------------------- This message was created automatically by mail delivery software. A message that you sent could not be delivered to one or more of its recipients. This is a permanent error. The following address(es) faile +d: echo@tarkus Unrouteable address not@tarkus Unrouteable address run@tarkus Unrouteable address ------ This is a copy of the message, including all the headers. ----- +- <...> From: Krylov Ivan <aitap@tarkus> To: echo@tarkus, if-mail.pl@tarkus, root@localhost, running@t +arkus Subject: try Message-ID: <E1VnDFD-0008Is-Co@Tarkus> Date: &#1055;&#1085;&#1076;, 02 &#1044;&#1077;&#1082; 2013 00:0 +3:55 +0400 ---------------------------------------------------------------- TEST ---------------------------------------------------------------- From: Mail Delivery System <Mailer-Daemon@tarkus> To: aitap@tarkus Subject: Mail delivery failed: returning message to sender Message-ID: <E1VnDFD-0008Iy-K2@Tarkus> Date: &#1055;&#1085;&#1076;, 02 &#1044;&#1077;&#1082; 2013 00:0 +3:55 +0400 ---------------------------------------------------------------- This message was created automatically by mail delivery software. A message that you sent could not be delivered to one or more of its recipients. This is a permanent error. The following address(es) faile +d: echo@tarkus Unrouteable address if-mail.pl@tarkus Unrouteable address running@tarkus Unrouteable address ------ This is a copy of the message, including all the headers. ----- +- <...>
        (notice if-mail.pl@tarkus in the list of addressees: it's from the expansion of "*" from the subject)

        Also, I think in your proposal the data on STDIN would be lost, but I have not tested it either.
        I was thinking about the simpliest way of passing the STDIN by just jeaving it to the process being executed, but yes, using eof on STDIN before the exec does indeed lose the first line of input (even on pipes). I have not figured a way around this, neither $|++ nor setbuf helped.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1064926]
Approved by boftx
Front-paged by boftx
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (4)
As of 2024-04-19 23:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found