Skip to content

Commit

Permalink
Improve swaks support for Windows
Browse files Browse the repository at this point in the history
-  Allow LOGNAME to be used on Windows.  It's not a concept native to Windows, but there's no reason not to allow the default sender to be overridden by an environment variable
-  Fix date generation on Windows.  Windows supports strftime, but not the %z format token we were using.  Dates will be GMT on Windows and any other platform that doesn't support %z.
-  Fix how the app name is found - was broken both by the change in file system delimiters and the .pl extension
-  use binmode on STDIN and STDOUT to prevent perl helping with line ending translations
-  change the #! line to use /usr/bin/env for portability (this might have been for FreeBSD actually)
-  Fix setting environment variables on windows
--- since env vars are caseless on Windows, force all options to lower case (Note that this breaks -S.  Document and try to avoid case collisions in options in the future).
--- Since Windows doesn't allow empty environment variables, allow variable values of '<>' to mean "set but empty"
--- Explicitly revoke support for header names embedded in the --header option when set via environment variable on Windows (because Windows vars are caseless)
  • Loading branch information
jetmore committed Nov 7, 2020
1 parent 05581df commit 6416f7b
Show file tree
Hide file tree
Showing 23 changed files with 101 additions and 28 deletions.
15 changes: 15 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,21 @@
> 20201014 released 20201014.0
* 20201016 Testing: Document and improve automated testing to make it easier
for new users to run in new environments
* 20201018 Change script interpreter line to use /usr/bin/env for portability
* 20201018 Testing: Add --winnow to run-all.pl
* 20201030 Fix date generation by confirming environment's strftime supports
%z format token. Windows supports strftime but not %z.
* 20201030 Windows: use binmode on STDERR/STDOUT to prevent line ending
translation.
* 20201031 Windows: Fix setting options via environment variable (this fix
now means that -S is not supported on Windows)
* 20201031 Windows: Explicitly revoke support for providing header names
embedded in environment variable names (%SWAKS_OPT_header_From%
will result in an error)
* 20201031 Allow a value of '<>' in an environment variable to mean empty
string. Usable everywhere, but needed on Windows since it doesn't
support set-but-empty environment variables.
* 20201101 Windows: Allow LOGNAME environment variable to override default
email sender
* 20201106 Testing: Extensive changes to allow test tools to run on Windows
cmd.exe
14 changes: 7 additions & 7 deletions doc/base.pod
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ Options can be supplied via environment variables. The variables are in the for
$ SWAKS_OPT_from='fred@example.com'
$ SWAKS_OPT_h_From='"Fred Example" <fred@example.com>'

Setting a variable to an empty value is the same as specifying it on the command line with no argument. For instance, setting <SWAKS_OPT_server=""> would cause Swaks to prompt the user for the server to which to connect at each invocation.
Setting a variable to an empty value is the same as specifying it on the command line with no argument. For instance, setting <SWAKS_OPT_server=""> would cause Swaks to prompt the user for the server to which to connect at each invocation. On Windows, it is not possible to set empty environment variables. The behavior can be simulated by setting the environment variable to C<< <> >> instead. Additionally, embedding the header name in the header option via environment variable is not allowed on Windows (eg C<SWAKS_OPT_header_Foo=bar> will result in an error, but C<SWAKS_OPT_header="Foo: bar"> will work.)

Because there is no inherent order in options provided by setting environment variables, the options are sorted before being processed. This is not a great solution, but it at least defines the behavior, which would be otherwise undefined. As an example, if both C<$SWAKS_OPT_from> and C<$SWAKS_OPT_f> were set, the value from C<$SWAKS_OPT_from> would be used, because it sorts after C<$SWAKS_OPT_f>. Also as a result of not having an inherent order in environment processing, unsetting options with the C<no-> prefix is unreliable. It works if the option being turned off sorts before C<no->, but fails if it sorts after. Because C<no-> is primarily meant to operate between config types (for instance, unsetting from the command line an option that was set in a config file), this is not likely to be a problem.

Expand Down Expand Up @@ -309,7 +309,7 @@ There is no default value for this option. If no recipients are provided via an

=item -f, --from [<email-address>]

