From d3175a7908429a01392fecf656439e920451dc39 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 30 Dec 2022 15:09:48 +0000 Subject: [PATCH] Fix GH-8086: Introduce mail.mixed_lf_and_crlf INI When this INI option is enabled, it reverts the line separator for headers and message to LF which was a non conformant behavior in PHP 7. It is done because some non conformant MTAs fail to parse CRLF line separator for headers and body. This is used for mail and mb_send_mail functions. --- ext/mbstring/mbstring.c | 16 ++++++++------ ext/mbstring/tests/gh8086.phpt | 33 +++++++++++++++++++++++++++++ ext/standard/mail.c | 12 ++++++----- ext/standard/tests/mail/gh8086.phpt | 18 ++++++++++++++++ main/main.c | 1 + main/php_globals.h | 1 + php.ini-development | 4 ++++ php.ini-production | 4 ++++ 8 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 ext/mbstring/tests/gh8086.phpt create mode 100644 ext/standard/tests/mail/gh8086.phpt diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 34678ea3d0f7f..7bcb771dae84b 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -4128,7 +4128,9 @@ PHP_FUNCTION(mb_send_mail) || orig_str.encoding->no_encoding == mbfl_no_encoding_pass) { orig_str.encoding = mbfl_identify_encoding(&orig_str, MBSTRG(current_detect_order_list), MBSTRG(current_detect_order_list_size), MBSTRG(strict_detection)); } - pstr = mbfl_mime_header_encode(&orig_str, &conv_str, tran_cs, head_enc, CRLF, sizeof("Subject: [PHP-jp nnnnnnnn]" CRLF) - 1); + const char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : CRLF; + size_t line_sep_len = strlen(line_sep); + pstr = mbfl_mime_header_encode(&orig_str, &conv_str, tran_cs, head_enc, line_sep, strlen("Subject: [PHP-jp nnnnnnnn]") + line_sep_len); if (pstr != NULL) { subject_buf = subject = (char *)pstr->val; } @@ -4167,14 +4169,14 @@ PHP_FUNCTION(mb_send_mail) n = ZSTR_LEN(str_headers); mbfl_memory_device_strncat(&device, p, n); if (n > 0 && p[n - 1] != '\n') { - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } zend_string_release_ex(str_headers, 0); } if (!zend_hash_str_exists(&ht_headers, "mime-version", sizeof("mime-version") - 1)) { mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER1, sizeof(PHP_MBSTR_MAIL_MIME_HEADER1) - 1); - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } if (!suppressed_hdrs.cnt_type) { @@ -4185,7 +4187,7 @@ PHP_FUNCTION(mb_send_mail) mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER3, sizeof(PHP_MBSTR_MAIL_MIME_HEADER3) - 1); mbfl_memory_device_strcat(&device, p); } - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } if (!suppressed_hdrs.cnt_trans_enc) { mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER4, sizeof(PHP_MBSTR_MAIL_MIME_HEADER4) - 1); @@ -4194,10 +4196,12 @@ PHP_FUNCTION(mb_send_mail) p = "7bit"; } mbfl_memory_device_strcat(&device, p); - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } - mbfl_memory_device_unput(&device); + if (!PG(mail_mixed_lf_and_crlf)) { + mbfl_memory_device_unput(&device); + } mbfl_memory_device_unput(&device); mbfl_memory_device_output('\0', &device); str_headers = zend_string_init((char *)device.buffer, strlen((char *)device.buffer), 0); diff --git a/ext/mbstring/tests/gh8086.phpt b/ext/mbstring/tests/gh8086.phpt new file mode 100644 index 0000000000000..2eeed67e5b483 --- /dev/null +++ b/ext/mbstring/tests/gh8086.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-8086 (mb_send_mail() function not working correctly in PHP 8.x) +--SKIPIF-- + +--INI-- +sendmail_path={MAIL:{PWD}/gh8086.eml} +mail.mixed_lf_and_crlf=on +--FILE-- + +--CLEAN-- + +--EXPECT-- +int(6) diff --git a/ext/standard/mail.c b/ext/standard/mail.c index 55790e6100fce..ef4c8c60a874e 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -429,6 +429,8 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co MAIL_RET(0); } + char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n"; + if (PG(mail_x_header)) { const char *tmp = zend_get_executed_filename(); zend_string *f; @@ -436,7 +438,7 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co f = php_basename(tmp, strlen(tmp), NULL, 0); if (headers != NULL && *headers) { - spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s\r\n%s", php_getuid(), ZSTR_VAL(f), headers); + spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s%s%s", php_getuid(), ZSTR_VAL(f), line_sep, headers); } else { spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s", php_getuid(), ZSTR_VAL(f)); } @@ -510,12 +512,12 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co MAIL_RET(0); } #endif - fprintf(sendmail, "To: %s\r\n", to); - fprintf(sendmail, "Subject: %s\r\n", subject); + fprintf(sendmail, "To: %s%s", to, line_sep); + fprintf(sendmail, "Subject: %s%s", subject, line_sep); if (hdr != NULL) { - fprintf(sendmail, "%s\r\n", hdr); + fprintf(sendmail, "%s%s", hdr, line_sep); } - fprintf(sendmail, "\r\n%s\r\n", message); + fprintf(sendmail, "%s%s%s", line_sep, message, line_sep); ret = pclose(sendmail); #if PHP_SIGCHILD diff --git a/ext/standard/tests/mail/gh8086.phpt b/ext/standard/tests/mail/gh8086.phpt new file mode 100644 index 0000000000000..fba5cc56bb450 --- /dev/null +++ b/ext/standard/tests/mail/gh8086.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-8086 (Mail() function not working correctly in PHP 8.x) +--INI-- +sendmail_path={MAIL:gh8086.out} +mail.mixed_lf_and_crlf=on +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +int(5) diff --git a/main/main.c b/main/main.c index 8be52d316a159..6b8a9e6bd1990 100644 --- a/main/main.c +++ b/main/main.c @@ -737,6 +737,7 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("SMTP", "localhost",PHP_INI_ALL, NULL) PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL) STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals) + STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_mixed_lf_and_crlf, php_core_globals, core_globals) STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals) PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap) PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit) diff --git a/main/php_globals.h b/main/php_globals.h index cbf0271c7b763..0e004bd011181 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -153,6 +153,7 @@ struct _php_core_globals { char *request_order; bool mail_x_header; + bool mail_mixed_lf_and_crlf; char *mail_log; bool in_error_log; diff --git a/php.ini-development b/php.ini-development index 70c6b00555844..ba75e4933ba7c 100644 --- a/php.ini-development +++ b/php.ini-development @@ -1095,6 +1095,10 @@ smtp_port = 25 ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename mail.add_x_header = Off +; Use mixed LF and CRLF line separators to keep compatibility with some +; RFC 2822 non conformant MTA. +mail.mixed_lf_and_crlf = Off + ; The path to a log file that will log all mail() calls. Log entries include ; the full path of the script, line number, To address and headers. ;mail.log = diff --git a/php.ini-production b/php.ini-production index 21627c9142487..9f9ff22586d6d 100644 --- a/php.ini-production +++ b/php.ini-production @@ -1097,6 +1097,10 @@ smtp_port = 25 ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename mail.add_x_header = Off +; Use mixed LF and CRLF line separators to keep compatibility with some +; RFC 2822 non conformant MTA. +mail.mixed_lf_and_crlf = Off + ; The path to a log file that will log all mail() calls. Log entries include ; the full path of the script, line number, To address and headers. ;mail.log =