The problem
Recently I had to deal with a PHP script that was designed to run as a cron job. It does a simple processing: gets some data, creates a CSV file and sends the file using email.
When I manually ran it on the development environment everything went well except for the email not being sent and this strange line of text sneaked in the script’s output:
sh: 1: -t: not found |
The email not being sent wasn’t a surprise, I knew that for some (technical or business) reason, the PHP code is unable to send emails from that particular machine. The strange error message (strange for me) is a different story. It was not generated by my code or by any of its dependencies.
Sure, a quick search on Google could bring some light over the mystery; this is how you reached this page, isn’t it?
Eventually I discovered the root cause of the problem by getting PHP info from the command line.
$ php -i | grep sendmail |
The email is not sent because the property sendmail_from
is not set in php.ini
.
The solution
The fix is really easy: open /etc/php.ini
, search for key sendmail_path
and make sure it reads:
sendmail_path=sendmail -t -i |
Additional or different parameters can be added after the command name (sendmail
). If you use a different mail program on your system (qmail
, postfix
or something else) then replace sendmail
with the name of the mail program you use. This is usually not necessary because most of the alternative mail programs create a link called sendmail
that points to the actual mail program (for compatibility.)
The mystery
However, what is the meaning of each component of the error message?
I peeked into the source code of PHP (version 5.4.16) on GitHub and this is what I found. The code that handles a call to the mail()
function is located in file ext/standard/mail.c.
On line 324 it reads:
324 | sendmail = popen(sendmail_cmd, "w"); |
Several lines above, the variable sendmail_cmd
is initialized with the value of sendmail_path
defined in php.ini
followed by the value of the 5th parameter of function mail()
, if provided (its name is $additional_parameters
.)
In my case, the first parameter of popen()
was -t -i
(no extra parameters were provided on the call to mail()
.)
Who is popen() and what does it do for a living?
popen()
is a system function from the standard C library. Details can be found by running man popen
in an Unix/Linux shell. See some relevant fragmens below:
SYNOPSIS |
This means popen()
creates an instance of the classic Bourne shell /bin/sh
and tells it to run the command -t -i
. I tried this on the devel machine:
axiac:~$ /bin/sh |
Et voilà! The error message is there!
Further investigation
I ran this command on several other Linux/Unix systems (including a Mac OS X Lion) and some of them provided the same output while on the others the 1:
part was missing. Further investigation revealed that on some of the systems that produce the error message containing the 1:
fragment, /bin/sh
is, in fact, dash
.
I run several other commands on dash
‘s command line and I found out that 1
from 1:
is the number of the command it (attempted to) ran since it started.
I never worked with dash
before (except when I thought /bin/sh
is a symlink to bash
and in fact it was dash
.) I cannot tell why dash
numbers the commands it runs and how can it be configured to do (or to not do) it so.
Conclusion
Investigating this cryptic error message we found some interesting things about the implementation of PHP. On a Linux/Unix system there are at least three ways to launch another program: fork()
+exec()
, system()
, popen()
. By default, sendmail
reads the message to send from STDIN
. From the three methods above, popen()
is the easiest (and the most natural) way to launch sendmail
and pass some text to it.
More than that, because popen()
doesn’t run the command by itself but passes it to a shell instance, this approach provides higher flexibility on defining the command used to send emails from PHP.
The first token of sendmail_path
configuration value can be a shell alias, a shell function, a pipe of commands, a list of commands or even a small script.