Use argument as envelope-sender for email, or prompt user if no argument specified. The string C<< <> >> can be supplied to mean the null sender. If user does not specify a sender address a default value is used. The domain-part of the default sender is a best guess at the fully-qualified domain name of the local host. The method of determining the local-part varies. On Windows, C<Win32::LoginName()> is used. On UNIX-ish platforms, the C<$LOGNAME> environment variable is used if it is set. Otherwise L<getpwuid(3)> is used. See also C<--force-getpwuid>. If Swaks cannot determine a local hostname and the sender address is needed for the transaction, Swaks will error and exit. In this case, a valid string must be provided via this option. (Arg-Required, From-Prompt)
Use argument as envelope-sender for email, or prompt user if no argument specified. The string C<< <> >> can be supplied to mean the null sender. If user does not specify a sender address a default value is used. The domain-part of the default sender is a best guess at the fully-qualified domain name of the local host. The method of determining the local-part varies. If the C<$LOGNAME> environment variable is set, it will be used as the local-part. Otherwise the value from C<Win32::LoginName()> will be used on Windows and L<getpwuid(3)> on UNIX-ish platforms. See also C<--force-getpwuid>. If Swaks cannot determine a local hostname and the sender address is needed for the transaction, Swaks will error and exit. In this case, a valid string must be provided via this option. (Arg-Required, From-Prompt)

=item --ehlo, --lhlo, -h, --helo [<helo-string>]

