3 ###############################################################
4 ###############################################################
9 #use IO::Socket::SSL qw( SSL_ERROR );
10 use Net::Domain qw(hostfqdn);
11 use Getopt::Long qw(:config default bundling no_ignore_case auto_version);
16 our @ISA = qw(Net::Cmd);
18 ###############################################################
19 ###############################################################
21 my ($smtp,$optsin,$opt,$mess,$rcpt,@headers,$finished_header,$ofh);
22 $main::VERSION = '1.2.2';
26 'debug|d' => \&optset,
27 'ehlo|helo|m=s' => \&optset,
28 'rcptto|recipient|r=s' => \&optset,
29 'host|h=s' => \&optset,
30 'from822|u=s' => \&optset,
33 'mailfrom|from821|from|f=s' => \&optset,
34 'port|p=i' => \&optset,
35 'wellknown|w=s' => \&optset,
36 'output|W=s' => \&optset,
37 'include_options|O' => \&optset,
38 'include_headers|H' => \&optset,
39 'bounce|B' => \&optset,
41 'nostarttls|s' => \&optset,
42 'stricttls|strict_tls' => \&optset,
43 'sslargs|tlsargs=s' => \&optset,
44 'verbose' => \&optset,
48 map { my $t = $_; $t =~ s/\|.*//; $opt->{$t} = undef; } keys %$optsin;
49 GetOptions( %$optsin ) or pod2usage(2);
50 pod2usage(1) if $opt->{'help'};
51 pod2usage(-exitval => 0, -verbose => 2) if $opt->{'man'};
53 print _Dumper($opt, 'Options')
56 ###############################################################
57 ###############################################################
61 ###############################################################
62 ###############################################################
64 bail( 1, "Host(--host) must be provided" )
67 $opt->{'port'} = $opt->{'tls'} ? 465 : 25
72 $opt->{'ehlo'} = hostfqdn();
73 fret( "Machine set to $opt->{'ehlo'}" ) if $opt->{'debug'};
75 if (!$opt->{'mailfrom'} && !$opt->{'bounce'})
77 $opt->{'mailfrom'} = $ENV{USER}. "@". $opt->{'ehlo'};
78 fret( "MAIL FROM set to $opt->{'mailfrom'}" ) if $opt->{'debug'};
80 if (!$opt->{'from822'})
82 $opt->{'from822'} = $opt->{'mailfrom'};
83 fret( "From: set to $opt->{'from822'}" ) if $opt->{'debug'};
87 $opt->{'mailfrom'} = "";
88 $opt->{'from822'} = 'mailer-daemon@'. hostfqdn();
89 fret( "MAIL FROM set to $opt->{'mailfrom'}", "From: set to $opt->{'from822'}" ) if $opt->{'debug'};
92 bail( 1, "EXPN or VRFY cannot be used without a recipient" )
93 if ($opt->{'expn'} || $opt->{'vrfy'}) && ! $opt->{'rcptto'};
95 bail( 1, "Either a recipient or well-known resource must be specified" )
96 if ! $opt->{'wellknown'} && ! $opt->{'rcptto'};
98 bail( 1, "Only one of recipient or well-known resource can be specified" )
99 if $opt->{'wellknown'} && $opt->{'rcptto'};
101 if ( $opt->{'sslargs'} )
103 my @p = split /[=,]/, $opt->{'sslargs'};
104 $opt->{'sslparams'} = \@p;
108 $opt->{'sslparams'} = [ 'SSL_verify_mode', $opt->{'stricttls'} ? 1 : 0 ];
110 fret( _Dumper( $opt->{'sslparams'}, 'sslparams' ) )
111 if $opt->{'debug'} && ( $opt->{'tls'} || ! $opt->{'nostarttls'} );
113 ###############################################################
114 ###############################################################
116 ## parameter checking complete. now onto operations
119 ###############################################################
120 ###############################################################
124 $smtp= Net::SMTP->new( $opt->{'host'},
125 Hello => $opt->{'ehlo'},
126 Debug => $opt->{'debug'},
127 ( $opt->{'tls'} ? ( 'SSL' => $opt->{'sslargs'} || 1 ) : () ),
128 Port => $opt->{'port'},
130 bail( 1, "Connection Failed: $@" )
133 if (!$opt->{'nostarttls'})
135 bail( $smtp, 1, "Failed to STARTTLS - $@" )
136 if ! $smtp->starttls( @{$opt->{'sslparams'}} );
138 fret( $smtp->message() )
139 if $opt->{'verbose'};
142 if ($opt->{'wellknown'})
144 bail( $smtp, 1, "Server does not support WELLKNOWN" )
145 if ! $smtp->supports('WELLKNOWN');
147 my $e_wk = encode_xtext( $opt->{'wellknown'} );
149 bail( $smtp, 1, "Failed to WELLKNOWN - $e_wk", $smtp->message() )
150 if ! ( $smtp->command( 'WELLKNOWN', $e_wk )->response() == CMD_OK );
152 fret( "Protocol violation. Code was OK, but not 250", $smtp->code. " - ". $smtp->message )
153 if $smtp->code ne '250';
155 $mess = $smtp->message;
157 ($info,$mess) = split( /\n/, $mess, 2 );
158 $info =~ /size=(\d+)/i;
160 $mess = decode_xtext( $mess );
161 fret( "Size mismatch on wellknown fetch", "Expected: ". $size, "Received: ". length($mess) )
162 if length($mess) != $size;
164 if ( $opt->{'output'} )
166 # Output to named file
168 bail( $smtp, 1, "Unable to open file $opt->{'output'} for WELLKNOWN output - $!" )
169 if ! ( $ofh = IO::File->new("> $opt->{'output'}") );
176 # might be hazardous, output via pager
177 print STDERR "$mess\n";
183 $smtp->verify($opt->{'vrfy'});
184 fret( $smtp->message() );
189 $smtp->expand($opt->{'expn'});
190 fret( $smtp->message() );
193 if ($opt->{'rcptto'})
195 bail( $smtp, 1, "MAIL FROM $opt->{'mailfrom'} failed", $@ )
196 if ! $smtp->mail($opt->{'mailfrom'});
198 bail( $smtp, 1, "RCPT TO $opt->{'rcptto'} failed", $@ )
199 if ! $smtp->to($opt->{'rcptto'});
201 # handle any recipients on command line
202 while( $rcpt = shift @ARGV )
204 last if $rcpt eq '--';
205 fret( "RCPT TO $rcpt failed", $@ )
206 if ! $smtp->to($rcpt);
209 bail( $smtp, 1, "Unable to set data mode", @_ )
214 push @headers, "Subject: Test Message\n";
215 $smtp->datasend("From: $opt->{'from822'}\n");
216 $smtp->datasend("To: $opt->{'rcptto'}\n");
217 $smtp->datasend("Subject: Test Message\n");
218 $smtp->datasend("\n");
219 $smtp->datasend("This is a test message\n");
220 $smtp->datasend("generated with mailtest\n");
225 if($finished_header==0)
229 $finished_header = 1;
232 push @headers," ".$_;
235 $smtp->datasend("$_");
238 if($opt->{'include_headers'} && @headers)
240 $smtp->datasend("\n Copy of headers follow....\n");
243 $smtp->datasend("$_");
245 $smtp->datasend("\n");
247 if($opt->{'include_options'})
249 $smtp->datasend("\n Copy of options follow....\n");
250 $smtp->datasend(" SMTP HOST $opt->{'host'}\n");
251 $smtp->datasend(" HELO $opt->{'ehlo'}\n");
252 $smtp->datasend(" MAIL FROM: $opt->{'mailfrom'}\n");
253 $smtp->datasend(" RCPT TO: $opt->{'rcptto'}\n\n");
255 fret( "dataend failed", $@ )
256 if ! $smtp->dataend();
262 ##############################################################
263 ##############################################################
270 #print STDERR "Setting $n to $v\n";
271 $opt->{$n->{'name'}} = $v;
278 $mess =~ s/[\n\r]//g;
279 $mess =~ s/\+([0-9a-fA-F]{2})/chr(hex($1))/ge;
287 $mess =~ s/([^!-*,-<>-~])/'+'.uc(unpack('H*', $1))/eg;
294 return Data::Dumper->Dump( [$_[0]], [$_[1] || 'VAR1'] );
300 map { print STDERR $_,"\n"; } @_;
313 ##############################################################
314 ##############################################################
320 mailtest - Simple SMTP sending for diagnostics
324 mailtest --host host.example.com --rcptto recipient@example.com [ send_options ... ] [ additional recipients ...]
336 recipient for message
339 machine name for EHLO
341 --vrfy request VRFY of recipient
342 --expn request EXPN of recipient
345 use as MAIL FROM value
354 include Options in body
356 include generated headers in body
358 --tls perform TLS on connect
359 --nostarttls do no attempt STARTTLS
360 --stricttls Enable strict verification on TLS connection
362 --tlsargs arg=value[,arg=value]
363 Explicitly define TLS options.
365 --bounce sending as bounce (<>)
370 Output file to receive well-known data
379 Print a brief help message and exits.
383 Enables debugging, outpus additional information whilst processing requests.
385 =item B<-h, --host>=I<host>
387 Specifies the host to connect to. Must be specified and must be IP-resolvable.
389 =item B<-m, --ehlo>=I<machine>
391 Specified the machine name to use as the B<EHLO> value. Defaults to the fully-qualified name of the host running the command.
393 =item B<-r, --rcptto>=I<recipient>
395 Specifies the recipient of message. This is used as the B<RCPT TO> value.
399 Uses the I<recipient> parameter for the value in a B<VRFY> request. This disables the sending of an email.
403 Uses the I<recipient> parameter for the value in an B<EXPN> request. This disables the sending of an email.
405 =item B<-f, --mailfrom>=I<from_address>
407 Specified the value to use in the B<MAIL FROM> command. Defaults to the current username at the FQDN of the machine B<-m> unless the B<-B> option is used.
409 =item B<-u, --from822>=I<from_user>
411 Specified the value to use in the message headers. Defaults to the B<-f> I<from_address> value unless the B<-B> option is used.
413 =item B<-B, --bounce>
415 Replace the B<--mailfrom> I<from_address> with B<\<\>> and the B<--from833> I<from_user> with B<mailer-daemon@host> where the host is the value of B<--ehlo> I<machine>
417 =item B<-p, --port>=I<port>
419 Specified the port to connect to on the specified host. Defaults to port 25 unless B<-S> is given in which case it defaults to 465.
423 Specifies that TLS be used directly on the connection prior to any SMTP commands. Changes the connection port to 465 unless it has been explicitly provided. Disables any attempts at B<STARTTLS>.
425 =item B<-s, --nostarttls>
427 Disables attempting STARTTLS if offered. Disabled by use of B<-S>.
431 Enables strict verification of the TLS connection. Sets the underlying SSL option B<SSL_verify_mode> to 1/SSL_VERIFY_PEER rather than 0/SSL_VERIFY_NONE. Since the aim of this tool is to test the SMTP protocol behaviour and not the TLS behaviour the decision was made to default the B<SSL_verify_mode> to 0/SSL_VERIFY_NONE.
433 =item B<--sslargs>=argname=argvalue[,argname=argvalue...]
435 Allow full control over underlying SSL options. Overrides B<--stricttls>. See the documentation for B<IO::Socket::SSL> for further details.
437 --sslargs SSL_verifycn_name=certname.example.com
441 Generate a body for the message being sent.
443 =item B<-O, --include-options>
445 Include details of options used in the message body.
447 =item B<-H, --include-headers>
449 Include a copy of the generated headers in the message body.
451 =item B<-w, --wellknown>=I<well-known-path>
453 Provides the path value for a B<WELLKNOWN> command.
455 =item B<-W, --output>=I<output_file>
457 Provides the output file where the B<WELLKNOWN> data should be stored.
463 B<mailtest> is a simple utility for testing SMTP connections.
464 It is designed to debug endpoints and not for full email generation.
466 It support a number of basic operations, SEND, VRFY, EXPN, WELLKNOWN.
470 C<mailtest> only requires modules that should be in all normal distributions.
474 Bernard Quatermass <bernardq@exim.org>
476 =head1 COPYRIGHT AND LICENSE
478 This software is Copyright (c) 2008,2020,2024 by Bernard Quatermass.
483 ###############################################################
486 ###############################################################