7

I get this message when there is an error in my crontab:

cron: No MTA installed, discarding output

I don't want to install a MTA on my system, but I also don't want to miss these error messages.

Where is it configured, that cron tries to send these by mail? Can I change that, so that these messages are send to a file instead? (perhaps via sysylog).

I don't want to log all cron messages, just the errors.

I have this in my rsyslog.conf:

cron.=info                    stop

*.*                          |/dev/xconsole

Unfortunately, it seems that even error messages have the .info tag

How can I only log cron errors only? Or, in other words: how can I send to log file, what would otherwise be sent to MTA if it was installed?

My system is Debian 10, and i am using rsyslog for logging (no systemd)

UPDATE:

using redirection for each line individually, as suggested by @basin is the solution I was using up until now, and it has few problems:

First of all, as I stated, I would like a solution that redirects what would normally be sent to MTA by default to some other location, ie |/dev/xconsole, without having to specify for each lie individually.

Second, if there is a syntax error in my crontab line, the redirection does not work. Cron still tries to send the error via MTA, and I get the No MTA installed error in the log.

Is there some way to redirect what would have ben sent via MTA, so that it is sent (either directly or via sysylog) to /dev/xconsole ?

ADDITIONAL QUESTION:

When using the solution suggested by @Binarus, to write my own custom sendmail script:

Instead of using the default /usr/sbin/sendmail , can I specify other location for for my custom script, such as /usr/local/sbin/sendmail ? Where does cron take the information that sendmail is in /usr/sbin/ ? Is this hard-coded, or can it be configured in one of the cron's config files ?

7
  • 1
    The sendmail path is hardcoded: strings /usr/sbin/cron | grep sendmail. That's usually a symbolic link to the actual mta.
    – user313992
    Commented Oct 16, 2021 at 15:36
  • It's trivial to point it to a script which invokes logger or whatever. There probably already are ready-made debian packages which do just that.
    – user313992
    Commented Oct 16, 2021 at 15:38
  • Your additional question: It's hardcoded. That's why I suggested recompiling. In short: In config.h (cron sources) you have #define MAILARG _PATH_SENDMAIL. In pathnames.h (cron sources), you have a #define which sets _PATH_SENDMAIL, but this is not relevant because _PATH_SENDMAIL already is defined here by the inclusion of <paths.h> (glibc sources), and there you finally have #define _PATH_SENDMAIL "/usr/sbin/sendmail". To change the script's location, you could write #define MAILARG "/your/own/path/to/script" in config.h and recompile cron.
    – Binarus
    Commented Oct 18, 2021 at 20:54
  • @CocaineMitch In Debian Buster, it's not even a symbolic link ... What did you mean by "It's trivial to point it to ..."? Did you speak about making /usr/sbin/sendmail a symbolic link pointing to the custom script? Then you'll have the same problem as my solution: What if the "real" program expected there is needed? I fear that there is no other way than recompiling to solve this problem ...
    – Binarus
    Commented Oct 18, 2021 at 21:10
  • @400 the cat As another and last idea, you could put your script as /usr/sbin/sendmail, and make it figure out who has called it when it is executed. If cron has called it, make it behave as described in my solution; if not, let it just call the "real" sendmail binary (wherever you have moved it to) with the same arguments and stdin (that is, just let the script "relay" the arguments and stdin to sendmail).
    – Binarus
    Commented Oct 20, 2021 at 10:22

4 Answers 4

6
+500

I believe I have a solution, but it is only tested halfway. Unfortunately, I couldn't test /dev/xconsole, because I don't have that device on my systems, and I admit that I even don't know what it is and that I don't have the time to research it.

However, there are two good things: First, the following method is generic; that is, you nearly sure can use /dev/xconsole instead of the file name I have used. Second, I have just tested it on Debian Buster, so it really should work out-of-the-box for you.

I'll first show the (surprisingly simple) solution, then explain how it works, and then show some possible problems and how to circumvent them.

Solution

As a precaution, first check whether you have /usr/sbin/sendmail. This should not be the case, because this program usually belongs to an MTA, but you said you didn't have installed an MTA. If it exists, uninstall the package where it is from.

Now, create a script /usr/sbin/sendmail with the following content:

#!/bin/bash
cat >>/root/result

Set the permissions accordingly:

chmod a+rx,u+w,og-w /usr/sbin/sendmail