Expand Down Expand Up @@ -445,7 +445,7 @@ If the server supports it, attempt Per-Recipient Data Response (PRDR) (L<https:/

=item --force-getpwuid

Tell Swaks to use the getpwuid method of finding the default sender local-part instead of trying C<$LOGNAME> first. (Arg-None)
Tell Swaks to use the system-default method of determining the current user's username for the default sender local-part instead of trying C<$LOGNAME> first. Despite the UNIX-ish-specific option name, this option also works on Windows. (Arg-None)

=back

Expand Down Expand Up @@ -769,7 +769,7 @@ Replaced with the envelope-recipient(s).

=item %DATE%

Replaced with the current time in a format suitable for inclusion in the Date: header. Note this attempts to use the standard module L<POSIX> for timezone calculations. If this module is unavailable the date string will be in GMT.
Replaced with the current time in a format suitable for inclusion in the Date: header. Note this attempts to use the standard module L<POSIX> for timezone calculations. If this module is unavailable or the current environment doesn't support the %z strftime format token (as on Windows) the date string will be in GMT.

=item %MESSAGEID%

Expand Down Expand Up @@ -837,7 +837,7 @@ The option can either be specified multiple times or a single time with multiple

=item --header <header-and-data>, --h-<header> <data>

These options allow a way to change headers that already exist in the DATA. C<--header 'Subject: foo'> and C<--h-Subject foo> are equivalent. If the header does not already exist in the data then this argument behaves identically to C<--add-header>. However, if the header already exists it is replaced with the one specified. Negating the version of this option with the header name in the option (eg C<--no-header-Subject>) will remove all previously processed C<--header> options, not just the ones used for 'Subject'. (Arg-Required)
These options allow a way to change headers that already exist in the DATA. C<--header 'Subject: foo'> and C<--h-Subject foo> are equivalent. If the header does not already exist in the data then this argument behaves identically to C<--add-header>. However, if the header already exists it is replaced with the one specified. Negating the version of this option with the header name in the option (eg C<--no-header-Subject>) will remove all previously processed C<--header> options, not just the ones used for 'Subject'. Embedding the header name in the option via environment variable is not supported on Windows and will result in an error. (Arg-Required)

=item -g

Expand Down Expand Up @@ -969,7 +969,7 @@ Do not display any content to the terminal. (Arg-None)

=item -S, --silent [ 1 | 2 | 3 ]

Cause Swaks to be silent. If no argument is given or if an argument of "1" is given, print no output unless/until an error occurs, after which all output is shown. If an argument of "2" is given, only print errors. If "3" is given, show no output ever. C<--silent> affects most output but not all. For instance, C<--help>, C<--version>, C<--dump>, and C<--dump-mail> are not affected. (Arg-Optional)
Cause Swaks to be silent. If no argument is given or if an argument of "1" is given, print no output unless/until an error occurs, after which all output is shown. If an argument of "2" is given, only print errors. If "3" is given, show no output ever. C<--silent> affects most output but not all. For instance, C<--help>, C<--version>, C<--dump>, and C<--dump-mail> are not affected. For historical reasons, -S is not settable via environment variable on Windows, use SWAKS_OPT_silent instead. (Arg-Optional)

=item --support

Expand Down Expand Up @@ -1021,7 +1021,7 @@ The C<--data>, C<--body>, C<--attach>, and C<--attach-body> options currently wi

This program was primarily intended for use on UNIX-like operating systems, and it should work on any reasonable version thereof. It has been developed and tested on Solaris, Linux, and Mac OS X and is feature complete on all of these.

This program is known to demonstrate basic functionality on Windows using ActiveState's Perl. It has not been fully tested. Known to work are basic SMTP functionality and the LOGIN, PLAIN, and CRAM-MD5 auth types. Unknown is any TLS functionality and the NTLM/SPA and DIGEST-MD5 auth types.
This program is known to demonstrate basic functionality on Windows using Strawberry Perl. In all documentation, unless otherwise noted, "Windows" refers to running Swaks via CMD.exe, not WSL or cygwin. It has not been fully tested, but known to work are basic SMTP functionality and the LOGIN, PLAIN, and CRAM-MD5 auth types. Unknown is any TLS functionality and the NTLM/SPA and DIGEST-MD5 auth types. Some functionality is known to be limited on Windows, including inability to embed header name in environment variables (see L</"CONFIGURATION ENVIRONMENT VARIABLES"> and C<--header>), inability to generate a local-timezone date string (see C<%DATE%> token under C<--data>), inability to use C<-S> option as an environment variable (see C<--silent>), and inability to have a "set but empty" value in an environment variable (see L</"CONFIGURATION ENVIRONMENT VARIABLES"> for workaround).

Because this program should work anywhere Perl works, I would appreciate knowing about any new operating systems you've thoroughly used Swaks on as well as any problems encountered on a new OS.

Expand Down
37 changes: 28 additions & 9 deletions swaks
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/perl
#!/usr/bin/env perl

# use 'swaks --help' to view documentation for this program
#
Expand All @@ -10,9 +10,13 @@
# Twitter: http://www.twitter.com/SwaksSMTP

use strict;
use File::Spec::Functions qw(splitdir);

binmode(STDOUT);
binmode(STDERR);

$| = 1;
my($p_name) = $0 =~ m|/?([^/]+)$|;
my($p_name) = (splitdir($0))[-1] =~ m|^(.+?)(\.pl)?$|;
my $p_version = build_version("DEVRELEASE", '$Id$');
my $p_usage = "Usage: $p_name [--help|--version] (see --help for details)";
my $p_cp = <<'EOM';
Expand Down Expand Up @@ -2236,9 +2240,19 @@ sub load_args {
@fakeARGV = ();
foreach my $v (sort keys %ENV) {
if ($v =~ m|^SWAKS_OPT_(.*)$|) {
my $tv = $1; $tv =~ s|_|-|g;
my $tv = $1;
my $ta = $ENV{$v};
$tv =~ s|_|-|g;
$tv = lc($tv) if ($^O eq 'MSWin32');
$ta = '' if ($ta eq '<>');

if ($^O eq 'MSWin32' && $tv =~ /^(no-)?h(?:eader)?-/) {
ptrans(12, 'Embedding header names in environment variable names is not supported on Windows. Exiting');
exit(1);
}

push(@fakeARGV, '--' . $tv);
push(@fakeARGV, $ENV{$v}) if (length($ENV{$v}));
push(@fakeARGV, $ta) if (length($ta));
}
}
fetch_args(\%ARGS, $option_list, \@fakeARGV) if (scalar(@fakeARGV));
Expand Down Expand Up @@ -3733,14 +3747,18 @@ sub get_running_state {

sub get_username {
my $force_getpwuid = shift;
my $fallback = '';
if ($^O eq 'MSWin32') {
require Win32;
return Win32::LoginName();
$fallback = Win32::LoginName();
}
else {
$fallback = (getpwuid($<))[0];
}
if ($force_getpwuid) {
return (getpwuid($<))[0];
return $fallback;
}
return $ENV{LOGNAME} || (getpwuid($<))[0];
return $ENV{LOGNAME} || $fallback;
}

sub get_date_string {
Expand All @@ -3750,8 +3768,9 @@ sub get_date_string {
my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @day_names = qw(Sun Mon Tue Wed Thu Fri Sat);

if (!avail("date_manip")) {
ptrans(12, avail_str("date_manip").". Date strings will be in GMT");
# Windows has POSIX::strftime but not %z. Consider adding POSIX::strftime::GNU as an alternate option for date_manip in future
# but for now just force to UTC if %z doesn't appear to work
if (!avail("date_manip") || POSIX::strftime("%z", localtime($et)) !~ /\d\d\d\d$/) {
my @l = gmtime($et);
$G::date_string = sprintf("%s, %02d %s %d %02d:%02d:%02d %+05d",
$day_names[$l[6]],
Expand Down
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01020.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: h-From, env var, no arg

pre action: SET_ENV SWAKS_OPT_h_From
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01021.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: h-From, env var, valid arg

pre action: SET_ENV SWAKS_OPT_h_From 'added-by--header'
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01070.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: h-From:, env var, no arg

pre action: SET_ENV SWAKS_OPT_h_From:
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01071.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: h-From:, env var, valid arg

pre action: SET_ENV SWAKS_OPT_h_From: 'added-by--header'
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01120.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: header-From, env var, no arg

pre action: SET_ENV SWAKS_OPT_header_From
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01121.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: header-From, env var, valid arg

pre action: SET_ENV SWAKS_OPT_header_From 'added-by--header'
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01170.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: header-From:, env var, no arg

pre action: SET_ENV SWAKS_OPT_header_From:
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
4 changes: 3 additions & 1 deletion testing/regressions/_options-data/01171.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr
title: header-From:, env var, valid arg

pre action: SET_ENV SWAKS_OPT_header_From: 'added-by--header'
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --helo hserver --server "ser.ver"

skip: IFOS=MSWin32 Windows does not support embedding the header name in the header option via environment variable
6 changes: 6 additions & 0 deletions testing/regressions/_options-data/05218.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr

skip: IFOS!=MSWin32 This test is for the Windows platform only

pre action: SET_ENV SWAKS_OPT_header_From 'joe@example.com'
test action: CMD_CAPTURE %SWAKS% --dump DATA --to user@host1.nodns.test.swaks.net --server ser.ver --helo host1.nodns.test.swaks.net --from from@host1.nodns.test.swaks.net
1 change: 1 addition & 0 deletions testing/regressions/_options-data/out-ref/05218.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*** Embedding header names in environment variable names is not supported on Windows. Exiting
Empty file.
1 change: 1 addition & 0 deletions testing/regressions/_options-data/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@
15 - --header works with spaces in header name (dupe)
16 - no colon in header line
17 - first line of data looks like a continuation header
18 - embedding header name in environment variable results in error on Windows
05250 - regression tests for multi-line headers
251 header replacement SINGLE-LINE, SINGLE-HEADER FIRST
252 header replacement SINGLE-LINE, SINGLE-HEADER MIDDLE
Expand Down
2 changes: 2 additions & 0 deletions testing/regressions/_options-output/01170.test
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ title: S, env var, no arg

pre action: SET_ENV SWAKS_OPT_S
test action: CMD_CAPTURE %SWAKS% --dump OUTPUT --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --server "ser ver"

skip: IFOS=MSWin32 Windows does not support the -S option when set via environment variable
2 changes: 2 additions & 0 deletions testing/regressions/_options-output/01171.test
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ title: S, env var, valid arg

pre action: SET_ENV SWAKS_OPT_S 2
test action: CMD_CAPTURE %SWAKS% --dump OUTPUT --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --server "ser ver"

skip: IFOS=MSWin32 Windows does not support the -S option when set via environment variable
2 changes: 2 additions & 0 deletions testing/regressions/_options-output/01172.test
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ title: S, env var, invalid arg

pre action: SET_ENV SWAKS_OPT_S asdf
test action: CMD_CAPTURE %SWAKS% --dump OUTPUT --to user@host1.nodns.test.swaks.net --from recip@host1.nodns.test.swaks.net --server "ser ver"

skip: IFOS=MSWin32 Windows does not support the -S option when set via environment variable
7 changes: 7 additions & 0 deletions testing/regressions/_options-processing/0181.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
auto: REMOVE_FILE,CREATE_FILE,MUNGE,COMPARE_FILE %TESTID%.stdout %TESTID%.stderr

# confirm that <> means "set but empty" for environment variables
# --copy-routing is meaningless, just a convenient function that errors if specified without an argument

pre action: SET_ENV SWAKS_OPT_copy_routing '<>'
test action: CMD_CAPTURE %SWAKS% --dump OUTPUT --to user@host1.nodns.test.swaks.net --server ser.ver
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*** Option processing error: option copy-routing specified with no argument
Empty file.

0 comments on commit 6416f7b

Please sign in to comment.