1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Rasmus Lerdorf <rasmus@php.net>                              |
16   +----------------------------------------------------------------------+
17 */
18
19/* $Id$ */
20
21#include <stdlib.h>
22#include <ctype.h>
23#include <stdio.h>
24#include <time.h>
25#include "php.h"
26#include "ext/standard/info.h"
27#include "ext/standard/php_string.h"
28#include "ext/standard/basic_functions.h"
29#include "ext/date/php_date.h"
30
31#if HAVE_SYSEXITS_H
32#include <sysexits.h>
33#endif
34#if HAVE_SYS_SYSEXITS_H
35#include <sys/sysexits.h>
36#endif
37
38#if PHP_SIGCHILD
39#if HAVE_SIGNAL_H
40#include <signal.h>
41#endif
42#endif
43
44#include "php_syslog.h"
45#include "php_mail.h"
46#include "php_ini.h"
47#include "php_string.h"
48#include "exec.h"
49
50#ifdef PHP_WIN32
51#include "win32/sendmail.h"
52#endif
53
54#ifdef NETWARE
55#define EX_OK           0       /* successful termination */
56#define EX_TEMPFAIL     75      /* temp failure; user is invited to retry */
57#endif
58
59#define SKIP_LONG_HEADER_SEP(str, pos)                                                                  \
60    if (str[pos] == '\r' && str[pos + 1] == '\n' && (str[pos + 2] == ' ' || str[pos + 2] == '\t')) {    \
61        pos += 2;                                                                                       \
62        while (str[pos + 1] == ' ' || str[pos + 1] == '\t') {                                           \
63            pos++;                                                                                      \
64        }                                                                                               \
65        continue;                                                                                       \
66    }                                                                                                   \
67
68#define MAIL_ASCIIZ_CHECK(str, len)             \
69    p = str;                                    \
70    e = p + len;                                \
71    while ((p = memchr(p, '\0', (e - p)))) {    \
72        *p = ' ';                               \
73    }                                           \
74
75extern zend_long php_getuid(TSRMLS_D);
76
77/* {{{ proto int ezmlm_hash(string addr)
78   Calculate EZMLM list hash value. */
79PHP_FUNCTION(ezmlm_hash)
80{
81    char *str = NULL;
82    unsigned int h = 5381;
83    size_t j, str_len;
84
85    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) == FAILURE) {
86        return;
87    }
88
89    for (j = 0; j < str_len; j++) {
90        h = (h + (h << 5)) ^ (zend_ulong) (unsigned char) tolower(str[j]);
91    }
92
93    h = (h % 53);
94
95    RETURN_LONG((zend_long) h);
96}
97/* }}} */
98
99/* {{{ proto int mail(string to, string subject, string message [, string additional_headers [, string additional_parameters]])
100   Send an email message */
101PHP_FUNCTION(mail)
102{
103    char *to=NULL, *message=NULL, *headers=NULL, *headers_trimmed=NULL;
104    char *subject=NULL;
105    zend_string *extra_cmd=NULL;
106    size_t to_len, message_len, headers_len = 0;
107    size_t subject_len, i;
108    char *force_extra_parameters = INI_STR("mail.force_extra_parameters");
109    char *to_r, *subject_r;
110    char *p, *e;
111
112    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|sS",  &to, &to_len, &subject, &subject_len, &message, &message_len, &headers, &headers_len, &extra_cmd) == FAILURE) {
113        return;
114    }
115
116    /* ASCIIZ check */
117    MAIL_ASCIIZ_CHECK(to, to_len);
118    MAIL_ASCIIZ_CHECK(subject, subject_len);
119    MAIL_ASCIIZ_CHECK(message, message_len);
120    if (headers) {
121        MAIL_ASCIIZ_CHECK(headers, headers_len);
122        headers_trimmed = php_trim(headers, headers_len, NULL, 0, NULL, 2 TSRMLS_CC);
123    }
124    if (extra_cmd) {
125        MAIL_ASCIIZ_CHECK(extra_cmd->val, extra_cmd->len);
126    }
127
128    if (to_len > 0) {
129        to_r = estrndup(to, to_len);
130        for (; to_len; to_len--) {
131            if (!isspace((unsigned char) to_r[to_len - 1])) {
132                break;
133            }
134            to_r[to_len - 1] = '\0';
135        }
136        for (i = 0; to_r[i]; i++) {
137            if (iscntrl((unsigned char) to_r[i])) {
138                /* According to RFC 822, section 3.1.1 long headers may be separated into
139                 * parts using CRLF followed at least one linear-white-space character ('\t' or ' ').
140                 * To prevent these separators from being replaced with a space, we use the
141                 * SKIP_LONG_HEADER_SEP to skip over them. */
142                SKIP_LONG_HEADER_SEP(to_r, i);
143                to_r[i] = ' ';
144            }
145        }
146    } else {
147        to_r = to;
148    }
149
150    if (subject_len > 0) {
151        subject_r = estrndup(subject, subject_len);
152        for (; subject_len; subject_len--) {
153            if (!isspace((unsigned char) subject_r[subject_len - 1])) {
154                break;
155            }
156            subject_r[subject_len - 1] = '\0';
157        }
158        for (i = 0; subject_r[i]; i++) {
159            if (iscntrl((unsigned char) subject_r[i])) {
160                SKIP_LONG_HEADER_SEP(subject_r, i);
161                subject_r[i] = ' ';
162            }
163        }
164    } else {
165        subject_r = subject;
166    }
167
168    if (force_extra_parameters) {
169        extra_cmd = php_escape_shell_cmd(force_extra_parameters);
170    } else if (extra_cmd) {
171        extra_cmd = php_escape_shell_cmd(extra_cmd->val);
172    }
173
174    if (php_mail(to_r, subject_r, message, headers_trimmed, extra_cmd ? extra_cmd->val : NULL TSRMLS_CC)) {
175        RETVAL_TRUE;
176    } else {
177        RETVAL_FALSE;
178    }
179
180    if (headers_trimmed) {
181        efree(headers_trimmed);
182    }
183
184    if (extra_cmd) {
185        zend_string_release(extra_cmd);
186    }
187    if (to_r != to) {
188        efree(to_r);
189    }
190    if (subject_r != subject) {
191        efree(subject_r);
192    }
193}
194/* }}} */
195
196
197void php_mail_log_crlf_to_spaces(char *message) {
198    /* Find all instances of carriage returns or line feeds and
199     * replace them with spaces. Thus, a log line is always one line
200     * long
201     */
202    char *p = message;
203    while ((p = strpbrk(p, "\r\n"))) {
204        *p = ' ';
205    }
206}
207
208void php_mail_log_to_syslog(char *message) {
209    /* Write 'message' to syslog. */
210#ifdef HAVE_SYSLOG_H
211    php_syslog(LOG_NOTICE, "%s", message);
212#endif
213}
214
215
216void php_mail_log_to_file(char *filename, char *message, size_t message_size TSRMLS_DC) {
217    /* Write 'message' to the given file. */
218    uint flags = IGNORE_URL_WIN | REPORT_ERRORS | STREAM_DISABLE_OPEN_BASEDIR;
219    php_stream *stream = php_stream_open_wrapper(filename, "a", flags, NULL);
220    if (stream) {
221        php_stream_write(stream, message, message_size);
222        php_stream_close(stream);
223    }
224}
225
226
227/* {{{ php_mail
228 */
229PHPAPI int php_mail(char *to, char *subject, char *message, char *headers, char *extra_cmd TSRMLS_DC)
230{
231#if (defined PHP_WIN32 || defined NETWARE)
232    int tsm_err;
233    char *tsm_errmsg = NULL;
234#endif
235    FILE *sendmail;
236    int ret;
237    char *sendmail_path = INI_STR("sendmail_path");
238    char *sendmail_cmd = NULL;
239    char *mail_log = INI_STR("mail.log");
240    char *hdr = headers;
241#if PHP_SIGCHILD
242    void (*sig_handler)() = NULL;
243#endif
244
245#define MAIL_RET(val) \
246    if (hdr != headers) {   \
247        efree(hdr); \
248    }   \
249    return val; \
250
251    if (mail_log && *mail_log) {
252        char *tmp;
253        time_t curtime;
254        int l;
255        zend_string *date_str;
256
257        time(&curtime);
258        date_str = php_format_date("d-M-Y H:i:s e", 13, curtime, 1 TSRMLS_CC);
259
260        l = spprintf(&tmp, 0, "[%s] mail() on [%s:%d]: To: %s -- Headers: %s\n", date_str->val, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C), to, hdr ? hdr : "");
261
262        zend_string_free(date_str);
263
264        if (hdr) {
265            php_mail_log_crlf_to_spaces(tmp);
266        }
267
268        if (!strcmp(mail_log, "syslog")) {
269            /* Drop the final space when logging to syslog. */
270            tmp[l - 1] = 0;
271            php_mail_log_to_syslog(tmp);
272        }
273        else {
274            /* Convert the final space to a newline when logging to file. */
275            tmp[l - 1] = '\n';
276            php_mail_log_to_file(mail_log, tmp, l TSRMLS_CC);
277        }
278
279        efree(tmp);
280    }
281    if (PG(mail_x_header)) {
282        const char *tmp = zend_get_executed_filename(TSRMLS_C);
283        zend_string *f;
284
285        f = php_basename(tmp, strlen(tmp), NULL, 0 TSRMLS_CC);
286
287        if (headers != NULL) {
288            spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s\n%s", php_getuid(TSRMLS_C), f->val, headers);
289        } else {
290            spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s", php_getuid(TSRMLS_C), f->val);
291        }
292        zend_string_release(f);
293    }
294
295    if (!sendmail_path) {
296#if (defined PHP_WIN32 || defined NETWARE)
297        /* handle old style win smtp sending */
298        if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL, NULL TSRMLS_CC) == FAILURE) {
299            if (tsm_errmsg) {
300                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tsm_errmsg);
301                efree(tsm_errmsg);
302            } else {
303                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", GetSMErrorText(tsm_err));
304            }
305            MAIL_RET(0);
306        }
307        MAIL_RET(1);
308#else
309        MAIL_RET(0);
310#endif
311    }
312    if (extra_cmd != NULL) {
313        spprintf(&sendmail_cmd, 0, "%s %s", sendmail_path, extra_cmd);
314    } else {
315        sendmail_cmd = sendmail_path;
316    }
317
318#if PHP_SIGCHILD
319    /* Set signal handler of SIGCHLD to default to prevent other signal handlers
320     * from being called and reaping the return code when our child exits.
321     * The original handler needs to be restored after pclose() */
322    sig_handler = (void *)signal(SIGCHLD, SIG_DFL);
323    if (sig_handler == SIG_ERR) {
324        sig_handler = NULL;
325    }
326#endif
327
328#ifdef PHP_WIN32
329    sendmail = popen_ex(sendmail_cmd, "wb", NULL, NULL TSRMLS_CC);
330#else
331    /* Since popen() doesn't indicate if the internal fork() doesn't work
332     * (e.g. the shell can't be executed) we explicitly set it to 0 to be
333     * sure we don't catch any older errno value. */
334    errno = 0;
335    sendmail = popen(sendmail_cmd, "w");
336#endif
337    if (extra_cmd != NULL) {
338        efree (sendmail_cmd);
339    }
340
341    if (sendmail) {
342#ifndef PHP_WIN32
343        if (EACCES == errno) {
344            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Permission denied: unable to execute shell to run mail delivery binary '%s'", sendmail_path);
345            pclose(sendmail);
346#if PHP_SIGCHILD
347            /* Restore handler in case of error on Windows
348               Not sure if this applicable on Win but just in case. */
349            if (sig_handler) {
350                signal(SIGCHLD, sig_handler);
351            }
352#endif
353            MAIL_RET(0);
354        }
355#endif
356        fprintf(sendmail, "To: %s\n", to);
357        fprintf(sendmail, "Subject: %s\n", subject);
358        if (hdr != NULL) {
359            fprintf(sendmail, "%s\n", hdr);
360        }
361        fprintf(sendmail, "\n%s\n", message);
362        ret = pclose(sendmail);
363
364#if PHP_SIGCHILD
365        if (sig_handler) {
366            signal(SIGCHLD, sig_handler);
367        }
368#endif
369
370#ifdef PHP_WIN32
371        if (ret == -1)
372#else
373#if defined(EX_TEMPFAIL)
374        if ((ret != EX_OK)&&(ret != EX_TEMPFAIL))
375#elif defined(EX_OK)
376        if (ret != EX_OK)
377#else
378        if (ret != 0)
379#endif
380#endif
381        {
382            MAIL_RET(0);
383        } else {
384            MAIL_RET(1);
385        }
386    } else {
387        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not execute mail delivery program '%s'", sendmail_path);
388#if PHP_SIGCHILD
389        if (sig_handler) {
390            signal(SIGCHLD, sig_handler);
391        }
392#endif
393        MAIL_RET(0);
394    }
395
396    MAIL_RET(1); /* never reached */
397}
398/* }}} */
399
400/* {{{ PHP_MINFO_FUNCTION
401 */
402PHP_MINFO_FUNCTION(mail)
403{
404    char *sendmail_path = INI_STR("sendmail_path");
405
406#ifdef PHP_WIN32
407    if (!sendmail_path) {
408        php_info_print_table_row(2, "Internal Sendmail Support for Windows", "enabled");
409    } else {
410        php_info_print_table_row(2, "Path to sendmail", sendmail_path);
411    }
412#else
413    php_info_print_table_row(2, "Path to sendmail", sendmail_path);
414#endif
415}
416/* }}} */
417
418/*
419 * Local variables:
420 * tab-width: 4
421 * c-basic-offset: 4
422 * End:
423 * vim600: sw=4 ts=4 fdm=marker
424 * vim<600: sw=4 ts=4
425 */
426