[pmg-devel] [PATCH pmg-api] dkim: local mail: fix `Date` header formatting under different locales

Christoph Heiss c.heiss at proxmox.com
Mon Sep 1 14:12:27 CEST 2025


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 <c.heiss at proxmox.com>
---
 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<POSIX::strftime(fmt, ..)>, 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<localtime()>.
+
+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





More information about the pmg-devel mailing list