1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 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(void);
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(), "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;
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
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) {
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
227static 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 */
266PHPAPI 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 */
445PHP_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