1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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 *logline;
290
291 spprintf(&logline, 0, "mail() on [%s:%d]: To: %s -- Headers: %s -- Subject: %s", zend_get_executed_filename(), zend_get_executed_lineno(), to, hdr ? hdr : "", subject);
292
293 if (hdr) {
294 php_mail_log_crlf_to_spaces(logline);
295 }
296
297 if (!strcmp(mail_log, "syslog")) {
298 php_mail_log_to_syslog(logline);
299 } else {
300 /* Add date when logging to file */
301 char *tmp;
302 time_t curtime;
303 zend_string *date_str;
304 size_t len;
305
306
307 time(&curtime);
308 date_str = php_format_date("d-M-Y H:i:s e", 13, curtime, 1);
309 len = spprintf(&tmp, 0, "[%s] %s%s", date_str->val, logline, PHP_EOL);
310
311 php_mail_log_to_file(mail_log, tmp, len);
312
313 zend_string_free(date_str);
314 efree(tmp);
315 }
316
317 efree(logline);
318 }
319
320 if (PG(mail_x_header)) {
321 const char *tmp = zend_get_executed_filename();
322 zend_string *f;
323
324 f = php_basename(tmp, strlen(tmp), NULL, 0);
325
326 if (headers != NULL && *headers) {
327 spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s\n%s", php_getuid(), ZSTR_VAL(f), headers);
328 } else {
329 spprintf(&hdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s", php_getuid(), ZSTR_VAL(f));
330 }
331 zend_string_release(f);
332 }
333
334 if (hdr && php_mail_detect_multiple_crlf(hdr)) {
335 php_error_docref(NULL, E_WARNING, "Multiple or malformed newlines found in additional_header");
336 MAIL_RET(0);
337 }
338
339 if (!sendmail_path) {
340 #if (defined PHP_WIN32 || defined NETWARE)
341 /* handle old style win smtp sending */
342 if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL, NULL) == FAILURE) {
343 if (tsm_errmsg) {
344 php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg);
345 efree(tsm_errmsg);
346 } else {
347 php_error_docref(NULL, E_WARNING, "%s", GetSMErrorText(tsm_err));
348 }
349 MAIL_RET(0);
350 }
351 MAIL_RET(1);
352 #else
353 MAIL_RET(0);
354 #endif
355 }
356 if (extra_cmd != NULL) {
357 spprintf(&sendmail_cmd, 0, "%s %s", sendmail_path, extra_cmd);
358 } else {
359 sendmail_cmd = sendmail_path;
360 }
361
362 #if PHP_SIGCHILD
363 /* Set signal handler of SIGCHLD to default to prevent other signal handlers
364 * from being called and reaping the return code when our child exits.
365 * The original handler needs to be restored after pclose() */
366 sig_handler = (void *)signal(SIGCHLD, SIG_DFL);
367 if (sig_handler == SIG_ERR) {
368 sig_handler = NULL;
369 }
370 #endif
371
372 #ifdef PHP_WIN32
373 sendmail = popen_ex(sendmail_cmd, "wb", NULL, NULL);
374 #else
375 /* Since popen() doesn't indicate if the internal fork() doesn't work
376 * (e.g. the shell can't be executed) we explicitly set it to 0 to be
377 * sure we don't catch any older errno value. */
378 errno = 0;
379 sendmail = popen(sendmail_cmd, "w");
380 #endif
381 if (extra_cmd != NULL) {
382 efree (sendmail_cmd);
383 }
384
385 if (sendmail) {
386 #ifndef PHP_WIN32
387 if (EACCES == errno) {
388 php_error_docref(NULL, E_WARNING, "Permission denied: unable to execute shell to run mail delivery binary '%s'", sendmail_path);
389 pclose(sendmail);
390 #if PHP_SIGCHILD
391 /* Restore handler in case of error on Windows
392 Not sure if this applicable on Win but just in case. */
393 if (sig_handler) {
394 signal(SIGCHLD, sig_handler);
395 }
396 #endif
397 MAIL_RET(0);
398 }
399 #endif
400 fprintf(sendmail, "To: %s\n", to);
401 fprintf(sendmail, "Subject: %s\n", subject);
402 if (hdr != NULL) {
403 fprintf(sendmail, "%s\n", hdr);
404 }
405 fprintf(sendmail, "\n%s\n", message);
406 ret = pclose(sendmail);
407
408 #if PHP_SIGCHILD
409 if (sig_handler) {
410 signal(SIGCHLD, sig_handler);
411 }
412 #endif
413
414 #ifdef PHP_WIN32
415 if (ret == -1)
416 #else
417 #if defined(EX_TEMPFAIL)
418 if ((ret != EX_OK)&&(ret != EX_TEMPFAIL))
419 #elif defined(EX_OK)
420 if (ret != EX_OK)
421 #else
422 if (ret != 0)
423 #endif
424 #endif
425 {
426 MAIL_RET(0);
427 } else {
428 MAIL_RET(1);
429 }
430 } else {
431 php_error_docref(NULL, E_WARNING, "Could not execute mail delivery program '%s'", sendmail_path);
432 #if PHP_SIGCHILD
433 if (sig_handler) {
434 signal(SIGCHLD, sig_handler);
435 }
436 #endif
437 MAIL_RET(0);
438 }
439
440 MAIL_RET(1); /* never reached */
441 }
442 /* }}} */
443
444 /* {{{ PHP_MINFO_FUNCTION
445 */
PHP_MINFO_FUNCTION(mail)446 PHP_MINFO_FUNCTION(mail)
447 {
448 char *sendmail_path = INI_STR("sendmail_path");
449
450 #ifdef PHP_WIN32
451 if (!sendmail_path) {
452 php_info_print_table_row(2, "Internal Sendmail Support for Windows", "enabled");
453 } else {
454 php_info_print_table_row(2, "Path to sendmail", sendmail_path);
455 }
456 #else
457 php_info_print_table_row(2, "Path to sendmail", sendmail_path);
458 #endif
459 }
460 /* }}} */
461
462 /*
463 * Local variables:
464 * tab-width: 4
465 * c-basic-offset: 4
466 * End:
467 * vim600: sw=4 ts=4 fdm=marker
468 * vim<600: sw=4 ts=4
469 */
470