Subject: | Broken timezone specification generated by Bulkmail.pm |
The Tz() method in Mail::Bulkmail occasionally generates invalid
timezone offsets (outside the range -1200 to +1200). This post
to the freebsd-ports mailing list by David Wolfskill from Nov 28,
2001 details the issues (both URLs to go the same message):
http://www.geocrawler.com/archives/3/167/2001/11/100/7193611/
http://www.geocrawler.com/mail/msg.php3?msg_id=7193611&list=167
The patch in the above mail message was attached to this bug
report for reference purposes (in case the URLs above cease
working before this bug gets fixed). Here is the contents of the
original message itself:
FROM: David Wolfskill
DATE: 11/28/2001 21:42:54
SUBJECT: ports/32372: Broken timezone specification generated by Bulkmail.pm
Show quoted text
>Number: 32372
>Category: ports
>Synopsis: Broken timezone specification generated by Bulkmail.pm
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-ports
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Wed Nov 28 21:50:01 PST 2001
>Closed-Date:
>Last-Modified:
>Originator: David Wolfskill
>Release: FreeBSD 4.1.1-STABLE i386
>Organization:
Whistle Communications
Show quoted text>Environment:
Use the /usr/ports/mail/p5-Mail-Bulkmail (Mail::Bulkmail) port.
Show quoted text>Description:
The Bulkmail.pm module generates its own Date: header for the
messages it sends. One of the fields in the Date: header is the
time specification, which includes a "timezone" specification,
which denotes the number of hours and minutes the time in
question is offset from UTC (GMT), as well as the direction of
that offset.
The module has a function ("Tz", starting at line 703) to
compute the above-mentioned offset. The function will, under
some circumstances, produce and return values that are not
within the range -1200 to +1200, and has the (slight!)
possibility of returning a value that is otherwise incorrect.
(This latter is highly improbable, but easily fixed.)
The Tz() function works by using the "hours" and "minutes" part
of the current time; in particular, it examines and uses the
difference between the "localtime()" and the "gmtime()" results.
There are some problems, though:
* There is code to check to see if the magnitude of the
difference exceeds 12 hours. However, in such a case, the
result is adjusted by 12 hours, rather than the 24 hours that
would be correct. (For example, sending a message at 1703
hrs. local time, from a time zone that is 8 hrs. west of GMT
-- so that the time in question is 0103 hrs. UTC/GMT the
following day -- would yield a difference of 16 hrs. Since
this is >12, the code then subtracts that difference (16) from
12, yielding -4. Rather, the code should subtract 24 from the
difference in the ">12" case, yielding -8 hrs., which is
correct.)
* Even though the "difference" is "corrected" (albeit
incorrectly, per the above), there is a sprintf() call
immediately afterward that clobbers the "correction" anyway,
so the reported offset in the above example will be "1600",
which is outside the range of -1200 to +1200. Note that the
extent to which this result will be in error depends on the
magnitude of the offset between local time and UTC/GMT, as
well as the time of day of the experiment.
* There is no provision to ensure that an offset that has a
non-zero "minutes" component actually has the direction of the
minutes component that agrees with the direction of the hours
component. (For 0- or 30-minute offsets, this isn't likely to
matter much.)
* Finally, the exceedingly improbable error is caused by the use
of two *different* calls to Perl's time() function (one for
the localtime() call; the other for the gmtime() call). It is
possible, though (very!) unlikely that the two calls will be
made at times such that the results could be on either side of
a minute (or even hour) boundary. The cure for this is to
invoke time() but once, save the value, and use that for each
of the localtime() and gmtime() calls.
Show quoted text>How-To-Repeat:
Well, here are the salient headers from an example such as the
one described above:
Received: from us.ibm.com (pau-amma.whistle.com [207.76.205.64])
by gatekeeper.whistle.com (8.11.1/8.11.1) with SMTP id fAS13NE20829
for <<EMAIL: PROTECTED>>; Tue, 27 Nov 2001 17:03:23 -0800 (PST)
Message-Id: <<EMAIL: PROTECTED>>
Date: Tue, 27 Nov 2001 17:03:23 +1600
Note the timezone specification in the Date: header vs. that in
the Received: header. Had this been done between 0000 - 1559
hrs. PST (8 hrs. west of GMT), the error would not have been
apparent.
Show quoted text>Fix:
Apply the following patch to Bulkmail.pm:
--- Bulkmail.pm.orig Fri Sep 7 12:28:16 2001
+++ Bulkmail.pm Wed Nov 28 07:27:50 2001
@@ -704,16 +704,31 @@
my $self = shift or undef;
- my ($min, $hour, $isdst) = (localtime(time))[1,2,-1];
- my ($gmin, $ghour, $gsdst) = (gmtime(time))[1,2, -1];
+ my $now = time;
+ my ($min, $hour, $isdst) = (localtime($now))[1,2,-1];
+ my ($gmin, $ghour, $gsdst) = (gmtime($now))[1,2, -1];
- my $diffhour = $hour - $ghour;
- $diffhour = 12 - $diffhour if $diffhour > 12;
- $diffhour = 12 + $diffhour if $diffhour < -12;
+ my $diffmin = ($hour - $ghour) * 60 + ($min - $gmin);
+ my $diffhour = int($diffmin / 60);
+ $diffmin -= $diffhour * 60;
+ if ($diffhour > 12) {
+ $diffhour -= 24;
+ if ($diffmin) {
+ $diffhour += 1;
+ $diffmin -= 60;
+ }
+ } elsif ($diffhour < -12) {
+ $diffhour += 24;
+ if ($diffmin) {
+ $diffhour -= 1;
+ $diffmin += 60;
+ }
+ }
+ $diffmin = abs($diffmin);
- ($diffhour = sprintf("%03d", $hour - $ghour)) =~ s/^0/\+/;
+ ($diffhour = sprintf("%03d%02d", $diffhour, $diffmin)) =~ s/^0/\+/;
- return $diffhour . sprintf("%02d", $min - $gmin);
+ return $diffhour;
};
Show quoted text>Release-Note:
>Audit-Trail:
>Unformatted:
To Unsubscribe: send mail to <EMAIL: PROTECTED>
with "unsubscribe freebsd-ports" in the body of the message
--- Bulkmail.pm.orig Fri Sep 7 12:28:16 2001
+++ Bulkmail.pm Wed Nov 28 07:27:50 2001
@@ -704,16 +704,31 @@
my $self = shift or undef;
- my ($min, $hour, $isdst) = (localtime(time))[1,2,-1];
- my ($gmin, $ghour, $gsdst) = (gmtime(time))[1,2, -1];
+ my $now = time;
+ my ($min, $hour, $isdst) = (localtime($now))[1,2,-1];
+ my ($gmin, $ghour, $gsdst) = (gmtime($now))[1,2, -1];
- my $diffhour = $hour - $ghour;
- $diffhour = 12 - $diffhour if $diffhour > 12;
- $diffhour = 12 + $diffhour if $diffhour < -12;
+ my $diffmin = ($hour - $ghour) * 60 + ($min - $gmin);
+ my $diffhour = int($diffmin / 60);
+ $diffmin -= $diffhour * 60;
+ if ($diffhour > 12) {
+ $diffhour -= 24;
+ if ($diffmin) {
+ $diffhour += 1;
+ $diffmin -= 60;
+ }
+ } elsif ($diffhour < -12) {
+ $diffhour += 24;
+ if ($diffmin) {
+ $diffhour -= 1;
+ $diffmin += 60;
+ }
+ }
+ $diffmin = abs($diffmin);
- ($diffhour = sprintf("%03d", $hour - $ghour)) =~ s/^0/\+/;
+ ($diffhour = sprintf("%03d%02d", $diffhour, $diffmin)) =~ s/^0/\+/;
- return $diffhour . sprintf("%02d", $min - $gmin);
+ return $diffhour;
};