From c.heiss at proxmox.com Mon Sep 1 14:12:27 2025 From: c.heiss at proxmox.com (Christoph Heiss) Date: Mon, 1 Sep 2025 14:12:27 +0200 Subject: [pmg-devel] [PATCH pmg-api] dkim: local mail: fix `Date` header formatting under different locales Message-ID: <20250901121229.833136-1-c.heiss@proxmox.com> This was reported in the community forum to cause problems if the (shell) locale was set to something different than 'C' or 'en_US.UTF-8' [0]. Tl;dr: A different locale would cause the `Date` header to be formatted according to that locale, instead of following the RFC2822 format. E.g. under the locale `de_DE.UTF-8`, the header would look like this: Date: Mo, 25 Aug 2025 00:01:04 +0200 instead of the correct format of: Date: Mon, 25 Aug 2025 00:01:04 +0200 `perldoc POSIX` documents the following under strftime: > For example, the specifiers `aAbBcpZ` change according to the locale > settings of the user, [..] Tested this by running the following, before and after: export LC_TIME=de_DE.UTF-8 unset LC_ALL # ensure LC_TIME is used pmgreport .. and verifying that the date header is now correct. [0] https://forum.proxmox.com/threads/datumsprobleme-mit-den-pmg-reports.170245 Fixes: 77cfddd ("dkim: local mail: add Date header") Signed-off-by: Christoph Heiss --- src/PMG/RuleDB/Notify.pm | 3 +-- src/PMG/SMTP.pm | 3 +-- src/PMG/Utils.pm | 28 ++++++++++++++++++++-- src/tests/Makefile | 6 ++++- src/tests/test_utils.pl | 52 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 7 deletions(-) create mode 100755 src/tests/test_utils.pl diff --git a/src/PMG/RuleDB/Notify.pm b/src/PMG/RuleDB/Notify.pm index caee64b..6adedb2 100644 --- a/src/PMG/RuleDB/Notify.pm +++ b/src/PMG/RuleDB/Notify.pm @@ -8,7 +8,6 @@ use MIME::Head; use MIME::Entity; use MIME::Words qw(encode_mimewords); use Encode qw(decode encode); -use POSIX qw(strftime); use PVE::SafeSyslog; @@ -249,7 +248,7 @@ sub execute { From => $from_header, To => $to, Subject => encode_mimewords(encode('UTF-8', $subject), "Charset" => "UTF-8"), - Date => strftime("%a, %d %b %Y %T %z", localtime()), + Date => PMG::Utils::format_date_header(localtime()), Data => encode('UTF-8', $body), ); diff --git a/src/PMG/SMTP.pm b/src/PMG/SMTP.pm index 58c952d..dcb2795 100644 --- a/src/PMG/SMTP.pm +++ b/src/PMG/SMTP.pm @@ -5,7 +5,6 @@ use warnings; use IO::Socket; use Encode; use MIME::Entity; -use POSIX qw(strftime); use PVE::SafeSyslog; @@ -295,7 +294,7 @@ EOF Type => 'multipart/report; report-type=delivery-status;', To => $sender, From => $from_header, - Date => strftime("%a, %d %b %Y %T %z", localtime()), + Date => PMG::Utils::format_date_header(localtime()), Subject => 'Undelivered Mail', ); diff --git a/src/PMG/Utils.pm b/src/PMG/Utils.pm index 890096f..0e47ad2 100644 --- a/src/PMG/Utils.pm +++ b/src/PMG/Utils.pm @@ -26,7 +26,7 @@ use MIME::Words; use Net::Cmd; use Net::IP; use Net::SMTP; -use POSIX qw(strftime); +use POSIX qw(strftime setlocale); use RRDs; use Socket; use Time::HiRes qw (gettimeofday); @@ -1360,7 +1360,7 @@ sub finalize_report { Type => ($html && $plaintext) ? 'multipart/alternative' : 'multipart/related', To => $data->{pmail_raw}, From => $mailfrom, - Date => strftime("%a, %d %b %Y %T %z", localtime()), + Date => format_date_header(localtime()), Subject => bencode_header(decode_entities($title)), ); @@ -1726,4 +1726,28 @@ sub test_regex { return undef; } +=head3 format_date_header + +Returns a RFC2822-formatted timestamp for usage in the Date header. + +Takes the same parameters as C, i.e. C<$sec>, +C<$min>, C<$hour>, C<$mday>, C<$mon>, C<$year>, C<$wday>, C<$yday>, C<$isdst> +in that exact order - as e.g. returned by C. + +For example: + + use PMG::Utils; + my $date = PMG::Utils::format_date_header(localtime()); + +=cut + +sub format_date_header { + # ensure that we always use the right locale for formatting `Date` headers + my $old_locale = setlocale(POSIX::LC_TIME, 'C') // 'C'; + my $date = strftime('%a, %d %b %Y %T %z', @_); + setlocale(POSIX::LC_TIME, 'C'); + + return $date; +} + 1; diff --git a/src/tests/Makefile b/src/tests/Makefile index dc35796..68f77e4 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -4,7 +4,8 @@ export PERLLIB = .. all: -check: +.PHONY: check +check: test-utils ./create_testdb.pl ./init_testdb.pl ./print_testdb.pl > testdb.txt.new @@ -17,6 +18,9 @@ check: # test_proxy.pl \ # test_unpack.pl +.PHONY: test-utils +test-utils: + ./test_utils.pl clean: rm -rf *~ proxytest_report.out test.cfg testdb.txt.new diff --git a/src/tests/test_utils.pl b/src/tests/test_utils.pl new file mode 100755 index 0000000..8abcc8b --- /dev/null +++ b/src/tests/test_utils.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use POSIX qw(setlocale strftime); + +use PMG::Utils; + +subtest 'format_date_header works' => sub { + cmp_ok(length(PMG::Utils::format_date_header(localtime())), '>=', 30); + is( + PMG::Utils::format_date_header(47, 55, 12, 1, 8, 125, 1, 243, 1), + 'Mon, 01 Sep 2025 12:55:47 +0200', + ); + is( + PMG::Utils::format_date_header(59, 2, 8, 2, 0, 125, 4, 2, 0), + 'Thu, 02 Jan 2025 08:02:59 +0100', + ); +}; + +subtest 'format_date_header works with other locales' => sub { + # also check correctness under some other locale + my $old_locale = setlocale(POSIX::LC_TIME); + + if (!defined(setlocale(POSIX::LC_TIME, "de_DE.UTF-8"))) { + # if the locale is not available, setlocale() returns undef + # in that case, the tests below do not make sense + plan(skip_all => "due to 'de_DE.UTF-8' locale not available"); + } + + # first check if the other locale indeed produces another format + is( + strftime('%a, %d %b %Y %T %z', 47, 55, 12, 1, 8, 125, 1, 243, 1), + 'Mo, 01 Sep 2025 12:55:47 +0200', + ); + + cmp_ok(length(PMG::Utils::format_date_header(localtime())), '>=', 30); + is( + PMG::Utils::format_date_header(47, 55, 12, 1, 8, 125, 1, 243, 1), + 'Mon, 01 Sep 2025 12:55:47 +0200', + ); + is( + PMG::Utils::format_date_header(59, 2, 8, 2, 0, 125, 4, 2, 0), + 'Thu, 02 Jan 2025 08:02:59 +0100', + ); + + setlocale(POSIX::LC_TIME, $old_locale); +}; + +done_testing(); -- 2.50.1