xref: /PHP-7.0/ext/standard/mail.c (revision 478f119a)
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