Restart cron:

systemctl restart cron

That was it. All error messages cron would normally send via email are now going into /root/result.

Of course, you must perform the steps above as root user.

In your case, you probably want to replace /root/result by /dev/xconsole (but please remember that I haven't tested this, as outlined above :-)), and you eventually should replace >> by > (but since I know exactly nothing about /dev/xconsole, this might be wrong).

How does it work?

Definition: In the following text, I'll use SENDMAIL to denote the SENDMAIL software package, and I'll use sendmail to denote an application.

Most (or at least, many) programs which send email messages do not themselves implement an SMTP protocol stack which would be needed to talk to an MTA directly via a socket or a network connection, and also don't incorporate SMTP libraries; making mistakes there would be fatal security-wise, and it's just not necessary because in most cases specialized applications come to rescue.

One of those specialized applications is /usr/bin/sendmail, which is capable of SMTP (and much more), and can be used very easily. A common pattern is:

cat MyMailMessage | /usr/bin/sendmail [sendmail-options]
# OR, even shorter
/usr/bin/sendmail <MyMailMessage

That is, the application constructs a mail message (which is very easy since it only needs a few headers plus the actual body) and pipes it to sendmail, which in turn does the SMTP wizardry.

Historically, SENDMAIL was the predominant MTA for a while. SENDMAIL contains not only an MTA, but also an MSP (message submission program). The MSP part is implemented in /usr/sbin/sendmail. For a long time, nobody got away without SENDMAIL, and therefore many applications still literally rely on /usr/sbin/sendmail or at least sendmail to be usable as mail submission program. This is the reason that even SENDMAIL's competitors like POSTFIX still also provide that program as a compatibility wrapper.

cron behaves like other applications in this respect. It is not capable of SMTP. Instead, it relies on sendmail to actually send messages; it just constructs the raw messages, including headers and body, and pipes them to sendmail, which in turn actually sends them.

So I just had to replace /usr/sbin/sendmail by the script shown above (since I have an MTA installed, this was just for testing). cron calls /usr/sbin/sendmail when it tries to send mail, and this now is our script. The script just takes its standard input and redirects it to the file.

As a side note, I actually don't know whether cron pipes the raw email message directly to sendmail, or whether it puts it into a temporary file first and then calls sendmail with stdin redirected from that file, or whether it does something else.

But that's not important here: The key point is that the raw email message in every case is constructed by cron, and that cron puts it into sendmail's stdin (however this may be accomplished).

Disadvantages, Problems, Improvements

Please note that using cat in the script might not be the best choice efficiency-wise, but this depends on your expected output. There are myriads of articles out there which explain how you best can put a script's stdin into a file; I don't think that this in your question's scope.

One disadvantage of my solution is obvious: Should you ever need the "real" /usr/sbin/sendmail, we have a problem. I could imagine something subtle: There might be applications which change their behavior (especially, error and status reporting) depending on whether they find /usr/sbin/sendmail.

For example, an application may decide to email errors if it finds that executable, but write errors to log files otherwise. This application will now change its behavior. What then happens heavily depends on circumstances. First, you might not find error messages at the usual place. Second, since sendmail now is our simple script and does not work as the application expects, weird things might happen.

There is a remedy, though: You mentioned that you would accept recompiling cron. I believe (but can't verify right now) that in its source code, in config.h, there is a #define which defines the MSP's name it uses. Then the remedy is clear: Rename our script to abcd1234, and use that as the value of the #define, or -probably better- put the script into another directory which is not in the system's search path, and use the complete path to the script as value of the #define.

And while you're at it, you eventually should correct the command line options as well, which are in a separate #define; they don't hurt our script because it just ignores them, though. Possibly you even could dump the script and direct cron's error output directly to the file or device you want. Telling whether that would be possible would require further analysis of the source code, which I haven't conducted. I would stick with the script anyway; see the next paragraph for an important reason.

[ Update per 2021-10-20:

As outlined in my comment to your original question from 2021-10-20, there is an alternative way around the problem which saves you from recompiling: Move the "real" sendmail elsewhere and install the scripts in its place. Then, whenever the script is executed, make it figure out who called it. If cron has called it, make it behave as shown above; if not, make it call the "real" sendmail with the same stdin and parameters, i.e. "relay" stdin and the parameters to the "real" sendmail.

]

Another problem is that you don't see only the error messages you're interested in, but each time see the whole raw email message cron would send, including the headers. The remedy would be to add some code to our script which filters out the lines you are not interested in, e.g. using grep, sed and their friends. But this also is outside the scope of this question.

3

You can tell cron to use a stdout/stderr redirector as your SHELL.

Cron (vixie-cron in particular, as in Debian Buster/10) runs each command in a new $SHELL. This behavior is specified in crontab(5) , and is defined in do_command.c,

The entire command portion of the line, up to a newline or % character, will be executed by /bin/sh or by the shell specified in the SHELL variable of the crontab file.

320 |           char    *shell = env_get("SHELL", jobenv);
... |
348 |           execle(shell, shell, "-c", e->cmd, (char *)0, jobenv);

What would be sent to MTA is anything in the stdout/stderr of $SHELL -c "<command>", regardless of syntax errors or runtime errors. (This is also defined in "do_command.c".) So by setting SHELL=/path/to/stdout/stderr/redirector in your cron file, all your four issues should be addressed:

  1. how can I send to log file, what would otherwise be sent to MTA if it was installed?

  2. without having to specify for each li[n]e individually

  3. if there is a syntax error in my crontab line, the redirection does not work.

  4. cron: No MTA installed, discarding output

I tested it with output to >>/tmp/test, and I think you can replace it with >/dev/xconsole. Or you can write a script to also keep timestamps, detailed commands, etc, such as a wrapper to syslog.

$ cat /tmp/sh-out
#!/usr/bin/sh
1>>/tmp/test 2>>/tmp/test /usr/bin/sh "$@"
$ crontab -l
SHELL=/tmp/sh-out
* * * * * echo output
* * * * * wrong-command
$ tail -f /tmp/test
output
/usr/bin/sh: 1: wrong-command: not found

To answer your other questions,

Where does cron take the information that sendmail is in /usr/sbin/?

Debian 10 and 11 use a heavily patched vixie-cron. The MAILCMD is defined to be _PATH_SENDMAIL in config.h#L24 as /usr/bin/sendmail.

Is there some way to redirect what would have ben sent via MTA, so that it is sent (either directly or via sysylog) to /dev/xconsole?

No, vixie-cron doesn't support that, although you can work around it by changing the commands or the MTA.

Cronie can do it.

       -s     This option will direct Cron to send the job output to the
              system log using syslog(3).  This is useful if your system
              does not have sendmail(8), installed or if mail is
              disabled.

However, cronie is only in Debian Experimental, and the plan to switch from vixie-cron to cronie has no news since 2019-11-05.

Unfortunately, it seems that even error messages have the .info tag

It's hardcoded in misc.c#L589, in the body of void log_it(username, xpid, event, detail)

    syslog(LOG_INFO, "(%s) %s (%s)", username, event, detail);

It seems a patch to log_it to add support for log levels could be used. However, log levels has nothing to do with command outputs in cron, since they are either sent to MTA, or discarded entirely.

3
  • Thanks for this interesting solution, upvoted. However, I believe that there is a flaw (not sure, though, and can't test at the moment): cron probably still tries to execute /usr/bin/sendmail under certain circumstances. You only have changed the shell the commands are executed in, but not how cron reports errors at a global level. Your solution works in most cases because cron does not see any output from executed commands and obviously doesn't try to call sendmail without such output. But what if not the shell executing the commands must report errors, but cron itself?
    – Binarus
    Commented Oct 24, 2021 at 7:28
  • @Binarus Cron errors would be logged, not mailed. It's stated in the vixie-cron man page that "any output is mailed", and "cron logs its action to the syslog facility 'cron'". The last time I checked the source code, I remember it's indeed syslog for cron, and mails for commands.
    – LZY
    Commented Nov 23, 2021 at 7:03
  • I see. I haven't had the time to research it myself. Thank you very much for the clarification!
    – Binarus
    Commented Nov 25, 2021 at 16:06
2

The easiest is to do the redirections in all tasks in your crontab:

*  *  *  *  * user-name  command to be executed >/dev/null 2>>/tmp/some.log

Alternatively, in cronie there's this option:

   -m     This option allows you to specify a shell command to use for sending Cron mail output instead of using sendmail(8) This command  must  accept  a  fully
          formatted  mail message (with headers) on standard input and send it as a mail message to the recipients specified in the mail headers.  Specifying the
          string off (i.e., crond -m off) will disable the sending of mail.

You can create a shell script that will internally cat its input to a file.

I haven't found a similar option in anacron, but maybe replacing /usr/bin/sendmail with a custom script will work.

1
  • thank you, please see my update Commented Oct 11, 2021 at 3:33
1

If your OS uses a sendmail-like mail system by default, you may be able to use a custom mailer.conf file which in turn calls a custom shell script which handles the cron job output however you would like.

This set up requires a sendmail-like environment, but the sendmail daemon itself should be disabled, i.e. not running.

Begin by creating a simple mailer.conf file which will define a substitute shell script to function as the sendmail component for "sending" mail, and also create the shell script itself:

printf 'sendmail\t/etc/mail/mail-script.sh\n' > /etc/mail/mailer.conf

cat << 'EOF' > /etc/mail/mail-script.sh
#!/usr/bin/env bash

{
  date
  printf -- '--- BEGIN\n'
  cat
  printf -- '--- END\n'
} >> /var/log/cron-jobs.log
EOF

chmod 755 /etc/mail/mail-script.sh

That completes the "email" part of the set-up. Now let's create a sample cron job that we can test with:

cat << 'EOF' > /root/testjob.sh 
#!/usr/bin/env bash

printf 'This is a test cron job run at %s\n' "$(date)"
EOF

chmod 755 /root/testjob.sh

Finally, we'll create an entry in /etc/crontab that will run /root/testjob.sh once per minute:

cat << EOF >> /etc/crontab
MAILTO="foobar"
*   *   *   *   *   root    /root/testjob.sh
EOF

The MAILTO entry must be present (or no email will be generated), but is arbitrary. If you wanted to, you could define certain MAILTO names for different purposes, and process the cron job output differently, depending on which MAILTO label the cron job references.

With all that in place, we find the following output accumulating in /var/log/cron-jobs.log:

# tail -f /var/log/cron-jobs.log
Tue Oct 12 14:11:00 PDT 2021
--- BEGIN
From: Cron Daemon <[email protected]>
To: foobar
Subject: Cron <root@test> /root/testjob.sh
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin>
X-Cron-Env: <MAILTO=foobar>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

This is a test cron job run at Tue Oct 12 14:11:00 PDT 2021
--- END
Tue Oct 12 14:12:00 PDT 2021
--- BEGIN
From: Cron Daemon <[email protected]>
To: foobar
Subject: Cron <root@test> /root/testjob.sh
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin>
X-Cron-Env: <MAILTO=foobar>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

This is a test cron job run at Tue Oct 12 14:12:00 PDT 2021
--- END
Tue Oct 12 14:13:00 PDT 2021
--- BEGIN
From: Cron Daemon <[email protected]>
To: foobar
Subject: Cron <root@test> /root/testjob.sh
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin>
X-Cron-Env: <MAILTO=foobar>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

This is a test cron job run at Tue Oct 12 14:13:00 PDT 2021
--- END
Tue Oct 12 14:14:00 PDT 2021
--- BEGIN
From: Cron Daemon <[email protected]>
To: foobar
Subject: Cron <root@test> /root/testjob.sh
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin>
X-Cron-Env: <MAILTO=foobar>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

This is a test cron job run at Tue Oct 12 14:14:00 PDT 2021
--- END
Tue Oct 12 14:15:00 PDT 2021
--- BEGIN
From: Cron Daemon <[email protected]>
To: foobar
Subject: Cron <root@test> /root/testjob.sh
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin>
X-Cron-Env: <MAILTO=foobar>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

This is a test cron job run at Tue Oct 12 14:15:00 PDT 2021
--- END
3
  • I have followed your instructions exactly, but it does not work. The file /var/log/cron-jobs.log is not created, and I still get this in my cron.log: CRON: (root) CMD (root /root/testjob.sh) and CRON: (CRON) info (No MTA installed, discarding output) Commented Oct 13, 2021 at 2:56
  • specifically, the file /etc/mail/mailer.conf seems to have no effect on my system (Debian 10). Commented Oct 14, 2021 at 6:29
  • @400theCat I understand your premise of wanting to avoid installing an MTA, but can you try making a default install of sendmail, and then leaving the service disabled? You don't need sendmail running (i.e. listening on port 25) for the above to work.
    – Jim L.
    Commented Oct 14, 2021 at 17:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .