1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 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
75 extern zend_long php_getuid(void);
76
77 /* {{{ proto int ezmlm_hash(string addr)
78 Calculate EZMLM list hash value. */
PHP_FUNCTION(ezmlm_hash)79 PHP_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(), "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 */
PHP_FUNCTION(mail)101 PHP_FUNCTION(mail)
102 {
103 char *to=NULL, *message=NULL;
104 char *subject=NULL;
105 zend_string *extra_cmd=NULL, *headers=NULL, *headers_trimmed=NULL;
106 size_t to_len, message_len;
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(), "sss|SS", &to, &to_len, &subject, &subject_len, &message, &message_len, &headers, &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(ZSTR_VAL(headers), ZSTR_LEN(headers));
122 headers_trimmed = php_trim(headers, NULL, 0, 2);
123 }
124 if (extra_cmd) {
125 MAIL_ASCIIZ_CHECK(ZSTR_VAL(extra_cmd), ZSTR_LEN(extra_cmd));
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(ZSTR_VAL(extra_cmd));
172 }
173
174 if (php_mail(to_r, subject_r, message, headers_trimmed ? ZSTR_VAL(headers_trimmed) : NULL, extra_cmd ? ZSTR_VAL(extra_cmd) : NULL)) {
175 RETVAL_TRUE;
176 } else {
177 RETVAL_FALSE;
178 }
179
180 if (headers_trimmed) {
181 zend_string_release(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
php_mail_log_crlf_to_spaces(char * message)197 void 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
php_mail_log_to_syslog(char * message)208 void 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
php_mail_log_to_file(char * filename,char * message,size_t message_size)216 void php_mail_log_to_file(char *filename, char *message, size_t message_size) {
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
php_mail_detect_multiple_crlf(char * hdr)227 static int php_mail_detect_multiple_crlf(char *hdr) {
228 /* This function detects multiple/malformed multiple newlines. */
229
230 if (!hdr || !strlen(hdr)) {
231 return 0;
232 }
233
234 /* Should not have any newlines at the beginning. */
235 /* RFC 2822 2.2. Header Fields */
236 if (*hdr < 33 || *hdr > 126 || *hdr == ':') {
237 return 1;
238 }
239
240 while(*hdr) {
241 if (*hdr == '\r') {
242 if (*(hdr+1) == '\0' || *(hdr+1) == '\r' || (*(hdr+1) == '\n' && (*(hdr+2) == '\0' || *(hdr+2) == '\n' || *(hdr+2) == '\r'))) {
243 /* Malformed or multiple newlines. */
244 return 1;
245 } else {
246 hdr += 2;
247 }
248 } else if (*hdr == '\n') {
249 if (*(hdr+1) == '\0' || *(hdr+1) == '\r' || *(hdr+1) == '\n') {
250 /* Malformed or multiple newlines. */
251 return 1;
252 } else {
253 hdr += 2;
254 }
255 } else {
256 hdr++;
257 }
258 }
259
260 return 0;
261 }
262
263
264 /* {{{ php_mail
265 */
php_mail(char * to,char * subject,char * message,char * headers,char * extra_cmd)266 PHPAPI int php_mail(char *to, char *subject, char *message, char *headers, char *extra_cmd)
267 {
268 #if (defined PHP_WIN32 || defined NETWARE)
269 int tsm_err;
270 char *tsm_errmsg = NULL;
271 #endif
272 FILE *sendmail;
273 int ret;
274 char *sendmail_path = INI_STR("sendmail_path");
275 char *sendmail_cmd = NULL;
276 char *mail_log = INI_STR("mail.log");
277 char *hdr = headers;
278 #if PHP_SIGCHILD
279 void (*sig_handler)() = NULL;
280 #endif
281
282 #define MAIL_RET(val) \
283 if (hdr != headers) { \
284 efree(hdr); \
285 } \
286 return val; \
287
288 if (mail_log && *mail_log) {
289 char *tmp;
290 time_t curtime;
291 size_t l;
292 zend_string *date_str;
293
294 time(&curtime);
295 date_str = php_format_date("d-M-Y H:i:s e", 13, curtime, 1);
296
297 l = spprintf(&tmp, 0, "[%s] mail() on [%s:%d]: To: %s -- Headers: %s\n", ZSTR_VAL(date_str), zend_get_executed_filename(), zend_get_executed_lineno(), to, hdr ? hdr : "");
298
299 zend_string_free(date_str);
300
301 if (hdr) {
302 php_mail_log_crlf_to_spaces(tmp);
303 }
304
305 if (!strcmp(mail_log, "syslog")) {
306 /* Drop the final space when logging to syslog. */
307 tmp[l - 1] = 0;
308 php_mail_log_to_syslog(tmp);
309 }
310 else {
311 /* Convert the final space to a newline when logging to file. */
312 tmp[l - 1] = '\n';
313 php_mail_log_to_file(mail_log, tmp, l);
314 }
315
316 efree(tmp);
317 }
318
319 if (PG(mail_x_header)) {
320 const char *tmp = zend_get_executed_filename();
321 zend_string *f;
322
323 f = php_basename(tmp, strlen(tmp), NULL, 0);
324
325 if (headers != NULL && *headers) {
326 spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s\n%s", php_getuid(), ZSTR_VAL(f), headers);
327 } else {
328 spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s", php_getuid(), ZSTR_VAL(f));
329 }
330 zend_string_release(f);
331 }
332
333 if (hdr && php_mail_detect_multiple_crlf(hdr)) {
334 php_error_docref(NULL, E_WARNING, "Multiple or malformed newlines found in additional_header");
335 MAIL_RET(0);
336 }
337
338 if (!sendmail_path) {
339 #if (defined PHP_WIN32 || defined NETWARE)
340 /* handle old style win smtp sending */
341 if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL, NULL) == FAILURE) {
342 if (tsm_errmsg) {
343 php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg);
344 efree(tsm_errmsg);
345 } else {
346 php_error_docref(NULL, E_WARNING, "%s", GetSMErrorText(tsm_err));
347 }
348 MAIL_RET(0);
349 }
350 MAIL_RET(1);
351 #else
352 MAIL_RET(0);
353 #endif
354 }
355 if (extra_cmd != NULL) {
356 spprintf(&sendmail_cmd, 0, "%s %s", sendmail_path, extra_cmd);
357 } else {
358 sendmail_cmd = sendmail_path;
359 }
360
361 #if PHP_SIGCHILD
362 /* Set signal handler of SIGCHLD to default to prevent other signal handlers
363 * from being called and reaping the return code when our child exits.
364 * The original handler needs to be restored after pclose() */
365 sig_handler = (void *)signal(SIGCHLD, SIG_DFL);
366 if (sig_handler == SIG_ERR) {
367 sig_handler = NULL;
368 }
369 #endif
370
371 #ifdef PHP_WIN32
372 sendmail = popen_ex(sendmail_cmd, "wb", NULL, NULL);
373 #else
374 /* Since popen() doesn't indicate if the internal fork() doesn't work
375 * (e.g. the shell can't be executed) we explicitly set it to 0 to be
376 * sure we don't catch any older errno value. */
377 errno = 0;
378 sendmail = popen(sendmail_cmd, "w");
379 #endif
380 if (extra_cmd != NULL) {
381 efree (sendmail_cmd);
382 }
383
384 if (sendmail) {
385 #ifndef PHP_WIN32
386 if (EACCES == errno) {
387 php_error_docref(NULL, E_WARNING, "Permission denied: unable to execute shell to run mail delivery binary '%s'", sendmail_path);
388 pclose(sendmail);
389 #if PHP_SIGCHILD
390 /* Restore handler in case of error on Windows
391 Not sure if this applicable on Win but just in case. */
392 if (sig_handler) {
393 signal(SIGCHLD, sig_handler);
394 }
395 #endif
396 MAIL_RET(0);
397 }
398 #endif
399 fprintf(sendmail, "To: %s\n", to);
400 fprintf(sendmail, "Subject: %s\n", subject);
401 if (hdr != NULL) {
402 fprintf(sendmail, "%s\n", hdr);
403 }
404 fprintf(sendmail, "\n%s\n", message);
405 ret = pclose(sendmail);
406
407 #if PHP_SIGCHILD
408 if (sig_handler) {
409 signal(SIGCHLD, sig_handler);
410 }
411 #endif
412
413 #ifdef PHP_WIN32
414 if (ret == -1)
415 #else
416 #if defined(EX_TEMPFAIL)
417 if ((ret != EX_OK)&&(ret != EX_TEMPFAIL))
418 #elif defined(EX_OK)
419 if (ret != EX_OK)
420 #else
421 if (ret != 0)
422 #endif
423 #endif
424 {
425 MAIL_RET(0);
426 } else {
427 MAIL_RET(1);
428 }
429 } else {
430 php_error_docref(NULL, E_WARNING, "Could not execute mail delivery program '%s'", sendmail_path);
431 #if PHP_SIGCHILD
432 if (sig_handler) {
433 signal(SIGCHLD, sig_handler);
434 }
435 #endif
436 MAIL_RET(0);
437 }
438
439 MAIL_RET(1); /* never reached */
440 }
441 /* }}} */
442
443 /* {{{ PHP_MINFO_FUNCTION
444 */
PHP_MINFO_FUNCTION(mail)445 PHP_MINFO_FUNCTION(mail)
446 {
447 char *sendmail_path = INI_STR("sendmail_path");
448
449 #ifdef PHP_WIN32
450 if (!sendmail_path) {
451 php_info_print_table_row(2, "Internal Sendmail Support for Windows", "enabled");
452 } else {
453 php_info_print_table_row(2, "Path to sendmail", sendmail_path);
454 }
455 #else
456 php_info_print_table_row(2, "Path to sendmail", sendmail_path);
457 #endif
458 }
459 /* }}} */
460
461 /*
462 * Local variables:
463 * tab-width: 4
464 * c-basic-offset: 4
465 * End:
466 * vim600: sw=4 ts=4 fdm=marker
467 * vim<600: sw=4 ts=4
468 */
469