diff --git a/Artistic b/Artistic new file mode 100644 index 0000000..d1b6e5a --- /dev/null +++ b/Artistic @@ -0,0 +1,131 @@ + + + + + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/Changes b/Changes new file mode 100644 index 0000000..d4f0a5b --- /dev/null +++ b/Changes @@ -0,0 +1,434 @@ +Revision history for Perl distribution libnet + +3.11 2017-11-14 + + - Treat FTP MLSD commands case-insensitively. [Brian M. Carlson, PR#32] + +3.10 2016-08-01 + + - Removed . from @INC when loading optional modules. [Tony Cook, Perl + RT#127834, CVE-2016-1238] + + - Removed the default Net::Cmd::timeout() since it inadvertently overrode + the timeout() method in whatever IO::Socket::INET-like class sub-classes + of Net::Cmd also derive from (at least in cases where Net::Cmd takes + precedence in the method resolution, which it should do so that + Net::Cmd::getline() overrides IO::Handle::getline()). + + This does cause problems for any Net::Cmd sub-classes that don't provide + (by whatever means) the necessary parts of the interface of + IO::Socket::INET, but since they mostly seem to anyway (apart from the one + that led to the CPAN RT#110978 report!) this is now simply a documented + requirement. + + [CPAN RT#116345] + +3.09 2016-07-19 + + - Provided (and documented) a default Net::Cmd::timeout(). [CPAN RT#110978] + + - Increased minimum required version of IO::Socket::IP to 0.25 to hopefully + stop t/pop3_ipv6.t hanging. [CPAN RT#104545] + + - Debug output now includes decoded (from base64) negotiation for SASL. + [Philip Prindeville, PR#27] + + - Adapted tests to Test2 revision of Test::More::note() [James E. Keenan, + PR#28] + +3.08 2016-01-05 + + - Fixed a bug introduced in version 1.28 whereby a short write in Net::Cmd + would be treated as an error instead of looping. [David Golden, PR#24] + + - Documented the fact that Net::SMTP::auth() can accept an Authen::SASL + object instead of a username and password. [Jan Viktorin, CPAN RT#106183] + + - Simplified specification of INSTALLDIRS: We do not need to check the lower + bound since we only support Perl 5.8.1 and higher anyway. + +3.07 2015-07-17 + + - Net::FTP::rmdir() has been made more robust by making use of the MLSD + command in addition to the NLST command since the latter is known not to + be processed correctly by some FTP servers. [Chris Lindee, CPAN + RT#100694] + + - Net::FTP, Net::NNTP, Net::POP3 and Net::SMTP can now restrict domain to + IPv4 even if IPv6 is available by using the new Domain or Family argument. + + Net::NNTP now supports the LocalPort argument in addition to LocalAddr. + + Net::POP3 now supports the LocalAddr and LocalPort arguments in addition + to ResvPort (which is retained for backwards compatibility). + + [Steffen Ullrich, PR#18] + + - Fixed a bug in Net::Cmd::datasend() which caused octets in [\x80-\xFF] + stored in a "binary string" to be replaced with their UTF-8 encodings if + the string happened to be stored internally in an "upgraded" state (i.e. + with the UTF-8 flag on). (As noted below, strings passed to datasend() + should always be encoded first, and therefore not stored in such a state + anyway, but it is all too easy for perl to change this internal state + unless the encodeing is done at the very last minute before calling + datasend(), so it helps if datasend() plays more nicely in this case. In + particular, it was wrong of datasend() to treat upgraded and downgraded + strings differently when their contents were identical at the Perl level.) + + This bugfix results in a breaking change to the case of a "text string" + with characters in U+0080..U+00FF stored internally in an upgraded state + since those characters are likewise no longer encoded to UTF-8 by + datasend(), but callers of datasend() should not have been relying on this + behaviour anyway: In general, datasend() has no idea what encoding is + required for output so callers should always encode the data to be output + to whatever encoding is required first. This has now been clarified in + the documentation. + + Finally, a text string with characters >= U+0100 will now cause a "Wide + character in print" warning from datasend() since such characters cannot + be output as bytes and datasend() no longer encodes to UTF-8. In this + case, UTF-8 bytes will still be output as before since that happens to be + the internal representation of such characters, but the warning is new. + Callers should heed this warning and encode such strings to whatever + encoding is required before calling datasend(), as noted above. + + [Ricardo Signes, CPAN RT#104433] + +3.06 2015-04-01 + + - Fixed INSTALLDIRS to account for the @INC reordering change in Perl 5.12. + See Perl RT#116479 for details. (libnet entered the perl core in Perl + 5.7.2 so that's what the lower bound of the check should strictly be, but + since we only support Perl 5.8.1 and higher anyway it suffices to check + for Perl 5.8. The upper bound is correctly Perl 5.11.0 since the @INC + reordering change in question (Perl core commit #b9ba2fadb1) first + appeared in Perl 5.11.0.) [CPAN RT#103238] + + - Fixed Net::FTP authorize() method, which incorrectly interpreted the + return value of the _RESP() method and falsely reported a failure. [Troy + Loveday, CPAN RT#48532] + + - Added optional SendHello argument to Net::SMTP->new() to allow preventing + the EHLO/HELO command from being automatically sent by the constructor. + [Danil Onishchenko, PR#13] + +3.05 2015-01-12 + + - Fixed infinite loop in Net::SMTP::auth(). [CPAN RT#100235] + +3.04 2014-11-29 + + - SNI is now only used for SSL connections if it is supported by + IO::Socket::SSL (i.e. OpenSSL version >= 1). (The previous release + switched to using SNI by default, which caused some CPAN Testers + failures.) [Steffen Ullrich, PR#10] + +3.03 2014-11-28 + + - Remodelled SSL support in Net::NNTP in the manner of Net::POP3 and + Net::SMTP. [Steffen Ullrich, PR#9] + + - Increased minimum requred IO::Socket::SSL version from 1.999 to 2.007 to + fix data connection problems in Net::FTP. [Steffen Ullrich, CPAN + RT#100529] + + - Fixed a broken port() call in pasv_xfer()/pasv_xfer_unique() in Net::FTP. + [Mario Preksavec, PR#8] + + - Increased minimum required Socket version from 1.3 to 2.016. This may be + required when those modules that can support IPv6 load IO::Socket::IP (on + some OSes, at least). It does not appear to be necessary if they load + IO::Socket::INET6 or IO::Socket::INET instead, but this is not easy for + the end-user to control so it is simpler to always insist on Socket 2.016 + or higher. [CPAN RT#100020] + + - Fixed "Argument ... isn't numeric in subroutine entry" warnings when using + older versions of Perl. [CPAN RT#100020] + + - Added optional Changes testing (skipped unless AUTHOR_TESTING). + + - Reformatted Changes file as per CPAN::Changes::Spec. + +3.02 2014-10-10 + + - Don't run interactive prompt() in Makefile.PL when in PERL_CORE. + + - Fix $smtp->auth($sasl) to try the AUTH mechanism (if present) in the + Authen::SASL object before falling back on other mechanisms. [CPAN + RT#99415] + +3.01 2014-10-09 + + - Require IO::Socket::SSL >= 1.999 to protect against a bad version (0.30) + of IO::Socket::IP and hopefully fix another bunch of CPAN Testers + failures. + +3.00 2014-10-09 + + - Skip Perl Critic, Pod and Pod Coverage tests unless AUTHOR_TESTING. [CPAN + RT#99399] + + - Synchronize all $VERSIONs to the distribution's version number, bumping + that to 3.00 so that no $VERSIONs end up going backwards. + +1.30 2014-10-08 + + - Sigh. Fix PAUSE indexing problem again. Net::SMTP::SSL is already used by + Net-SMTP-SSL. + +1.29 2014-10-08 + + - Fix PAUSE indexing problem. Net::POP3::_SSLified and Net::SMTP::_SSLified + are already used by Net-SSLGlue. + +1.28 2014-10-08 + + - Improve code()/message() initialization and error handling in Net::Cmd. + [Tom Metro, CPAN RT#14875] + + - Don't use the ALLO command on FTP servers that don't support it. [CPAN + RT#95717] + + - Stop Makefile.PL from requiring interactive configuration when running via + cpan, cpanp or cpanm: just accept all defaults in these cases, as when + running non-interactively. [CPAN RT#48966] + + - Add optional POD coverage testing. + + - Add optional POD testing. + + - Add optional Perl::Critic testing. + + - Make code Perl::Critic clean. + + - Move Net/*.pm into lib/Net/ sub-directory within distribution. This is + the usual layout style these days. + + - Change Net::SMTP::auth() so that it now falls back to another supported + AUTH method if a given AUTH method fails. [Ivan Baktsheev, PR#3] + + - Change Net::SMTP::auth() so that it uses the SMTP AUTH mechanism(s) + specified in the Authen::SASL object if one is provided instead of a + username. If a plain text username is specified then use the first + reported SMTP AUTH method supported, as usual. [Ewen McNeill, CPAN + RT#58002] + + - Add support for IPv6 and SSL to Net::FTP, Net::NNTP, Net::POP3 and + Net::SMTP. These features are only available if the user has: + + * a recent IO::Socket::SSL for SSL support; + + * a recent IO::Socket::IP or an older IO::Socket::INET6 for IPv6 support. + + If no SSL module is available it will work as before, but attempts to use + the SSL functionality will result in an error message. If no IPv6 modules + are available it will just use IPv4 as before. With IPv6 modules + installed one can of course still access IPv4 hosts. + + [Steffen Ullrich, CPAN RT#93823] + +1.27 2014-05-30 + + - Simplified Makefile.PL requirements. + +1.26 2014-05-30 + + - Set minimum required ExtUtils::MakeMaker version to 6.64 to ensure that + all parameters used are supported, to save jumping through hoops to + support earlier versions. (This should not be a problem since + ExtUtils::MakeMaker 6.64 is easily installed into Perl 5.8.1 and above, + that being the whole point of the new choice of minimum supported Perl + version.) + + - Set minimum required Perl version to 5.8.1. This is in line with the + minimum requirement of the "Perl Toolchain". + +1.25 2014-02-04 + + - Fix Net::FTP::pasv_wait() not handling errors from Net::Cmd::reponse(). + [bergner@cs.umu.se, CPAN RT#50420] + + - Make inheritance from Net::Cmd clearer in the documentation. [CPAN + RT#72889] + + - Set timeout for data connection creation in Net::FTP. [Oleg G, CPAN + RT#78926] + + - Stop Net::Domain::domainname() from giving out warnings in Android. + [Brian Fraser] + +1.24 2014-01-06 + + - Fix incorrect handling of CRLF in Net::FTP. [Willem Monsuw�, CPAN + RT#41642/62029] + + - POD fixes. [Dominic Hargreaves, CPAN RT#91761] + +1.23 2013-08-12 + + - Typo fixes. [David Steinbrunner, CPAN RT#87681] + +1.22_02 2013-08-08 + + - Make Net::FTP::dataconn::close() more robust. [Together with changes to + Net::FTP already made in 1.22_01, this resolves CPAN RT#37700.] + + - Document scalar/list context return values from Net::Cmd::message(). + + - Fix broken URL. [CPAN RT#68749] + + - Fix documentation typo in Net::Netrc. + + - Fix broken POD in Net::POP3. + + - Improve Net::SMTP documentation of new(), auth() and message(). [CPAN + RT#36038] + + - Add proper skips to skipped tests in ftp.t. + + - Import hostname.t fix from Perl core commit #adeb94125a. + + - Add time.t, imported from Perl core commit #c85707204c. + + - Add new maintainer information, with updated CPAN and GitHub links. + +1.22_01 2010-05-31 09:40:25-05:00 + + - Do not create/pass a remote name if one is not given to put_unique. + + - Add ->passive() method to switch between PORT/PASV connections. + + - Accept - in command parsed from SMTP HELO response. + + - Allow group to set to a group named "0". + + - Set $@ when ->new() returns undef. + + - Add support for LocalAddr to be passed to ->new(). + + - Document that timeout is in seconds. + + - Fix leading . encoding in datasend(). + + - Make ->supported() check ->feature(). + + - Allow words other than FILE to prefix the unique name returned in info + message from stou. + + - Send ALLO command just before the store command. + + - Avoid warnings when server do not prefix messages with codes. + + - Use uppercase characters for xtext encoding. + + - Catch timeout condition while doing an abort. + + - Ensure REST is sent directly before command being restarted. + + - Fix URL. [Leon Brocard, CPAN RT#49920] + + - Avoid long hang on Mac OS X when hostname is *.local by not calling + gethostbyname(). [Father Chrysostomos] + + - Avoid infinite recursion in rmdir(). + + - Allow finding _netrc on machines that do not support .netrc. [Ben Bimber] + +1.22 2007-08-26 07:13:18-05:00 + +[Bug Fixes] + + - Fix a bug in Net::Cmd that is_utf8() does not exist prior to Perl 5.8.1. + +1.21 2007-05-19 08:53:09-05:00 + +[Bug Fixes] + + - Fix bug causing utf8 encoding of 8-bit strings in Net::Cmd. + + - Fix precedence issue in Net::NNTP. [Brendan O'Dea] + + - Fixed bug causing removal of last character on the line when doing ASCII + FTP transfers. + +[Enhancements] + + - Add support for ENVID and AUTH to Net::SMTP. [Mark Martinec] + + - Changed default for FTP transfers to be passive. + + - Added support for FTP FEAT command. + +1.20 2007-02-02 19:42:51-06:00 + +[Bug Fixes] + + - Fixed incorrect handling of CRLF that straddled two blocks. + + - Fix bug in response() which was too liberal in what it thought was a + response line. + + - Silence uninitialized value warnings in Net::Cmd during testing on Win32. + + - Documentation typos and updates. + +[Enhancements] + + - Added support for ORCPT into Net::SMTP. + + - Support for servers that expect the USER command in upper or lower case. + Try USER first then try user if that fails. + +1.19 2004-06-30 14:53:48+01:00 + +[Bug Fixes] + + - Fixed datasend() test to work on Win32 platform. + + - Fixed Authen::SASL checking in Net::SMTP and Net::POP3. + + - Fixed bug that a restarted get with Net::FTP did not append to local file. + +1.18 2004-03-22 16:19:01Z + +[Bug Fixes] + + - Fixed bug in CRLF translation in Net::Cmd datasend()/dataend() methods. + + - Fixed bug in converting numbers returned by PASV command into a packed IP + address. + + - Fixed bug that caused Net::FTP->get() to truncate the local file after the + restart method had been called. + + - Fixed bug in Net::FTP->rmdir() when the server returned . and .. in the + contents of a directory. + + - Fixed bug in Net::POP3 that was sending unnecessary RSETs. + +[Enhancements] + + - Added support for POP3 CAPA command. + + - Added support for XVERP to Net::SMTP. + + - Added Net::POP3->banner() method to return the banner received from the + server during connect. + + - Added Net::POP3->auth() method for performing authentication using SASL; + requires Authen::SASL. + + - Added Host option to ->new() constructor of FTP, NNTP, SMTP and POP3, + which can be used instead of passing the host as the first argument. + + - Added ->host() method to FTP, NNTP, SMTP and POP3 to return the host + string used for the connect. This is useful to determine which host was + connected to when multiple hosts are specified. + + - Added support for more non-standard responses to Net::FTP->size(). + + - Updated POD for Net::SMTP with respect to not passing a Hello parameter to + the constructor. [Jeff Macdonald] + +ChangeLogs for releases prior to 1.18 may be found at: +https://github.com/steve-m-hay/perl-libnet/blob/v1.17/ChangeLog diff --git a/Configure b/Configure new file mode 100644 index 0000000..105e151 --- /dev/null +++ b/Configure @@ -0,0 +1,608 @@ +#!perl +# +# $Id: Configure,v 1.8 1997/03/04 09:22:32 gbarr Exp $ + +use 5.008001; + +use strict; +use warnings; + +use ExtUtils::MakeMaker qw(prompt); +use Getopt::Std; +use IO::File; + +## no critic (Subroutines::ProhibitSubroutinePrototypes) + +our($opt_d, $opt_o); + +## +## +## + +my %cfg = (); +my @cfg = (); + +my($libnet_cfg,$msg,$ans,$def,$have_old); + +## +## +## + +sub valid_host +{ + my $h = shift; + + defined($h) && (($cfg{'test_exist'} == 0) || gethostbyname($h)); +} + +## +## +## + +sub test_hostnames (\@) +{ + my $hlist = shift; + my @h = (); + my $err = 0; + + foreach my $host (@$hlist) + { + if(valid_host($host)) + { + push(@h, $host); + next; + } + warn "Bad hostname: '$host'\n"; + $err++; + } + @$hlist = @h; + $err ? join(" ",@h) : undef; +} + +## +## +## + +sub Prompt +{ + my($prompt,$def) = @_; + + $def = "" unless defined $def; + + chomp($prompt); + + if($opt_d) + { + print $prompt,," [",$def,"]\n"; + return $def; + } + prompt($prompt,$def); +} + +## +## +## + +sub get_host_list +{ + my($prompt,$def) = @_; + + $def = join(" ",@$def) if ref($def); + + my @hosts; + + do + { + my $ans = Prompt($prompt,$def); + + $ans =~ s/(\A\s+|\s+\Z)//g; + + @hosts = split(/\s+/, $ans); + } + while(@hosts && defined($def = test_hostnames(@hosts))); + + \@hosts; +} + +## +## +## + +sub get_hostname +{ + my($prompt,$def) = @_; + + my $host; + + while(1) + { + my $ans = Prompt($prompt,$def); + $host = ($ans =~ /(\S*)/)[0]; + last + if(!length($host) || valid_host($host)); + + $def ="" + if $def eq $host; + + print <<"EDQ"; + +*** ERROR: + Hostname `$host' does not seem to exist, please enter again + or a single space to clear any default + +EDQ + } + + length $host + ? $host + : undef; +} + +## +## +## + +sub get_bool ($$) +{ + my($prompt,$def) = @_; + + chomp($prompt); + + my $val = Prompt($prompt,$def ? "yes" : "no"); + + $val =~ /^y/i ? 1 : 0; +} + +## +## +## + +sub get_netmask ($$) +{ + my($prompt,$def) = @_; + + chomp($prompt); + + my %list; + @list{@$def} = (); + +MASK: + while(1) { + my $bad = 0; + my $ans = Prompt($prompt) or last; + + if($ans eq '*') { + %list = (); + next; + } + + if($ans eq '=') { + print "\n",( %list ? join("\n", sort keys %list) : 'none'),"\n\n"; + next; + } + + unless ($ans =~ m{^\s*(?:(-?\s*)(\d+(?:\.\d+){0,3})/(\d+))}) { + warn "Bad netmask '$ans'\n"; + next; + } + + my($remove,$bits,@ip) = ($1,$3,split(/\./, $2),0,0,0); + if ( $ip[0] < 1 || $bits < 1 || $bits > 32) { + warn "Bad netmask '$ans'\n"; + next MASK; + } + foreach my $byte (@ip) { + if ( $byte > 255 ) { + warn "Bad netmask '$ans'\n"; + next MASK; + } + } + + my $mask = sprintf("%d.%d.%d.%d/%d",@ip[0..3],$bits); + + if ($remove) { + delete $list{$mask}; + } + else { + $list{$mask} = 1; + } + + } + + [ keys %list ]; +} + +## +## +## + +sub default_hostname +{ + my @host; + + foreach my $host (@_) + { + if(defined($host) && valid_host($host)) + { + return $host + unless wantarray; + push(@host,$host); + } + } + + return wantarray ? @host : undef; +} + +## +## +## + +getopts('do:'); + +$libnet_cfg = "libnet.cfg" + unless(defined($libnet_cfg = $opt_o)); + +my %oldcfg = (); + +{ +no warnings 'once'; +$Net::Config::CONFIGURE = 1; # Suppress load of user overrides +} +if( -f $libnet_cfg ) + { + %oldcfg = ( %{ do $libnet_cfg } ); + } +elsif (eval { require Net::Config }) + { + $have_old = 1; + no warnings 'once'; + %oldcfg = %Net::Config::NetConfig; + } + +map { $cfg{lc $_} = $cfg{$_}; delete $cfg{$_} if /[A-Z]/ } keys %cfg; + +$oldcfg{'test_exist'} = 1 unless exists $oldcfg{'test_exist'}; +$oldcfg{'test_hosts'} = 1 unless exists $oldcfg{'test_hosts'}; + +#--------------------------------------------------------------------------- + +if($have_old && !$opt_d) + { + $msg = <. To accept the +default, hit + +EDQ + +$msg = 'Enter a list of available NNTP hosts :'; + +$def = $oldcfg{'nntp_hosts'} || + [ default_hostname($ENV{NNTPSERVER},$ENV{NEWSHOST},'news') ]; + +$cfg{'nntp_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available SMTP hosts :'; + +$def = $oldcfg{'smtp_hosts'} || + [ default_hostname(split(/:/,$ENV{SMTPHOSTS} || ""), 'mailhost') ]; + +$cfg{'smtp_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available POP3 hosts :'; + +$def = $oldcfg{'pop3_hosts'} || []; + +$cfg{'pop3_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available SNPP hosts :'; + +$def = $oldcfg{'snpp_hosts'} || []; + +$cfg{'snpp_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available PH Hosts :' ; + +$def = $oldcfg{'ph_hosts'} || + [ default_hostname('dirserv') ]; + +$cfg{'ph_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available TIME Hosts :' ; + +$def = $oldcfg{'time_hosts'} || []; + +$cfg{'time_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = 'Enter a list of available DAYTIME Hosts :' ; + +$def = $oldcfg{'daytime_hosts'} || $oldcfg{'time_hosts'}; + +$cfg{'daytime_hosts'} = get_host_list($msg,$def); + +#--------------------------------------------------------------------------- + +$msg = < external user & password +fwuser/fwpass => firewall user & password + +0) None +1) ----------------------- + USER user@remote.host + PASS pass +2) ----------------------- + USER fwuser + PASS fwpass + USER user@remote.host + PASS pass +3) ----------------------- + USER fwuser + PASS fwpass + SITE remote.site + USER user + PASS pass +4) ----------------------- + USER fwuser + PASS fwpass + OPEN remote.site + USER user + PASS pass +5) ----------------------- + USER user@fwuser@remote.site + PASS pass@fwpass +6) ----------------------- + USER fwuser@remote.site + PASS fwpass + USER user + PASS pass +7) ----------------------- + USER user@remote.host + PASS pass + AUTH fwuser + RESP fwpass + +Choice: +EDQ + $def = exists $oldcfg{'ftp_firewall_type'} ? $oldcfg{'ftp_firewall_type'} : 1; + $ans = Prompt($msg,$def); + $cfg{'ftp_firewall_type'} = 0+$ans; + $def = $oldcfg{'ftp_firewall'} || $ENV{FTP_FIREWALL}; + + $cfg{'ftp_firewall'} = get_hostname("FTP proxy hostname :", $def); +} +else { + delete $cfg{'ftp_firewall'}; +} + + +#--------------------------------------------------------------------------- + +if (defined $cfg{'ftp_firewall'}) + { + print <new($libnet_cfg, "w") or + die "Cannot create `$libnet_cfg': $!"; + +print "Writing $libnet_cfg\n"; + +print $fh "{\n"; + +foreach my $key (keys %cfg) { + my $val = $cfg{$key}; + if(!defined($val)) { + $val = "undef"; + } + elsif(ref($val)) { + $val = '[' . join(",", + map { + my $v = "undef"; + if(defined $_) { + ($v = $_) =~ s/'/\'/sog; + $v = "'" . $v . "'"; + } + $v; + } @$val ) . ']'; + } + else { + $val =~ s/'/\'/sog; + $val = "'" . $val . "'" if $val =~ /\D/; + } + print $fh "\t'",$key,"' => ",$val,",\n"; +} + +print $fh "}\n"; + +$fh->close; + +############################################################################ +############################################################################ + +exit 0; diff --git a/Copying b/Copying new file mode 100644 index 0000000..8de98af --- /dev/null +++ b/Copying @@ -0,0 +1,251 @@ + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..a7a6232 --- /dev/null +++ b/INSTALL @@ -0,0 +1,42 @@ +PREREQUISITES + + Perl + + Perl version 5.8.1 or later. + The latest version of Perl is available from http://www.perl.com/. + + Perl Modules + + There are no non-standard Perl modules required by this module. + +INSTALLATION + + To install this module, cd to the directory that contains this INSTALL file + and type the following: + + perl Makefile.PL + make + make test + make install + + Normally when Makefile.PL is run it will run Configure which will ask some + questions about your system. The results of these questions will be stored + in a file called libnet.cfg which will be installed alongside the other perl + modules in this distribution. Makefile.PL will run Configure in an + interactive mode unless these exists a file called libnet.cfg in the build + directory or Makefile.PL itself is being run non-interactively or via cpan, + cpanp or cpanm. + + If you are on a system which cannot run this script you can create an empty + file to make Makefile.PL skip running Configure. If you want to keep your + existing settings and not run interactivly then simply run: + + perl Configure -d + + before running Makefile.PL. + + Use the appropriate program name instead of "make" in the above commands if + your perl was built with a different make program. To determine which make + program was used to build your perl type the following: + + perl -V:make diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..cafb5b6 --- /dev/null +++ b/LICENCE @@ -0,0 +1,23 @@ +This distribution is free software; you can redistribute it and/or modify it +under the terms of either: + +a) the GNU General Public License as published by the Free Software Foundation; + either version 1, or (at your option) any later version; or + +b) the "Artistic License" which comes with this distribution. + +This distribution is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +Artistic License for more details. + +You should have received a copy of the GNU General Public License along with +this distribution in the file named "Copying". If not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA or visit their web page on the internet at +http://www.gnu.org/copyleft/gpl.html or the Perl web page at +http://dev.perl.org/licenses/gpl1.html. + +You should also have received a copy of the Artistic License with this +distribution, in the file named "Artistic". If not, visit the Perl web page on +the internet at http://dev.perl.org/licenses/artistic.html. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..4cf2bd1 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,56 @@ +Artistic The "Artistic License" +Changes Differences from previous version +Configure Configuration script +Copying The GNU General Public License +demos/ftp Demo script for Net::FTP +demos/nntp Demo script for Net::NNTP +demos/nntp.mirror Demo script for Net::NNTP +demos/pop3 Demo script for Net::POP3 +demos/smtp.self Demo script for Net::SMTP +demos/time Demo script for Net::Time +INSTALL Detailed installation instructions +lib/Net/Cmd.pm Net::Cmd Perl module +lib/Net/Config.pm Net::Config Perl module +lib/Net/Domain.pm Net::Domain Perl module +lib/Net/FTP.pm Net::FTP Perl module +lib/Net/FTP/A.pm Net::FTP::A Perl module +lib/Net/FTP/dataconn.pm Net::FTP::dataconn Perl module +lib/Net/FTP/E.pm Net::FTP::E Perl module +lib/Net/FTP/I.pm Net::FTP::I Perl module +lib/Net/FTP/L.pm Net::FTP::L Perl module +lib/Net/libnetFAQ.pod Frequently Asked Questions +lib/Net/Netrc.pm Net::Netrc Perl module +lib/Net/NNTP.pm Net::NNTP Perl module +lib/Net/POP3.pm Net::POP3 Perl module +lib/Net/SMTP.pm Net::SMTP Perl module +lib/Net/Time.pm Net::Time Perl module +LICENCE The Licence +Makefile.PL Makefile writer +MANIFEST This list of files +MANIFEST.SKIP Manifest skip specs +README The Instructions +t/changes.t See if Changes file format is OK +t/config.t Test script +t/critic.t See if coding style is OK +t/datasend.t Test script +t/external/ftp-ssl.t Test script +t/external/pop3-ssl.t Test script +t/external/smtp-ssl.t Test script +t/ftp.t Test script +t/hostname.t Test script +t/libnet_t.pl Test script +t/netrc.t Test script +t/nntp.t Test script +t/nntp_ipv6.t Test script +t/nntp_ssl.t Test script +t/pod.t See if POD is OK +t/pod_coverage.t See if POD coverage is OK +t/pop3_ipv6.t Test script +t/pop3_ssl.t Test script +t/require.t Test script +t/smtp.t Test script +t/smtp_ipv6.t Test script +t/smtp_ssl.t Test script +t/time.t Test script +META.yml Module YAML meta-data (added by MakeMaker) +META.json Module JSON meta-data (added by MakeMaker) diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 0000000..4b53248 --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,19 @@ +# Source control system files +^\.git/ +^\.gitignore$ + +# Files generated by Makefile.PL +^libnet.cfg$ +^Makefile$ +^MYMETA\.json$ +^MYMETA\.yml$ + +# Files generated by *make +^blib/ +^pm_to_blib$ + +# Files generated by *make clean +^Makefile\.old$ + +# Files generated by *make dist +^libnet-\d\.\d\d\.tar\.gz$ diff --git a/META.json b/META.json new file mode 100644 index 0000000..c44cee8 --- /dev/null +++ b/META.json @@ -0,0 +1,158 @@ +{ + "abstract" : "Collection of network protocol modules", + "author" : [ + "Graham Barr , Steve Hay " + ], + "dynamic_config" : 1, + "generated_by" : "ExtUtils::MakeMaker version 7.04, CPAN::Meta::Converter version 2.150001", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "libnet", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "optional_features" : { + "APOP" : { + "description" : "APOP support", + "prereqs" : { + "runtime" : { + "requires" : { + "Digest::MD5" : "0" + } + } + } + }, + "AUTH" : { + "description" : "AUTH support", + "prereqs" : { + "runtime" : { + "requires" : { + "Authen::SASL" : "0", + "MIME::Base64" : "0" + } + } + } + }, + "IPv6" : { + "description" : "IPv6 support", + "prereqs" : { + "runtime" : { + "requires" : { + "IO::Socket::IP" : "0.25" + } + } + } + }, + "SSL" : { + "description" : "SSL support", + "prereqs" : { + "runtime" : { + "requires" : { + "IO::Socket::SSL" : "2.007" + } + } + } + }, + "changestest" : { + "description" : "Changes testing", + "prereqs" : { + "test" : { + "requires" : { + "Test::CPAN::Changes" : "0" + } + } + } + }, + "critictest" : { + "description" : "Perl::Critic testing", + "prereqs" : { + "test" : { + "requires" : { + "Test::Perl::Critic" : "0" + } + } + } + }, + "podcoveragetest" : { + "description" : "POD coverage testing", + "prereqs" : { + "test" : { + "requires" : { + "Test::Pod::Coverage" : "0.08" + } + } + } + }, + "podtest" : { + "description" : "POD testing", + "prereqs" : { + "test" : { + "requires" : { + "Test::Pod" : "1.00" + } + } + } + } + }, + "prereqs" : { + "build" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "6.64", + "Getopt::Std" : "0", + "IO::File" : "0", + "perl" : "5.008001", + "strict" : "0", + "vars" : "0", + "warnings" : "0" + } + }, + "runtime" : { + "requires" : { + "Carp" : "0", + "Errno" : "0", + "Exporter" : "0", + "Fcntl" : "0", + "File::Basename" : "0", + "FileHandle" : "0", + "IO::Select" : "0", + "IO::Socket" : "1.05", + "POSIX" : "0", + "Socket" : "2.016", + "Symbol" : "0", + "Time::Local" : "0", + "constant" : "0", + "perl" : "5.008001", + "strict" : "0", + "utf8" : "0", + "vars" : "0" + } + }, + "test" : { + "requires" : { + "Config" : "0", + "Cwd" : "0" + } + } + }, + "release_status" : "stable", + "resources" : { + "repository" : { + "type" : "git", + "url" : "https://github.com/steve-m-hay/perl-libnet.git" + } + }, + "version" : "3.11" +} diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..20a7111 --- /dev/null +++ b/META.yml @@ -0,0 +1,74 @@ +--- +abstract: 'Collection of network protocol modules' +author: + - 'Graham Barr , Steve Hay ' +build_requires: + Config: '0' + Cwd: '0' + ExtUtils::MakeMaker: '0' +configure_requires: + ExtUtils::MakeMaker: '6.64' + Getopt::Std: '0' + IO::File: '0' + perl: '5.008001' + strict: '0' + vars: '0' + warnings: '0' +dynamic_config: 1 +generated_by: 'ExtUtils::MakeMaker version 7.04, CPAN::Meta::Converter version 2.150001' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: libnet +no_index: + directory: + - t + - inc +optional_features: + APOP: + description: 'APOP support' + requires: + Digest::MD5: '0' + AUTH: + description: 'AUTH support' + requires: + Authen::SASL: '0' + MIME::Base64: '0' + IPv6: + description: 'IPv6 support' + requires: + IO::Socket::IP: '0.25' + SSL: + description: 'SSL support' + requires: + IO::Socket::SSL: '2.007' + changestest: + description: 'Changes testing' + critictest: + description: 'Perl::Critic testing' + podcoveragetest: + description: 'POD coverage testing' + podtest: + description: 'POD testing' +requires: + Carp: '0' + Errno: '0' + Exporter: '0' + Fcntl: '0' + File::Basename: '0' + FileHandle: '0' + IO::Select: '0' + IO::Socket: '1.05' + POSIX: '0' + Socket: '2.016' + Symbol: '0' + Time::Local: '0' + constant: '0' + perl: '5.008001' + strict: '0' + utf8: '0' + vars: '0' +resources: + repository: https://github.com/steve-m-hay/perl-libnet.git +version: '3.11' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..73be0a1 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,263 @@ +#!perl +#=============================================================================== +# +# Makefile.PL +# +# DESCRIPTION +# Makefile creation script. +# +# COPYRIGHT +# Copyright (C) 2014, 2015 Steve Hay. All rights reserved. +# +# LICENCE +# This script is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU +# General Public License or the Artistic License, as specified in the LICENCE +# file. +# +#=============================================================================== + +use 5.008001; + +use strict; +use warnings; + +use ExtUtils::MakeMaker 6.64; +use ExtUtils::MakeMaker qw(WriteMakefile); + +## no critic (Subroutines::ProhibitSubroutinePrototypes) + +sub running_under_cpan(); + +#=============================================================================== +# INITIALIZATION +#=============================================================================== + +our($CfgFile, $CfgPath); + +BEGIN { + $CfgFile = 'libnet.cfg'; + $CfgPath = "Net/$CfgFile"; +} + +#=============================================================================== +# MAIN PROGRAM +#=============================================================================== + +MAIN: { + my %prereq_pms = (); + $prereq_pms{'Convert::EBCDIC'} = '0.06' if $^O eq 'os390'; + + my $xt = 'n'; + if (not running_under_cpan() and not $ENV{PERL_CORE}) { + $xt = prompt("Should I do external tests?\n" . + "These tests will fail if there is no internet" . + " connection or if a firewall\n" . + "blocks or modifies some traffic.\n" . + "[y/N]", 'n'); + } + + my $tests = 't/*.t'; + $tests .= ' t/external/*.t' if $xt =~ m/^y/io; + + WriteMakefile( + NAME => 'Net', + DISTNAME => 'libnet', + ABSTRACT => 'Collection of network protocol modules', + AUTHOR => 'Graham Barr , Steve Hay ', + LICENSE => 'perl_5', + VERSION => '3.11', + + META_MERGE => { + 'meta-spec' => { + version => 2 + }, + + resources => { + repository => { + type => 'git', + url => 'https://github.com/steve-m-hay/perl-libnet.git' + } + }, + + optional_features => { + APOP => { + description => 'APOP support', + prereqs => { + runtime => { + requires => { + 'Digest::MD5' => '0' + } + } + } + }, + + AUTH => { + description => 'AUTH support', + prereqs => { + runtime => { + requires => { + 'Authen::SASL' => '0', + 'MIME::Base64' => '0' + } + } + } + }, + + SSL => { + description => 'SSL support', + prereqs => { + runtime => { + requires => { + 'IO::Socket::SSL' => '2.007' + } + } + } + }, + + IPv6 => { + description => 'IPv6 support', + prereqs => { + runtime => { + requires => { + 'IO::Socket::IP' => '0.25' + # or IO::Socket::INET6 2.62 + } + } + } + }, + + changestest => { + description => 'Changes testing', + prereqs => { + test => { + requires => { + 'Test::CPAN::Changes' => '0' + } + } + } + }, + + critictest => { + description => 'Perl::Critic testing', + prereqs => { + test => { + requires => { + 'Test::Perl::Critic' => '0' + } + } + } + }, + + podtest => { + description => 'POD testing', + prereqs => { + test => { + requires => { + 'Test::Pod' => '1.00' + } + } + } + }, + + podcoveragetest => { + description => 'POD coverage testing', + prereqs => { + test => { + requires => { + 'Test::Pod::Coverage' => '0.08' + } + } + } + } + } + }, + + MIN_PERL_VERSION => '5.008001', + + CONFIGURE_REQUIRES => { + 'ExtUtils::MakeMaker' => '6.64', + 'Getopt::Std' => '0', + 'IO::File' => '0', + 'perl' => '5.008001', + 'strict' => '0', + 'vars' => '0', + 'warnings' => '0' + }, + + TEST_REQUIRES => { + 'Config' => '0', + 'Cwd' => '0' + }, + + PREREQ_PM => { + %prereq_pms, + 'Carp' => '0', + 'Errno' => '0', + 'Exporter' => '0', + 'Fcntl' => '0', + 'File::Basename' => '0', + 'FileHandle' => '0', + 'IO::Select' => '0', + 'IO::Socket' => '1.05', + 'POSIX' => '0', + 'Socket' => '2.016', + 'Symbol' => '0', + 'Time::Local' => '0', + 'constant' => '0', + 'strict' => '0', + 'utf8' => '0', + 'vars' => '0' + }, + + INSTALLDIRS => ($] < 5.011 ? 'perl' : 'site'), + + realclean => { + FILES => $CfgFile + }, + + test => { + TESTS => $tests + }, + + dist => { + PREOP => 'find $(DISTVNAME) -type d -print|xargs chmod 0755 && ' . + 'find $(DISTVNAME) -type f -print|xargs chmod 0644', + TO_UNIX => 'find $(DISTVNAME) -type f -print|xargs dos2unix' + } + ); +} + +#=============================================================================== +# MAKEMAKER OVERRIDES +#=============================================================================== + +sub MY::post_initialize { + my $self = shift; + + return '' if $self->{PERL_CORE}; + + if (not -f $CfgFile) { + my @args = qw(Configure); + push @args, '-d' if $ENV{PERL5_CPAN_IS_RUNNING} || + $ENV{PERL5_CPANPLUS_IS_RUNNING} || + $ENV{PERL5_CPANM_IS_RUNNING}; + system(($^O eq 'VMS' ? 'mcr ': ()), $^X, @args) + } + + $self->{PM}{$CfgFile} = $self->catfile('$(INST_LIBDIR)',$CfgPath); + + return ''; +} + +#=============================================================================== +# SUBROUTINES +#=============================================================================== + +sub running_under_cpan() { + return $ENV{PERL5_CPAN_IS_RUNNING} || # cpan + $ENV{PERL5_CPANPLUS_IS_RUNNING} || # cpanp + $ENV{PERL5_CPANM_IS_RUNNING}; # cpanm +} + +#=============================================================================== diff --git a/README b/README new file mode 100644 index 0000000..b4a4201 --- /dev/null +++ b/README @@ -0,0 +1,80 @@ +libnet is a collection of Perl modules which provides a simple +and consistent programming interface (API) to the client side +of various protocols used in the internet community. + +For details of each protocol please refer to the RFC. RFCs +can be found in various places on the web, for a starting +point look at: + + http://www.rfc-editor.org/ + +The RFCs implemented in this distribution are + +Net::FTP RFC959 File Transfer Protocol +Net::SMTP RFC821 Simple Mail Transfer Protocol +Net::Time RFC867 Daytime Protocol +Net::Time RFC868 Time Protocol +Net::NNTP RFC977 Network News Transfer Protocol +Net::POP3 RFC1939 Post Office Protocol 3 + +AVAILABILITY + +The latest version of libnet is available from the Comprehensive Perl +Archive Network (CPAN). To find a CPAN site near you see: + + http://search.cpan.org/dist/libnet/ + +The GitHub source repository can be browsed at + + https://github.com/steve-m-hay/perl-libnet + +If you have a Git client, then you can checkout the latest code with + + git clone https://github.com/steve-m-hay/perl-libnet.git + +DOCUMENTATION + +See Changes for recent changes. POD style documentation is included +in all modules and scripts. These are normally converted to manual +pages and installed as part of the installation process. You should +also be able to use the 'perldoc' utility to extract documentation from +the module files directly. + +DEMOS + +The demos directory does contain a few demo scripts. These should be +run from the top directory like + + demos/smtp.self -user my-email-address -debug + +However I do not guarantee these scripts to work. + +SUPPORT + +Questions about how to use this library should be directed to the +comp.lang.perl.modules USENET Newsgroup. Bug reports and suggestions +for improvements can be reported on the CPAN Request Tracker at + + https://rt.cpan.org/Public/Bug/Report.html?Queue=libnet + +Most of the modules in this library have an option to output a debug +transcript to STDERR. When reporting bugs/problems please, if possible, +include a transcript of a run. + +INSTALLATION + +See the INSTALL file. + +COPYRIGHT + +Copyright (C) 1996-2007 Graham Barr. All rights reserved. +Copyright (C) 2013-2017 Steve Hay. All rights reserved. + +LICENCE + +This distribution is free software; you can redistribute it and/or modify it +under the same terms as Perl itself, i.e. under the terms of either the GNU +General Public License or the Artistic License, as specified in the LICENCE +file. + +Share and Enjoy! diff --git a/demos/ftp b/demos/ftp new file mode 100644 index 0000000..aff60aa --- /dev/null +++ b/demos/ftp @@ -0,0 +1,26 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use blib; +use Getopt::Long; +use Net::FTP; + +our $opt_debug = undef; +our $opt_firewall = undef; + +GetOptions(qw(debug firewall=s)); + +my @firewall = defined $opt_firewall ? (Firewall => $opt_firewall) : (); + +foreach my $host (@ARGV) + { + my $ftp = Net::FTP->new($host, @firewall, Debug => $opt_debug ? 1 : 0); + $ftp->login(); + print $ftp->pwd,"\n"; + $ftp->quit; + } + diff --git a/demos/nntp b/demos/nntp new file mode 100644 index 0000000..6f75f82 --- /dev/null +++ b/demos/nntp @@ -0,0 +1,46 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use blib; +use Getopt::Long; +use Net::NNTP; + +our $opt_debug = undef; + +GetOptions(qw(debug)); + +my @groups = @ARGV; + +my $nntp = Net::NNTP->new('news', Debug => $opt_debug ? 1 : 0); + +my $subs; +if($subs = $nntp->newsgroups) + { + print join("\n",(keys %$subs)[0 .. 10]),"\n"; + } + else + { + warn $nntp->message; + } + +foreach my $group (@groups) + { + my $news = $nntp->newnews(time - 3600, lc $group); + + if(ref($news) && scalar(@$news)) + { + print @{$news}[0..3],"\n" + if $news = $nntp->article($news->[-1]); + + warn $nntp->message + unless $news; + } + } + +$nntp->quit; + + diff --git a/demos/nntp.mirror b/demos/nntp.mirror new file mode 100644 index 0000000..6e80903 --- /dev/null +++ b/demos/nntp.mirror @@ -0,0 +1,97 @@ +#!perl + +### Subject: Re: Fuller example of Net::NNTP? +### Date: Tue, 4 Feb 1997 10:37:58 -0800 +### From: "Paul E. Hoffman" +### To: Graham Barr +### +### Thanks for your reply. After looking at the examples, I realized that +### you're not doing what I want, which is to store the messages on the local +### hard disk with the same message number as what was on the remote. So, I +### rolled my own program, although I haven't finished it yet (I have a hook +### for expiring, but haven't done it yet). +### +### You are welcome to use this in the Net:: distribution if you think it is +### useful. +### +### NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE +### +### This script is included as-is, I give no guarantee that it will +### work on every system +### + +use 5.008001; + +use strict; +use warnings; + +use Net::NNTP; + +my $BaseDir = '/usr/usenet'; +chdir($BaseDir) or die "Could not cd to $BaseDir\n"; + +# Format of grouplist is: +# groupnameexpirationdays +# expirationdays is the number of days to leave the articles around; +# set it to 0 if you want the articles to stay forever +# If the groupname starts with a #, it is skipped +my $GroupList; +open($GroupList, '<', 'grouplist.txt') or die "Could not open grouplist.txt\n"; +my @Groups; +while(<$GroupList>) { + my $Line = $_; chomp($Line); + if($Line eq '') { next }; # Skip blank lines + if(substr($Line, 0, 1) eq '#') { next }; # Skip comments + push(@Groups, $Line) +} +close $GroupList; + +my $NntpPtr = Net::NNTP->new('news.server.com'); + +foreach my $GroupLine (@Groups) { + my($GroupName, $GroupExp) = split(/\s/, $GroupLine, 2); + # Process the expiration first (still to be done...) + + # See if this is a new group + unless(-e "$BaseDir/$GroupName") { + unless(mkdir("$BaseDir/$GroupName", 0755)) + { die "Could not make $BaseDir/$GroupName\n" } + } + chdir("$BaseDir/$GroupName") or die "Couldn't chdir to $GroupName\n"; + # Find the last article in the directory + my @AllInDir = glob('*'); my @RevSortedAllInDir = reverse(sort(@AllInDir)); + my $LenArr = @RevSortedAllInDir; + my $NumLastInDir; + if($LenArr > 0) { $NumLastInDir = $RevSortedAllInDir[0] } + else { $NumLastInDir = 0 } + my($NumArt, $NumFirst, $NumLast, $XGroupName) = + $NntpPtr->group($GroupName); + + if($NumLast == $NumLastInDir) { next } # No new articles + if($NumLast < $NumLastInDir) + { die "In $GroupName, the last number was $NumLast, but the " . + " last number in the directory was $NumLastInDir\n" } + # Figure out which article to start from + my $GetArtNum; + if($NumLastInDir == 0) { $GetArtNum = $NumFirst } + else { $GetArtNum = $NumLastInDir + 1 } + + # Now read each of the new articles + while(1) { # Loop until "last" is called + my $ArtRef = $NntpPtr->article($GetArtNum); + my @ArtArr = @$ArtRef; my $ArtArrLen = @ArtArr; + if($ArtArrLen > 0 ) { # Skip article numbers that had 0 len + my $Out; + open($Out, '>', $GetArtNum) or + die "Could not create $GroupName/$GetArtNum\n"; + print $Out @$ArtRef; close($Out); + } + + # Check if we're at the end + if($GetArtNum == $NumLast) { last } + $GetArtNum += 1; # Increment the article number to get + } +} + +$NntpPtr->quit; +exit; diff --git a/demos/pop3 b/demos/pop3 new file mode 100644 index 0000000..53a626a --- /dev/null +++ b/demos/pop3 @@ -0,0 +1,29 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use blib; +use Getopt::Long; +use Net::POP3; + +our $opt_debug = 0; +our $opt_user = undef; + +GetOptions(qw(debug user=s)); + +my $pop = Net::POP3->new('backup3', Debug => $opt_debug ? 6 : 0); + +my $user = $opt_user || $ENV{USER} || $ENV{LOGNAME}; + +my $count = $pop->login($user); + +if($count) + { + my $m = $pop->get(1); + print @$m if $m; + } + +$pop->quit; diff --git a/demos/smtp.self b/demos/smtp.self new file mode 100644 index 0000000..e34d585 --- /dev/null +++ b/demos/smtp.self @@ -0,0 +1,85 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use blib; +use Getopt::Long; +use Net::SMTP; + +=head1 NAME + + smtp.self - mail a message via smtp + +=head1 DESCRIPTION + +C will attempt to send a message to a given user + +=head1 OPTIONS + +=over 4 + +=item -debug + +Enabe the output of dubug information + +=item -help + +Display this help text and quit + +=item -user USERNAME + +Send the message to C + +=back + +=head1 EXAMPLE + + demos/smtp.self -user foo.bar + + demos/smtp.self -debug -user Graham.Barr + +=cut + +our $opt_debug = undef; +our $opt_user = undef; +our $opt_help = undef; +GetOptions(qw(debug user=s help)); +exec("pod2text $0") + if defined $opt_help; + +Net::SMTP->debug(1) if $opt_debug; + +my $smtp = Net::SMTP->new("mailhost"); + +my $user = $opt_user || $ENV{USER} || $ENV{LOGNAME}; + +$smtp->mail($user) && $smtp->to($user); +$smtp->reset; + +if($smtp->mail($user) && $smtp->to($user)) + { + $smtp->data(); + + my @data; + map { s/-USER-/$user/g } @data=; ## no critic (ControlStructures::ProhibitMutatingListFunctions) + + $smtp->datasend(@data); + $smtp->dataend; + } +else + { + warn $smtp->message; + } + +$smtp->quit; + +__DATA__ +To: <-USER-> +Subject: A test message + +The message was sent directly via SMTP using Net::SMTP +. +The message was sent directly via SMTP using Net::SMTP diff --git a/demos/time b/demos/time new file mode 100644 index 0000000..d87bafd --- /dev/null +++ b/demos/time @@ -0,0 +1,18 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use blib; +use Net::Time qw(inet_time inet_daytime); + +print inet_daytime('localhost'); +print inet_daytime('localhost','tcp'); +print inet_daytime('localhost','udp'); + +print inet_time('localhost'),"\n"; +print inet_time('localhost','tcp'),"\n"; +print inet_time('localhost','udp'),"\n"; + diff --git a/lib/Net/Cmd.pm b/lib/Net/Cmd.pm new file mode 100644 index 0000000..b695f64 --- /dev/null +++ b/lib/Net/Cmd.pm @@ -0,0 +1,873 @@ +# Net::Cmd.pm +# +# Copyright (C) 1995-2006 Graham Barr. All rights reserved. +# Copyright (C) 2013-2016 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::Cmd; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Exporter; +use Symbol 'gensym'; +use Errno 'EINTR'; + +BEGIN { + if ($^O eq 'os390') { + require Convert::EBCDIC; + + # Convert::EBCDIC->import; + } +} + +our $VERSION = "3.11"; +our @ISA = qw(Exporter); +our @EXPORT = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING); + +use constant CMD_INFO => 1; +use constant CMD_OK => 2; +use constant CMD_MORE => 3; +use constant CMD_REJECT => 4; +use constant CMD_ERROR => 5; +use constant CMD_PENDING => 0; + +use constant DEF_REPLY_CODE => 421; + +my %debug = (); + +my $tr = $^O eq 'os390' ? Convert::EBCDIC->new() : undef; + +sub toebcdic { + my $cmd = shift; + + unless (exists ${*$cmd}{'net_cmd_asciipeer'}) { + my $string = $_[0]; + my $ebcdicstr = $tr->toebcdic($string); + ${*$cmd}{'net_cmd_asciipeer'} = $string !~ /^\d+/ && $ebcdicstr =~ /^\d+/; + } + + ${*$cmd}{'net_cmd_asciipeer'} + ? $tr->toebcdic($_[0]) + : $_[0]; +} + + +sub toascii { + my $cmd = shift; + ${*$cmd}{'net_cmd_asciipeer'} + ? $tr->toascii($_[0]) + : $_[0]; +} + + +sub _print_isa { + no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict) + + my $pkg = shift; + my $cmd = $pkg; + + $debug{$pkg} ||= 0; + + my %done = (); + my @do = ($pkg); + my %spc = ($pkg, ""); + + while ($pkg = shift @do) { + next if defined $done{$pkg}; + + $done{$pkg} = 1; + + my $v = + defined ${"${pkg}::VERSION"} + ? "(" . ${"${pkg}::VERSION"} . ")" + : ""; + + my $spc = $spc{$pkg}; + $cmd->debug_print(1, "${spc}${pkg}${v}\n"); + + if (@{"${pkg}::ISA"}) { + @spc{@{"${pkg}::ISA"}} = (" " . $spc{$pkg}) x @{"${pkg}::ISA"}; + unshift(@do, @{"${pkg}::ISA"}); + } + } +} + + +sub debug { + @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([LEVEL])'; + + my ($cmd, $level) = @_; + my $pkg = ref($cmd) || $cmd; + my $oldval = 0; + + if (ref($cmd)) { + $oldval = ${*$cmd}{'net_cmd_debug'} || 0; + } + else { + $oldval = $debug{$pkg} || 0; + } + + return $oldval + unless @_ == 2; + + $level = $debug{$pkg} || 0 + unless defined $level; + + _print_isa($pkg) + if ($level && !exists $debug{$pkg}); + + if (ref($cmd)) { + ${*$cmd}{'net_cmd_debug'} = $level; + } + else { + $debug{$pkg} = $level; + } + + $oldval; +} + + +sub message { + @_ == 1 or croak 'usage: $obj->message()'; + + my $cmd = shift; + + wantarray + ? @{${*$cmd}{'net_cmd_resp'}} + : join("", @{${*$cmd}{'net_cmd_resp'}}); +} + + +sub debug_text { $_[2] } + + +sub debug_print { + my ($cmd, $out, $text) = @_; + print STDERR $cmd, ($out ? '>>> ' : '<<< '), $cmd->debug_text($out, $text); +} + + +sub code { + @_ == 1 or croak 'usage: $obj->code()'; + + my $cmd = shift; + + ${*$cmd}{'net_cmd_code'} = $cmd->DEF_REPLY_CODE + unless exists ${*$cmd}{'net_cmd_code'}; + + ${*$cmd}{'net_cmd_code'}; +} + + +sub status { + @_ == 1 or croak 'usage: $obj->status()'; + + my $cmd = shift; + + substr(${*$cmd}{'net_cmd_code'}, 0, 1); +} + + +sub set_status { + @_ == 3 or croak 'usage: $obj->set_status(CODE, MESSAGE)'; + + my $cmd = shift; + my ($code, $resp) = @_; + + $resp = defined $resp ? [$resp] : [] + unless ref($resp); + + (${*$cmd}{'net_cmd_code'}, ${*$cmd}{'net_cmd_resp'}) = ($code, $resp); + + 1; +} + +sub _syswrite_with_timeout { + my $cmd = shift; + my $line = shift; + + my $len = length($line); + my $offset = 0; + my $win = ""; + vec($win, fileno($cmd), 1) = 1; + my $timeout = $cmd->timeout || undef; + my $initial = time; + my $pending = $timeout; + + local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS'; + + while ($len) { + my $wout; + my $nfound = select(undef, $wout = $win, undef, $pending); + if ((defined $nfound and $nfound > 0) or -f $cmd) # -f for testing on win32 + { + my $w = syswrite($cmd, $line, $len, $offset); + if (! defined($w) ) { + my $err = $!; + $cmd->close; + $cmd->_set_status_closed($err); + return; + } + $len -= $w; + $offset += $w; + } + elsif ($nfound == -1) { + if ( $! == EINTR ) { + if ( defined($timeout) ) { + redo if ($pending = $timeout - ( time - $initial ) ) > 0; + $cmd->_set_status_timeout; + return; + } + redo; + } + my $err = $!; + $cmd->close; + $cmd->_set_status_closed($err); + return; + } + else { + $cmd->_set_status_timeout; + return; + } + } + + return 1; +} + +sub _set_status_timeout { + my $cmd = shift; + my $pkg = ref($cmd) || $cmd; + + $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Timeout"); + carp(ref($cmd) . ": " . (caller(1))[3] . "(): timeout") if $cmd->debug; +} + +sub _set_status_closed { + my $cmd = shift; + my $err = shift; + my $pkg = ref($cmd) || $cmd; + + $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Connection closed"); + carp(ref($cmd) . ": " . (caller(1))[3] + . "(): unexpected EOF on command channel: $err") if $cmd->debug; +} + +sub _is_closed { + my $cmd = shift; + if (!defined fileno($cmd)) { + $cmd->_set_status_closed($!); + return 1; + } + return 0; +} + +sub command { + my $cmd = shift; + + return $cmd + if $cmd->_is_closed; + + $cmd->dataend() + if (exists ${*$cmd}{'net_cmd_last_ch'}); + + if (scalar(@_)) { + my $str = join( + " ", + map { + /\n/ + ? do { my $n = $_; $n =~ tr/\n/ /; $n } + : $_; + } @_ + ); + $str = $cmd->toascii($str) if $tr; + $str .= "\015\012"; + + $cmd->debug_print(1, $str) + if ($cmd->debug); + + # though documented to return undef on failure, the legacy behavior + # was to return $cmd even on failure, so this odd construct does that + $cmd->_syswrite_with_timeout($str) + or return $cmd; + } + + $cmd; +} + + +sub ok { + @_ == 1 or croak 'usage: $obj->ok()'; + + my $code = $_[0]->code; + 0 < $code && $code < 400; +} + + +sub unsupported { + my $cmd = shift; + + $cmd->set_status(580, 'Unsupported command'); + + 0; +} + + +sub getline { + my $cmd = shift; + + ${*$cmd}{'net_cmd_lines'} ||= []; + + return shift @{${*$cmd}{'net_cmd_lines'}} + if scalar(@{${*$cmd}{'net_cmd_lines'}}); + + my $partial = defined(${*$cmd}{'net_cmd_partial'}) ? ${*$cmd}{'net_cmd_partial'} : ""; + + return + if $cmd->_is_closed; + + my $fd = fileno($cmd); + my $rin = ""; + vec($rin, $fd, 1) = 1; + + my $buf; + + until (scalar(@{${*$cmd}{'net_cmd_lines'}})) { + my $timeout = $cmd->timeout || undef; + my $rout; + + my $select_ret = select($rout = $rin, undef, undef, $timeout); + if ($select_ret > 0) { + unless (sysread($cmd, $buf = "", 1024)) { + my $err = $!; + $cmd->close; + $cmd->_set_status_closed($err); + return; + } + + substr($buf, 0, 0) = $partial; ## prepend from last sysread + + my @buf = split(/\015?\012/, $buf, -1); ## break into lines + + $partial = pop @buf; + + push(@{${*$cmd}{'net_cmd_lines'}}, map {"$_\n"} @buf); + + } + else { + $cmd->_set_status_timeout; + return; + } + } + + ${*$cmd}{'net_cmd_partial'} = $partial; + + if ($tr) { + foreach my $ln (@{${*$cmd}{'net_cmd_lines'}}) { + $ln = $cmd->toebcdic($ln); + } + } + + shift @{${*$cmd}{'net_cmd_lines'}}; +} + + +sub ungetline { + my ($cmd, $str) = @_; + + ${*$cmd}{'net_cmd_lines'} ||= []; + unshift(@{${*$cmd}{'net_cmd_lines'}}, $str); +} + + +sub parse_response { + return () + unless $_[1] =~ s/^(\d\d\d)(.?)//o; + ($1, $2 eq "-"); +} + + +sub response { + my $cmd = shift; + my ($code, $more) = (undef) x 2; + + $cmd->set_status($cmd->DEF_REPLY_CODE, undef); # initialize the response + + while (1) { + my $str = $cmd->getline(); + + return CMD_ERROR + unless defined($str); + + $cmd->debug_print(0, $str) + if ($cmd->debug); + + ($code, $more) = $cmd->parse_response($str); + unless (defined $code) { + carp("$cmd: response(): parse error in '$str'") if ($cmd->debug); + $cmd->ungetline($str); + $@ = $str; # $@ used as tunneling hack + return CMD_ERROR; + } + + ${*$cmd}{'net_cmd_code'} = $code; + + push(@{${*$cmd}{'net_cmd_resp'}}, $str); + + last unless ($more); + } + + return unless defined $code; + substr($code, 0, 1); +} + + +sub read_until_dot { + my $cmd = shift; + my $fh = shift; + my $arr = []; + + while (1) { + my $str = $cmd->getline() or return; + + $cmd->debug_print(0, $str) + if ($cmd->debug & 4); + + last if ($str =~ /^\.\r?\n/o); + + $str =~ s/^\.\././o; + + if (defined $fh) { + print $fh $str; + } + else { + push(@$arr, $str); + } + } + + $arr; +} + + +sub datasend { + my $cmd = shift; + my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_; + my $line = join("", @$arr); + + # Perls < 5.10.1 (with the exception of 5.8.9) have a performance problem with + # the substitutions below when dealing with strings stored internally in + # UTF-8, so downgrade them (if possible). + # Data passed to datasend() should be encoded to octets upstream already so + # shouldn't even have the UTF-8 flag on to start with, but if it so happens + # that the octets are stored in an upgraded string (as can sometimes occur) + # then they would still downgrade without fail anyway. + # Only Unicode codepoints > 0xFF stored in an upgraded string will fail to + # downgrade. We fail silently in that case, and a "Wide character in print" + # warning will be emitted later by syswrite(). + utf8::downgrade($line, 1) if $] < 5.010001 && $] != 5.008009; + + return 0 + if $cmd->_is_closed; + + my $last_ch = ${*$cmd}{'net_cmd_last_ch'}; + + # We have not send anything yet, so last_ch = "\012" means we are at the start of a line + $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch; + + return 1 unless length $line; + + if ($cmd->debug) { + foreach my $b (split(/\n/, $line)) { + $cmd->debug_print(1, "$b\n"); + } + } + + $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015"; + + my $first_ch = ''; + + if ($last_ch eq "\015") { + # Remove \012 so it does not get prefixed with another \015 below + # and escape the . if there is one following it because the fixup + # below will not find it + $first_ch = "\012" if $line =~ s/^\012(\.?)/$1$1/; + } + elsif ($last_ch eq "\012") { + # Fixup below will not find the . as the first character of the buffer + $first_ch = "." if $line =~ /^\./; + } + + $line =~ s/\015?\012(\.?)/\015\012$1$1/sg; + + substr($line, 0, 0) = $first_ch; + + ${*$cmd}{'net_cmd_last_ch'} = substr($line, -1, 1); + + $cmd->_syswrite_with_timeout($line) + or return; + + 1; +} + + +sub rawdatasend { + my $cmd = shift; + my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_; + my $line = join("", @$arr); + + return 0 + if $cmd->_is_closed; + + return 1 + unless length($line); + + if ($cmd->debug) { + my $b = "$cmd>>> "; + print STDERR $b, join("\n$b", split(/\n/, $line)), "\n"; + } + + $cmd->_syswrite_with_timeout($line) + or return; + + 1; +} + + +sub dataend { + my $cmd = shift; + + return 0 + if $cmd->_is_closed; + + my $ch = ${*$cmd}{'net_cmd_last_ch'}; + my $tosend; + + if (!defined $ch) { + return 1; + } + elsif ($ch ne "\012") { + $tosend = "\015\012"; + } + + $tosend .= ".\015\012"; + + $cmd->debug_print(1, ".\n") + if ($cmd->debug); + + $cmd->_syswrite_with_timeout($tosend) + or return 0; + + delete ${*$cmd}{'net_cmd_last_ch'}; + + $cmd->response() == CMD_OK; +} + +# read and write to tied filehandle +sub tied_fh { + my $cmd = shift; + ${*$cmd}{'net_cmd_readbuf'} = ''; + my $fh = gensym(); + tie *$fh, ref($cmd), $cmd; + return $fh; +} + +# tie to myself +sub TIEHANDLE { + my $class = shift; + my $cmd = shift; + return $cmd; +} + +# Tied filehandle read. Reads requested data length, returning +# end-of-file when the dot is encountered. +sub READ { + my $cmd = shift; + my ($len, $offset) = @_[1, 2]; + return unless exists ${*$cmd}{'net_cmd_readbuf'}; + my $done = 0; + while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) { + ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return; + $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m; + } + + $_[0] = ''; + substr($_[0], $offset + 0) = substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len); + substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len) = ''; + delete ${*$cmd}{'net_cmd_readbuf'} if $done; + + return length $_[0]; +} + + +sub READLINE { + my $cmd = shift; + + # in this context, we use the presence of readbuf to + # indicate that we have not yet reached the eof + return unless exists ${*$cmd}{'net_cmd_readbuf'}; + my $line = $cmd->getline; + return if $line =~ /^\.\r?\n/; + $line; +} + + +sub PRINT { + my $cmd = shift; + my ($buf, $len, $offset) = @_; + $len ||= length($buf); + $offset += 0; + return unless $cmd->datasend(substr($buf, $offset, $len)); + ${*$cmd}{'net_cmd_sending'}++; # flag that we should call dataend() + return $len; +} + + +sub CLOSE { + my $cmd = shift; + my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1; + delete ${*$cmd}{'net_cmd_readbuf'}; + delete ${*$cmd}{'net_cmd_sending'}; + $r; +} + +1; + +__END__ + + +=head1 NAME + +Net::Cmd - Network Command class (as used by FTP, SMTP etc) + +=head1 SYNOPSIS + + use Net::Cmd; + + @ISA = qw(Net::Cmd); + +=head1 DESCRIPTION + +C is a collection of methods that can be inherited by a sub-class +of C. These methods implement the functionality required for a +command based protocol, for example FTP and SMTP. + +If your sub-class does not also derive from C or similar (e.g. +C, C or C) then you must +provide the following methods by other means yourself: C and +C. + +=head1 USER METHODS + +These methods provide a user interface to the C object. + +=over 4 + +=item debug ( VALUE ) + +Set the level of debug information for this object. If C is not given +then the current state is returned. Otherwise the state is changed to +C and the previous state returned. + +Different packages +may implement different levels of debug but a non-zero value results in +copies of all commands and responses also being sent to STDERR. + +If C is C then the debug level will be set to the default +debug level for the class. + +This method can also be called as a I method to set/get the default +debug level for a given class. + +=item message () + +Returns the text message returned from the last command. In a scalar +context it returns a single string, in a list context it will return +each line as a separate element. (See L below.) + +=item code () + +Returns the 3-digit code from the last command. If a command is pending +then the value 0 is returned. (See L below.) + +=item ok () + +Returns non-zero if the last code value was greater than zero and +less than 400. This holds true for most command servers. Servers +where this does not hold may override this method. + +=item status () + +Returns the most significant digit of the current status code. If a command +is pending then C is returned. + +=item datasend ( DATA ) + +Send data to the remote server, converting LF to CRLF. Any line starting +with a '.' will be prefixed with another '.'. +C may be an array or a reference to an array. +The C passed in must be encoded by the caller to octets of whatever +encoding is required, e.g. by using the Encode module's C function. + +=item dataend () + +End the sending of data to the remote server. This is done by ensuring that +the data already sent ends with CRLF then sending '.CRLF' to end the +transmission. Once this data has been sent C calls C and +returns true if C returns CMD_OK. + +=back + +=head1 CLASS METHODS + +These methods are not intended to be called by the user, but used or +over-ridden by a sub-class of C + +=over 4 + +=item debug_print ( DIR, TEXT ) + +Print debugging information. C denotes the direction I being +data being sent to the server. Calls C before printing to +STDERR. + +=item debug_text ( DIR, TEXT ) + +This method is called to print debugging information. TEXT is +the text being sent. The method should return the text to be printed. + +This is primarily meant for the use of modules such as FTP where passwords +are sent, but we do not want to display them in the debugging information. + +=item command ( CMD [, ARGS, ... ]) + +Send a command to the command server. All arguments are first joined with +a space character and CRLF is appended, this string is then sent to the +command server. + +Returns undef upon failure. + +=item unsupported () + +Sets the status code to 580 and the response text to 'Unsupported command'. +Returns zero. + +=item response () + +Obtain a response from the server. Upon success the most significant digit +of the status code is returned. Upon failure, timeout etc., I is +returned. + +=item parse_response ( TEXT ) + +This method is called by C as a method with one argument. It should +return an array of 2 values, the 3-digit status code and a flag which is true +when this is part of a multi-line response and this line is not the last. + +=item getline () + +Retrieve one line, delimited by CRLF, from the remote server. Returns I +upon failure. + +B: If you do use this method for any reason, please remember to add +some C calls into your method. + +=item ungetline ( TEXT ) + +Unget a line of text from the server. + +=item rawdatasend ( DATA ) + +Send data to the remote server without performing any conversions. C +is a scalar. +As with C, the C passed in must be encoded by the caller +to octets of whatever encoding is required, e.g. by using the Encode module's +C function. + +=item read_until_dot () + +Read data from the remote server until a line consisting of a single '.'. +Any lines starting with '..' will have one of the '.'s removed. + +Returns a reference to a list containing the lines, or I upon failure. + +=item tied_fh () + +Returns a filehandle tied to the Net::Cmd object. After issuing a +command, you may read from this filehandle using read() or <>. The +filehandle will return EOF when the final dot is encountered. +Similarly, you may write to the filehandle in order to send data to +the server after issuing a command that expects data to be written. + +See the Net::POP3 and Net::SMTP modules for examples of this. + +=back + +=head1 PSEUDO RESPONSES + +Normally the values returned by C and C are +obtained from the remote server, but in a few circumstances, as +detailed below, C will return values that it sets. You +can alter this behavior by overriding DEF_REPLY_CODE() to specify +a different default reply code, or overriding one of the specific +error handling methods below. + +=over 4 + +=item Initial value + +Before any command has executed or if an unexpected error occurs +C will return "421" (temporary connection failure) and +C will return undef. + +=item Connection closed + +If the underlying C is closed, or if there are +any read or write failures, the file handle will be forced closed, +and C will return "421" (temporary connection failure) +and C will return "[$pkg] Connection closed" +(where $pkg is the name of the class that subclassed C). +The _set_status_closed() method can be overridden to set a different +message (by calling set_status()) or otherwise trap this error. + +=item Timeout + +If there is a read or write timeout C will return "421" +(temporary connection failure) and C will return +"[$pkg] Timeout" (where $pkg is the name of the class +that subclassed C). The _set_status_timeout() method +can be overridden to set a different message (by calling set_status()) +or otherwise trap this error. + +=back + +=head1 EXPORTS + +C exports six subroutines, five of these, C, C, +C, C and C, correspond to possible results +of C and C. The sixth is C. + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-2006 Graham Barr. All rights reserved. + +Copyright (C) 2013-2016 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/Config.pm b/lib/Net/Config.pm new file mode 100644 index 0000000..4f822a4 --- /dev/null +++ b/lib/Net/Config.pm @@ -0,0 +1,345 @@ +# Net::Config.pm +# +# Copyright (C) 2000 Graham Barr. All rights reserved. +# Copyright (C) 2013-2014, 2016 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::Config; + +use 5.008001; + +use strict; +use warnings; + +use Exporter; +use Socket qw(inet_aton inet_ntoa); + +our @EXPORT = qw(%NetConfig); +our @ISA = qw(Net::LocalCfg Exporter); +our $VERSION = "3.11"; + +our($CONFIGURE, $LIBNET_CFG); + +eval { + local @INC = @INC; + pop @INC if $INC[-1] eq '.'; + local $SIG{__DIE__}; + require Net::LocalCfg; +}; + +our %NetConfig = ( + nntp_hosts => [], + snpp_hosts => [], + pop3_hosts => [], + smtp_hosts => [], + ph_hosts => [], + daytime_hosts => [], + time_hosts => [], + inet_domain => undef, + ftp_firewall => undef, + ftp_ext_passive => 1, + ftp_int_passive => 1, + test_hosts => 1, + test_exist => 1, +); + +# +# Try to get as much configuration info as possible from InternetConfig +# +{ +## no critic (BuiltinFunctions::ProhibitStringyEval) +$^O eq 'MacOS' and eval < [ \$InternetConfig{ kICNNTPHost() } ], + pop3_hosts => [ \$InternetConfig{ kICMailAccount() } =~ /\@(.*)/ ], + smtp_hosts => [ \$InternetConfig{ kICSMTPHost() } ], + ftp_testhost => \$InternetConfig{ kICFTPHost() } ? \$InternetConfig{ kICFTPHost()} : undef, + ph_hosts => [ \$InternetConfig{ kICPhHost() } ], + ftp_ext_passive => \$InternetConfig{"646F676F\xA5UsePassiveMode"} || 0, + ftp_int_passive => \$InternetConfig{"646F676F\xA5UsePassiveMode"} || 0, + socks_hosts => + \$InternetConfig{ kICUseSocks() } ? [ \$InternetConfig{ kICSocksHost() } ] : [], + ftp_firewall => + \$InternetConfig{ kICUseFTPProxy() } ? [ \$InternetConfig{ kICFTPProxyHost() } ] : [], +); +\@NetConfig{keys %nc} = values %nc; +} +TRY_INTERNET_CONFIG +} + +my $file = __FILE__; +my $ref; +$file =~ s/Config.pm/libnet.cfg/; +if (-f $file) { + $ref = eval { local $SIG{__DIE__}; do $file }; + if (ref($ref) eq 'HASH') { + %NetConfig = (%NetConfig, %{$ref}); + $LIBNET_CFG = $file; + } +} +if ($< == $> and !$CONFIGURE) { + my $home = eval { local $SIG{__DIE__}; (getpwuid($>))[7] } || $ENV{HOME}; + $home ||= $ENV{HOMEDRIVE} . ($ENV{HOMEPATH} || '') if defined $ENV{HOMEDRIVE}; + if (defined $home) { + $file = $home . "/.libnetrc"; + $ref = eval { local $SIG{__DIE__}; do $file } if -f $file; + %NetConfig = (%NetConfig, %{$ref}) + if ref($ref) eq 'HASH'; + } +} +my ($k, $v); +while (($k, $v) = each %NetConfig) { + $NetConfig{$k} = [$v] + if ($k =~ /_hosts$/ and $k ne "test_hosts" and defined($v) and !ref($v)); +} + +# Take a hostname and determine if it is inside the firewall + + +sub requires_firewall { + shift; # ignore package + my $host = shift; + + return 0 unless defined $NetConfig{'ftp_firewall'}; + + $host = inet_aton($host) or return -1; + $host = inet_ntoa($host); + + if (exists $NetConfig{'local_netmask'}) { + my $quad = unpack("N", pack("C*", split(/\./, $host))); + my $list = $NetConfig{'local_netmask'}; + $list = [$list] unless ref($list); + foreach (@$list) { + my ($net, $bits) = (m#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#) or next; + my $mask = ~0 << (32 - $bits); + my $addr = unpack("N", pack("C*", split(/\./, $net))); + + return 0 if (($addr & $mask) == ($quad & $mask)); + } + return 1; + } + + return 0; +} + +*is_external = \&requires_firewall; + +1; + +__END__ + +=head1 NAME + +Net::Config - Local configuration data for libnet + +=head1 SYNOPSIS + + use Net::Config qw(%NetConfig); + +=head1 DESCRIPTION + +C holds configuration data for the modules in the libnet +distribution. During installation you will be asked for these values. + +The configuration data is held globally in a file in the perl installation +tree, but a user may override any of these values by providing their own. This +can be done by having a C<.libnetrc> file in their home directory. This file +should return a reference to a HASH containing the keys described below. +For example + + # .libnetrc + { + nntp_hosts => [ "my_preferred_host" ], + ph_hosts => [ "my_ph_server" ], + } + __END__ + +=head1 METHODS + +C defines the following methods. They are methods as they are +invoked as class methods. This is because C inherits from +C so you can override these methods if you want. + +=over 4 + +=item requires_firewall ( HOST ) + +Attempts to determine if a given host is outside your firewall. Possible +return values are. + + -1 Cannot lookup hostname + 0 Host is inside firewall (or there is no ftp_firewall entry) + 1 Host is outside the firewall + +This is done by using hostname lookup and the C entry in +the configuration data. + +=back + +=head1 NetConfig VALUES + +=over 4 + +=item nntp_hosts + +=item snpp_hosts + +=item pop3_hosts + +=item smtp_hosts + +=item ph_hosts + +=item daytime_hosts + +=item time_hosts + +Each is a reference to an array of hostnames (in order of preference), +which should be used for the given protocol + +=item inet_domain + +Your internet domain name + +=item ftp_firewall + +If you have an FTP proxy firewall (B an HTTP or SOCKS firewall) +then this value should be set to the firewall hostname. If your firewall +does not listen to port 21, then this value should be set to +C<"hostname:port"> (eg C<"hostname:99">) + +=item ftp_firewall_type + +There are many different ftp firewall products available. But unfortunately +there is no standard for how to traverse a firewall. The list below shows the +sequence of commands that Net::FTP will use + + user Username for remote host + pass Password for remote host + fwuser Username for firewall + fwpass Password for firewall + remote.host The hostname of the remote ftp server + +=over 4 + +=item 0Z<> + +There is no firewall + +=item 1Z<> + + USER user@remote.host + PASS pass + +=item 2Z<> + + USER fwuser + PASS fwpass + USER user@remote.host + PASS pass + +=item 3Z<> + + USER fwuser + PASS fwpass + SITE remote.site + USER user + PASS pass + +=item 4Z<> + + USER fwuser + PASS fwpass + OPEN remote.site + USER user + PASS pass + +=item 5Z<> + + USER user@fwuser@remote.site + PASS pass@fwpass + +=item 6Z<> + + USER fwuser@remote.site + PASS fwpass + USER user + PASS pass + +=item 7Z<> + + USER user@remote.host + PASS pass + AUTH fwuser + RESP fwpass + +=back + +=item ftp_ext_passive + +=item ftp_int_passive + +FTP servers can work in passive or active mode. Active mode is when +you want to transfer data you have to tell the server the address and +port to connect to. Passive mode is when the server provide the +address and port and you establish the connection. + +With some firewalls active mode does not work as the server cannot +connect to your machine (because you are behind a firewall) and the firewall +does not re-write the command. In this case you should set C +to a I value. + +Some servers are configured to only work in passive mode. If you have +one of these you can force C to always transfer in passive +mode; when not going via a firewall, by setting C to +a I value. + +=item local_netmask + +A reference to a list of netmask strings in the form C<"134.99.4.0/24">. +These are used by the C function to determine if a given +host is inside or outside your firewall. + +=back + +The following entries are used during installation & testing on the +libnet package + +=over 4 + +=item test_hosts + +If true then C may attempt to connect to hosts given in the +configuration. + +=item test_exists + +If true then C will check each hostname given that it exists + +=back + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1998-2011 Graham Barr. All rights reserved. + +Copyright (C) 2013-2014, 2016 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/Domain.pm b/lib/Net/Domain.pm new file mode 100644 index 0000000..556cc15 --- /dev/null +++ b/lib/Net/Domain.pm @@ -0,0 +1,365 @@ +# Net::Domain.pm +# +# Copyright (C) 1995-1998 Graham Barr. All rights reserved. +# Copyright (C) 2013-2014 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::Domain; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Exporter; +use Net::Config; + +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(hostname hostdomain hostfqdn domainname); +our $VERSION = "3.11"; + +my ($host, $domain, $fqdn) = (undef, undef, undef); + +# Try every conceivable way to get hostname. + + +sub _hostname { + + # we already know it + return $host + if (defined $host); + + if ($^O eq 'MSWin32') { + require Socket; + my ($name, $alias, $type, $len, @addr) = gethostbyname($ENV{'COMPUTERNAME'} || 'localhost'); + while (@addr) { + my $a = shift(@addr); + $host = gethostbyaddr($a, Socket::AF_INET()); + last if defined $host; + } + if (defined($host) && index($host, '.') > 0) { + $fqdn = $host; + ($host, $domain) = $fqdn =~ /^([^.]+)\.(.*)$/; + } + return $host; + } + elsif ($^O eq 'MacOS') { + chomp($host = `hostname`); + } + elsif ($^O eq 'VMS') { ## multiple varieties of net s/w makes this hard + $host = $ENV{'UCX$INET_HOST'} if defined($ENV{'UCX$INET_HOST'}); + $host = $ENV{'MULTINET_HOST_NAME'} if defined($ENV{'MULTINET_HOST_NAME'}); + if (index($host, '.') > 0) { + $fqdn = $host; + ($host, $domain) = $fqdn =~ /^([^.]+)\.(.*)$/; + } + return $host; + } + else { + local $SIG{'__DIE__'}; + + # syscall is preferred since it avoids tainting problems + eval { + my $tmp = "\0" x 256; ## preload scalar + eval { + package main; + require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) + defined(&main::SYS_gethostname); + } + || eval { + package main; + require "sys/syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) + defined(&main::SYS_gethostname); + } + and $host = + (syscall(&main::SYS_gethostname, $tmp, 256) == 0) + ? $tmp + : undef; + } + + # POSIX + || eval { + require POSIX; + $host = (POSIX::uname())[1]; + } + + # trusty old hostname command + || eval { + chop($host = `(hostname) 2>/dev/null`); # BSD'ish + } + + # sysV/POSIX uname command (may truncate) + || eval { + chop($host = `uname -n 2>/dev/null`); ## SYSV'ish && POSIX'ish + } + + # Apollo pre-SR10 + || eval { $host = (split(/[:. ]/, `/com/host`, 6))[0]; } + + || eval { $host = ""; }; + } + + # remove garbage + $host =~ s/[\0\r\n]+//go; + $host =~ s/(\A\.+|\.+\Z)//go; + $host =~ s/\.\.+/\./go; + + $host; +} + + +sub _hostdomain { + + # we already know it + return $domain + if (defined $domain); + + local $SIG{'__DIE__'}; + + return $domain = $NetConfig{'inet_domain'} + if defined $NetConfig{'inet_domain'}; + + # try looking in /etc/resolv.conf + # putting this here and assuming that it is correct, eliminates + # calls to gethostbyname, and therefore DNS lookups. This helps + # those on dialup systems. + + local ($_); + + if (open(my $res, '<', "/etc/resolv.conf")) { + while (<$res>) { + $domain = $1 + if (/\A\s*(?:domain|search)\s+(\S+)/); + } + close($res); + + return $domain + if (defined $domain); + } + + # just try hostname and system calls + + my $host = _hostname(); + my (@hosts); + + @hosts = ($host, "localhost"); + + unless (defined($host) && $host =~ /\./) { + my $dom = undef; + eval { + my $tmp = "\0" x 256; ## preload scalar + eval { + package main; + require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) + } + || eval { + package main; + require "sys/syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) + } + and $dom = + (syscall(&main::SYS_getdomainname, $tmp, 256) == 0) + ? $tmp + : undef; + }; + + if ($^O eq 'VMS') { + $dom ||= $ENV{'TCPIP$INET_DOMAIN'} + || $ENV{'UCX$INET_DOMAIN'}; + } + + chop($dom = `domainname 2>/dev/null`) + unless (defined $dom || $^O =~ /^(?:cygwin|MSWin32|android)/); + + if (defined $dom) { + my @h = (); + $dom =~ s/^\.+//; + while (length($dom)) { + push(@h, "$host.$dom"); + $dom =~ s/^[^.]+.+// or last; + } + unshift(@hosts, @h); + } + } + + # Attempt to locate FQDN + + foreach (grep { defined $_ } @hosts) { + my @info = gethostbyname($_); + + next unless @info; + + # look at real name & aliases + foreach my $site ($info[0], split(/ /, $info[1])) { + if (rindex($site, ".") > 0) { + + # Extract domain from FQDN + + ($domain = $site) =~ s/\A[^.]+\.//; + return $domain; + } + } + } + + # Look for environment variable + + $domain ||= $ENV{LOCALDOMAIN} || $ENV{DOMAIN}; + + if (defined $domain) { + $domain =~ s/[\r\n\0]+//g; + $domain =~ s/(\A\.+|\.+\Z)//g; + $domain =~ s/\.\.+/\./g; + } + + $domain; +} + + +sub domainname { + + return $fqdn + if (defined $fqdn); + + _hostname(); + + # *.local names are special on darwin. If we call gethostbyname below, it + # may hang while waiting for another, non-existent computer to respond. + if($^O eq 'darwin' && $host =~ /\.local$/) { + return $host; + } + + _hostdomain(); + + # Assumption: If the host name does not contain a period + # and the domain name does, then assume that they are correct + # this helps to eliminate calls to gethostbyname, and therefore + # eliminate DNS lookups + + return $fqdn = $host . "." . $domain + if (defined $host + and defined $domain + and $host !~ /\./ + and $domain =~ /\./); + + # For hosts that have no name, just an IP address + return $fqdn = $host if defined $host and $host =~ /^\d+(\.\d+){3}$/; + + my @host = defined $host ? split(/\./, $host) : ('localhost'); + my @domain = defined $domain ? split(/\./, $domain) : (); + my @fqdn = (); + + # Determine from @host & @domain the FQDN + + my @d = @domain; + +LOOP: + while (1) { + my @h = @host; + while (@h) { + my $tmp = join(".", @h, @d); + if ((gethostbyname($tmp))[0]) { + @fqdn = (@h, @d); + $fqdn = $tmp; + last LOOP; + } + pop @h; + } + last unless shift @d; + } + + if (@fqdn) { + $host = shift @fqdn; + until ((gethostbyname($host))[0]) { + $host .= "." . shift @fqdn; + } + $domain = join(".", @fqdn); + } + else { + undef $host; + undef $domain; + undef $fqdn; + } + + $fqdn; +} + + +sub hostfqdn { domainname() } + + +sub hostname { + domainname() + unless (defined $host); + return $host; +} + + +sub hostdomain { + domainname() + unless (defined $domain); + return $domain; +} + +1; # Keep require happy + +__END__ + +=head1 NAME + +Net::Domain - Attempt to evaluate the current host's internet name and domain + +=head1 SYNOPSIS + + use Net::Domain qw(hostname hostfqdn hostdomain domainname); + +=head1 DESCRIPTION + +Using various methods B to find the Fully Qualified Domain Name (FQDN) +of the current host. From this determine the host-name and the host-domain. + +Each of the functions will return I if the FQDN cannot be determined. + +=over 4 + +=item hostfqdn () + +Identify and return the FQDN of the current host. + +=item domainname () + +An alias for hostfqdn (). + +=item hostname () + +Returns the smallest part of the FQDN which can be used to identify the host. + +=item hostdomain () + +Returns the remainder of the FQDN after the I has been removed. + +=back + +=head1 AUTHOR + +Graham Barr EFE. + +Adapted from Sys::Hostname by David Sundstrom EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-1998 Graham Barr. All rights reserved. + +Copyright (C) 2013-2014 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/FTP.pm b/lib/Net/FTP.pm new file mode 100644 index 0000000..14153be --- /dev/null +++ b/lib/Net/FTP.pm @@ -0,0 +1,2049 @@ +# Net::FTP.pm +# +# Copyright (C) 1995-2004 Graham Barr. All rights reserved. +# Copyright (C) 2013-2017 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. +# +# Documentation (at end) improved 1996 by Nathan Torkington . + +package Net::FTP; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Fcntl qw(O_WRONLY O_RDONLY O_APPEND O_CREAT O_TRUNC); +use IO::Socket; +use Net::Cmd; +use Net::Config; +use Socket; +use Time::Local; + +our $VERSION = '3.11'; + +our $IOCLASS; +my $family_key; +BEGIN { + # Code for detecting if we can use SSL + my $ssl_class = eval { + require IO::Socket::SSL; + # first version with default CA on most platforms + no warnings 'numeric'; + IO::Socket::SSL->VERSION(2.007); + } && 'IO::Socket::SSL'; + + my $nossl_warn = !$ssl_class && + 'To use SSL please install IO::Socket::SSL with version>=2.007'; + + # Code for detecting if we can use IPv6 + my $inet6_class = eval { + require IO::Socket::IP; + no warnings 'numeric'; + IO::Socket::IP->VERSION(0.25); + } && 'IO::Socket::IP' || eval { + require IO::Socket::INET6; + no warnings 'numeric'; + IO::Socket::INET6->VERSION(2.62); + } && 'IO::Socket::INET6'; + + sub can_ssl { $ssl_class }; + sub can_inet6 { $inet6_class }; + + $IOCLASS = $ssl_class || $inet6_class || 'IO::Socket::INET'; + $family_key = + ( $ssl_class ? $ssl_class->can_ipv6 : $inet6_class || '' ) + eq 'IO::Socket::IP' + ? 'Family' : 'Domain'; +} + +our @ISA = ('Exporter','Net::Cmd',$IOCLASS); + +use constant TELNET_IAC => 255; +use constant TELNET_IP => 244; +use constant TELNET_DM => 242; + +use constant EBCDIC => $^O eq 'os390'; + +sub new { + my $pkg = shift; + my ($peer, %arg); + if (@_ % 2) { + $peer = shift; + %arg = @_; + } + else { + %arg = @_; + $peer = delete $arg{Host}; + } + + my $host = $peer; + my $fire = undef; + my $fire_type = undef; + + if (exists($arg{Firewall}) || Net::Config->requires_firewall($peer)) { + $fire = $arg{Firewall} + || $ENV{FTP_FIREWALL} + || $NetConfig{ftp_firewall} + || undef; + + if (defined $fire) { + $peer = $fire; + delete $arg{Port}; + $fire_type = $arg{FirewallType} + || $ENV{FTP_FIREWALL_TYPE} + || $NetConfig{firewall_type} + || undef; + } + } + + my %tlsargs; + if (can_ssl()) { + # for name verification strip port from domain:port, ipv4:port, [ipv6]:port + (my $hostname = $host) =~s{(? 'ftp', + SSL_verifycn_name => $hostname, + # use SNI if supported by IO::Socket::SSL + $pkg->can_client_sni ? (SSL_hostname => $hostname):(), + # reuse SSL session of control connection in data connections + SSL_session_cache => Net::FTP::_SSL_SingleSessionCache->new, + ); + # user defined SSL arg + $tlsargs{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg); + + } elsif ($arg{SSL}) { + croak("IO::Socket::SSL >= 2.007 needed for SSL support"); + } + + my $ftp = $pkg->SUPER::new( + PeerAddr => $peer, + PeerPort => $arg{Port} || ($arg{SSL} ? 'ftps(990)' : 'ftp(21)'), + LocalAddr => $arg{'LocalAddr'}, + $family_key => $arg{Domain} || $arg{Family}, + Proto => 'tcp', + Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120, + %tlsargs, + $arg{SSL} ? ():( SSL_startHandshake => 0 ), + ) or return; + + ${*$ftp}{'net_ftp_host'} = $host; # Remote hostname + ${*$ftp}{'net_ftp_type'} = 'A'; # ASCII/binary/etc mode + ${*$ftp}{'net_ftp_blksize'} = abs($arg{'BlockSize'} || 10240); + + ${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'}; + ${*$ftp}{'net_ftp_domain'} = $arg{Domain} || $arg{Family}; + + ${*$ftp}{'net_ftp_firewall'} = $fire + if (defined $fire); + ${*$ftp}{'net_ftp_firewall_type'} = $fire_type + if (defined $fire_type); + + ${*$ftp}{'net_ftp_passive'} = + int exists $arg{Passive} ? $arg{Passive} + : exists $ENV{FTP_PASSIVE} ? $ENV{FTP_PASSIVE} + : defined $fire ? $NetConfig{ftp_ext_passive} + : $NetConfig{ftp_int_passive}; # Whew! :-) + + ${*$ftp}{net_ftp_tlsargs} = \%tlsargs if %tlsargs; + if ($arg{SSL}) { + ${*$ftp}{net_ftp_tlsprot} = 'P'; + ${*$ftp}{net_ftp_tlsdirect} = 1; + } + + $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024); + + $ftp->autoflush(1); + + $ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef); + + unless ($ftp->response() == CMD_OK) { + $ftp->close(); + # keep @$ if no message. Happens, when response did not start with a code. + $@ = $ftp->message || $@; + undef $ftp; + } + + $ftp; +} + +## +## User interface methods +## + + +sub host { + my $me = shift; + ${*$me}{'net_ftp_host'}; +} + +sub passive { + my $ftp = shift; + return ${*$ftp}{'net_ftp_passive'} unless @_; + ${*$ftp}{'net_ftp_passive'} = shift; +} + + +sub hash { + my $ftp = shift; # self + + my ($h, $b) = @_; + unless ($h) { + delete ${*$ftp}{'net_ftp_hash'}; + return [\*STDERR, 0]; + } + ($h, $b) = (ref($h) ? $h : \*STDERR, $b || 1024); + select((select($h), $| = 1)[0]); + $b = 512 if $b < 512; + ${*$ftp}{'net_ftp_hash'} = [$h, $b]; +} + + +sub quit { + my $ftp = shift; + + $ftp->_QUIT; + $ftp->close; +} + + +sub DESTROY { } + + +sub ascii { shift->type('A', @_); } +sub binary { shift->type('I', @_); } + + +sub ebcdic { + carp "TYPE E is unsupported, shall default to I"; + shift->type('E', @_); +} + + +sub byte { + carp "TYPE L is unsupported, shall default to I"; + shift->type('L', @_); +} + +# Allow the user to send a command directly, BE CAREFUL !! + + +sub quot { + my $ftp = shift; + my $cmd = shift; + + $ftp->command(uc $cmd, @_); + $ftp->response(); +} + + +sub site { + my $ftp = shift; + + $ftp->command("SITE", @_); + $ftp->response(); +} + + +sub mdtm { + my $ftp = shift; + my $file = shift; + + # Server Y2K bug workaround + # + # sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of + # ("%d",tm.tm_year+1900). This results in an extra digit in the + # string returned. To account for this we allow an optional extra + # digit in the year. Then if the first two digits are 19 we use the + # remainder, otherwise we subtract 1900 from the whole year. + + $ftp->_MDTM($file) + && $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ + ? timegm($8, $7, $6, $5, $4 - 1, $2 eq '19' ? $3 : ($1 - 1900)) + : undef; +} + + +sub size { + my $ftp = shift; + my $file = shift; + my $io; + if ($ftp->supported("SIZE")) { + return $ftp->_SIZE($file) + ? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0] + : undef; + } + elsif ($ftp->supported("STAT")) { + my @msg; + return + unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3; + foreach my $line (@msg) { + return (split(/\s+/, $line))[4] + if $line =~ /^[-rwxSsTt]{10}/; + } + } + else { + my @files = $ftp->dir($file); + if (@files) { + return (split(/\s+/, $1))[4] + if $files[0] =~ /^([-rwxSsTt]{10}.*)$/; + } + } + undef; +} + + +sub starttls { + my $ftp = shift; + can_ssl() or croak("IO::Socket::SSL >= 2.007 needed for SSL support"); + $ftp->is_SSL and croak("called starttls within SSL session"); + $ftp->_AUTH('TLS') == CMD_OK or return; + + $ftp->connect_SSL or return; + $ftp->prot('P'); + return 1; +} + +sub prot { + my ($ftp,$prot) = @_; + $prot eq 'C' or $prot eq 'P' or croak("prot must by C or P"); + $ftp->_PBSZ(0) or return; + $ftp->_PROT($prot) or return; + ${*$ftp}{net_ftp_tlsprot} = $prot; + return 1; +} + +sub stoptls { + my $ftp = shift; + $ftp->is_SSL or croak("called stoptls outside SSL session"); + ${*$ftp}{net_ftp_tlsdirect} and croak("cannot stoptls direct SSL session"); + $ftp->_CCC() or return; + $ftp->stop_SSL(); + return 1; +} + +sub login { + my ($ftp, $user, $pass, $acct) = @_; + my ($ok, $ruser, $fwtype); + + unless (defined $user) { + require Net::Netrc; + + my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}); + + ($user, $pass, $acct) = $rc->lpa() + if ($rc); + } + + $user ||= "anonymous"; + $ruser = $user; + + $fwtype = ${*$ftp}{'net_ftp_firewall_type'} + || $NetConfig{'ftp_firewall_type'} + || 0; + + if ($fwtype && defined ${*$ftp}{'net_ftp_firewall'}) { + if ($fwtype == 1 || $fwtype == 7) { + $user .= '@' . ${*$ftp}{'net_ftp_host'}; + } + else { + require Net::Netrc; + + my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); + + my ($fwuser, $fwpass, $fwacct) = $rc ? $rc->lpa() : (); + + if ($fwtype == 5) { + $user = join('@', $user, $fwuser, ${*$ftp}{'net_ftp_host'}); + $pass = $pass . '@' . $fwpass; + } + else { + if ($fwtype == 2) { + $user .= '@' . ${*$ftp}{'net_ftp_host'}; + } + elsif ($fwtype == 6) { + $fwuser .= '@' . ${*$ftp}{'net_ftp_host'}; + } + + $ok = $ftp->_USER($fwuser); + + return 0 unless $ok == CMD_OK || $ok == CMD_MORE; + + $ok = $ftp->_PASS($fwpass || ""); + + return 0 unless $ok == CMD_OK || $ok == CMD_MORE; + + $ok = $ftp->_ACCT($fwacct) + if defined($fwacct); + + if ($fwtype == 3) { + $ok = $ftp->command("SITE", ${*$ftp}{'net_ftp_host'})->response; + } + elsif ($fwtype == 4) { + $ok = $ftp->command("OPEN", ${*$ftp}{'net_ftp_host'})->response; + } + + return 0 unless $ok == CMD_OK || $ok == CMD_MORE; + } + } + } + + $ok = $ftp->_USER($user); + + # Some dumb firewalls don't prefix the connection messages + $ok = $ftp->response() + if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/); + + if ($ok == CMD_MORE) { + unless (defined $pass) { + require Net::Netrc; + + my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}, $ruser); + + ($ruser, $pass, $acct) = $rc->lpa() + if ($rc); + + $pass = '-anonymous@' + if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o)); + } + + $ok = $ftp->_PASS($pass || ""); + } + + $ok = $ftp->_ACCT($acct) + if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK)); + + if ($fwtype == 7 && $ok == CMD_OK && defined ${*$ftp}{'net_ftp_firewall'}) { + my ($f, $auth, $resp) = _auth_id($ftp); + $ftp->authorize($auth, $resp) if defined($resp); + } + + $ok == CMD_OK; +} + + +sub account { + @_ == 2 or croak 'usage: $ftp->account( ACCT )'; + my $ftp = shift; + my $acct = shift; + $ftp->_ACCT($acct) == CMD_OK; +} + + +sub _auth_id { + my ($ftp, $auth, $resp) = @_; + + unless (defined $resp) { + require Net::Netrc; + + $auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME}; + + my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth) + || Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); + + ($auth, $resp) = $rc->lpa() + if ($rc); + } + ($ftp, $auth, $resp); +} + + +sub authorize { + @_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize( [AUTH [, RESP]])'; + + my ($ftp, $auth, $resp) = &_auth_id; + + my $ok = $ftp->_AUTH($auth || ""); + + return $ftp->_RESP($resp || "") + if ($ok == CMD_MORE); + + $ok == CMD_OK; +} + + +sub rename { + @_ == 3 or croak 'usage: $ftp->rename(FROM, TO)'; + + my ($ftp, $from, $to) = @_; + + $ftp->_RNFR($from) + && $ftp->_RNTO($to); +} + + +sub type { + my $ftp = shift; + my $type = shift; + my $oldval = ${*$ftp}{'net_ftp_type'}; + + return $oldval + unless (defined $type); + + return + unless ($ftp->_TYPE($type, @_)); + + ${*$ftp}{'net_ftp_type'} = join(" ", $type, @_); + + $oldval; +} + + +sub alloc { + my $ftp = shift; + my $size = shift; + my $oldval = ${*$ftp}{'net_ftp_allo'}; + + return $oldval + unless (defined $size); + + return + unless ($ftp->supported("ALLO") and $ftp->_ALLO($size, @_)); + + ${*$ftp}{'net_ftp_allo'} = join(" ", $size, @_); + + $oldval; +} + + +sub abort { + my $ftp = shift; + + send($ftp, pack("CCC", TELNET_IAC, TELNET_IP, TELNET_IAC), MSG_OOB); + + $ftp->command(pack("C", TELNET_DM) . "ABOR"); + + ${*$ftp}{'net_ftp_dataconn'}->close() + if defined ${*$ftp}{'net_ftp_dataconn'}; + + $ftp->response(); + + $ftp->status == CMD_OK; +} + + +sub get { + my ($ftp, $remote, $local, $where) = @_; + + my ($loc, $len, $buf, $resp, $data); + local *FD; + + my $localfd = ref($local) || ref(\$local) eq "GLOB"; + + ($local = $remote) =~ s#^.*/## + unless (defined $local); + + croak("Bad remote filename '$remote'\n") + if $remote =~ /[\r\n]/s; + + ${*$ftp}{'net_ftp_rest'} = $where if defined $where; + my $rest = ${*$ftp}{'net_ftp_rest'}; + + delete ${*$ftp}{'net_ftp_port'}; + delete ${*$ftp}{'net_ftp_pasv'}; + + $data = $ftp->retr($remote) + or return; + + if ($localfd) { + $loc = $local; + } + else { + $loc = \*FD; + + unless (sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND: O_TRUNC))) { + carp "Cannot open Local file $local: $!\n"; + $data->abort; + return; + } + } + + if ($ftp->type eq 'I' && !binmode($loc)) { + carp "Cannot binmode Local file $local: $!\n"; + $data->abort; + close($loc) unless $localfd; + return; + } + + $buf = ''; + my ($count, $hashh, $hashb, $ref) = (0); + + ($hashh, $hashb) = @$ref + if ($ref = ${*$ftp}{'net_ftp_hash'}); + + my $blksize = ${*$ftp}{'net_ftp_blksize'}; + local $\; # Just in case + + while (1) { + last unless $len = $data->read($buf, $blksize); + + if (EBCDIC && $ftp->type ne 'I') { + $buf = $ftp->toebcdic($buf); + $len = length($buf); + } + + if ($hashh) { + $count += $len; + print $hashh "#" x (int($count / $hashb)); + $count %= $hashb; + } + unless (print $loc $buf) { + carp "Cannot write to Local file $local: $!\n"; + $data->abort; + close($loc) + unless $localfd; + return; + } + } + + print $hashh "\n" if $hashh; + + unless ($localfd) { + unless (close($loc)) { + carp "Cannot close file $local (perhaps disk space) $!\n"; + return; + } + } + + unless ($data->close()) # implied $ftp->response + { + carp "Unable to close datastream"; + return; + } + + return $local; +} + + +sub cwd { + @_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd( [ DIR ] )'; + + my ($ftp, $dir) = @_; + + $dir = "/" unless defined($dir) && $dir =~ /\S/; + + $dir eq ".." + ? $ftp->_CDUP() + : $ftp->_CWD($dir); +} + + +sub cdup { + @_ == 1 or croak 'usage: $ftp->cdup()'; + $_[0]->_CDUP; +} + + +sub pwd { + @_ == 1 || croak 'usage: $ftp->pwd()'; + my $ftp = shift; + + $ftp->_PWD(); + $ftp->_extract_path; +} + +# rmdir( $ftp, $dir, [ $recurse ] ) +# +# Removes $dir on remote host via FTP. +# $ftp is handle for remote host +# +# If $recurse is TRUE, the directory and deleted recursively. +# This means all of its contents and subdirectories. +# +# Initial version contributed by Dinkum Software +# +sub rmdir { + @_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir( DIR [, RECURSE ] )'); + + # Pick off the args + my ($ftp, $dir, $recurse) = @_; + my $ok; + + return $ok + if $ok = $ftp->_RMD($dir) + or !$recurse; + + # Try to delete the contents + # Get a list of all the files in the directory, excluding the current and parent directories + my @filelist = map { /^(?:\S+;)+ (.+)$/ ? ($1) : () } grep { !/^(?:\S+;)*type=[cp]dir;/i } $ftp->_list_cmd("MLSD", $dir); + + # Fallback to using the less well-defined NLST command if MLSD fails + @filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir) + unless @filelist; + + return + unless @filelist; # failed, it is probably not a directory + + return $ftp->delete($dir) + if @filelist == 1 and $dir eq $filelist[0]; + + # Go thru and delete each file or the directory + foreach my $file (map { m,/, ? $_ : "$dir/$_" } @filelist) { + next # successfully deleted the file + if $ftp->delete($file); + + # Failed to delete it, assume its a directory + # Recurse and ignore errors, the final rmdir() will + # fail on any errors here + return $ok + unless $ok = $ftp->rmdir($file, 1); + } + + # Directory should be empty + # Try to remove the directory again + # Pass results directly to caller + # If any of the prior deletes failed, this + # rmdir() will fail because directory is not empty + return $ftp->_RMD($dir); +} + + +sub restart { + @_ == 2 || croak 'usage: $ftp->restart( BYTE_OFFSET )'; + + my ($ftp, $where) = @_; + + ${*$ftp}{'net_ftp_rest'} = $where; + + return; +} + + +sub mkdir { + @_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir( DIR [, RECURSE ] )'; + + my ($ftp, $dir, $recurse) = @_; + + $ftp->_MKD($dir) || $recurse + or return; + + my $path = $dir; + + unless ($ftp->ok) { + my @path = split(m#(?=/+)#, $dir); + + $path = ""; + + while (@path) { + $path .= shift @path; + + $ftp->_MKD($path); + + $path = $ftp->_extract_path($path); + } + + # If the creation of the last element was not successful, see if we + # can cd to it, if so then return path + + unless ($ftp->ok) { + my ($status, $message) = ($ftp->status, $ftp->message); + my $pwd = $ftp->pwd; + + if ($pwd && $ftp->cwd($dir)) { + $path = $dir; + $ftp->cwd($pwd); + } + else { + undef $path; + } + $ftp->set_status($status, $message); + } + } + + $path; +} + + +sub delete { + @_ == 2 || croak 'usage: $ftp->delete( FILENAME )'; + + $_[0]->_DELE($_[1]); +} + + +sub put { shift->_store_cmd("stor", @_) } +sub put_unique { shift->_store_cmd("stou", @_) } +sub append { shift->_store_cmd("appe", @_) } + + +sub nlst { shift->_data_cmd("NLST", @_) } +sub list { shift->_data_cmd("LIST", @_) } +sub retr { shift->_data_cmd("RETR", @_) } +sub stor { shift->_data_cmd("STOR", @_) } +sub stou { shift->_data_cmd("STOU", @_) } +sub appe { shift->_data_cmd("APPE", @_) } + + +sub _store_cmd { + my ($ftp, $cmd, $local, $remote) = @_; + my ($loc, $sock, $len, $buf); + local *FD; + + my $localfd = ref($local) || ref(\$local) eq "GLOB"; + + if (!defined($remote) and 'STOU' ne uc($cmd)) { + croak 'Must specify remote filename with stream input' + if $localfd; + + require File::Basename; + $remote = File::Basename::basename($local); + } + if (defined ${*$ftp}{'net_ftp_allo'}) { + delete ${*$ftp}{'net_ftp_allo'}; + } + else { + + # if the user hasn't already invoked the alloc method since the last + # _store_cmd call, figure out if the local file is a regular file(not + # a pipe, or device) and if so get the file size from stat, and send + # an ALLO command before sending the STOR, STOU, or APPE command. + my $size = do { local $^W; -f $local && -s _ }; # no ALLO if sending data from a pipe + ${*$ftp}{'net_ftp_allo'} = $size if $size; + } + croak("Bad remote filename '$remote'\n") + if defined($remote) and $remote =~ /[\r\n]/s; + + if ($localfd) { + $loc = $local; + } + else { + $loc = \*FD; + + unless (sysopen($loc, $local, O_RDONLY)) { + carp "Cannot open Local file $local: $!\n"; + return; + } + } + + if ($ftp->type eq 'I' && !binmode($loc)) { + carp "Cannot binmode Local file $local: $!\n"; + return; + } + + delete ${*$ftp}{'net_ftp_port'}; + delete ${*$ftp}{'net_ftp_pasv'}; + + $sock = $ftp->_data_cmd($cmd, grep { defined } $remote) + or return; + + $remote = ($ftp->message =~ /\w+\s*:\s*(.*)/)[0] + if 'STOU' eq uc $cmd; + + my $blksize = ${*$ftp}{'net_ftp_blksize'}; + + my ($count, $hashh, $hashb, $ref) = (0); + + ($hashh, $hashb) = @$ref + if ($ref = ${*$ftp}{'net_ftp_hash'}); + + while (1) { + last unless $len = read($loc, $buf = "", $blksize); + + if (EBCDIC && $ftp->type ne 'I') { + $buf = $ftp->toascii($buf); + $len = length($buf); + } + + if ($hashh) { + $count += $len; + print $hashh "#" x (int($count / $hashb)); + $count %= $hashb; + } + + my $wlen; + unless (defined($wlen = $sock->write($buf, $len)) && $wlen == $len) { + $sock->abort; + close($loc) + unless $localfd; + print $hashh "\n" if $hashh; + return; + } + } + + print $hashh "\n" if $hashh; + + close($loc) + unless $localfd; + + $sock->close() + or return; + + if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/) { + require File::Basename; + $remote = File::Basename::basename($+); + } + + return $remote; +} + + +sub port { + @_ == 1 || @_ == 2 or croak 'usage: $self->port([PORT])'; + return _eprt('PORT',@_); +} + +sub eprt { + @_ == 1 || @_ == 2 or croak 'usage: $self->eprt([PORT])'; + return _eprt('EPRT',@_); +} + +sub _eprt { + my ($cmd,$ftp,$port) = @_; + delete ${*$ftp}{net_ftp_intern_port}; + unless ($port) { + my $listen = ${*$ftp}{net_ftp_listen} ||= $IOCLASS->new( + Listen => 1, + Timeout => $ftp->timeout, + LocalAddr => $ftp->sockhost, + $family_key => $ftp->sockdomain, + can_ssl() ? ( + %{ ${*$ftp}{net_ftp_tlsargs} }, + SSL_startHandshake => 0, + ):(), + ); + ${*$ftp}{net_ftp_intern_port} = 1; + my $fam = ($listen->sockdomain == AF_INET) ? 1:2; + if ( $cmd eq 'EPRT' || $fam == 2 ) { + $port = "|$fam|".$listen->sockhost."|".$listen->sockport."|"; + $cmd = 'EPRT'; + } else { + my $p = $listen->sockport; + $port = join(',',split(m{\.},$listen->sockhost),$p >> 8,$p & 0xff); + } + } elsif (ref($port) eq 'ARRAY') { + $port = join(',',split(m{\.},@$port[0]),@$port[1] >> 8,@$port[1] & 0xff); + } + my $ok = $cmd eq 'EPRT' ? $ftp->_EPRT($port) : $ftp->_PORT($port); + ${*$ftp}{net_ftp_port} = $port if $ok; + return $ok; +} + + +sub ls { shift->_list_cmd("NLST", @_); } +sub dir { shift->_list_cmd("LIST", @_); } + + +sub pasv { + my $ftp = shift; + @_ and croak 'usage: $ftp->port()'; + return $ftp->epsv if $ftp->sockdomain != AF_INET; + delete ${*$ftp}{net_ftp_intern_port}; + + if ( $ftp->_PASV && + $ftp->message =~ m{(\d+,\d+,\d+,\d+),(\d+),(\d+)} ) { + my $port = 256 * $2 + $3; + ( my $ip = $1 ) =~s{,}{.}g; + return ${*$ftp}{net_ftp_pasv} = [ $ip,$port ]; + } + return; +} + +sub epsv { + my $ftp = shift; + @_ and croak 'usage: $ftp->epsv()'; + delete ${*$ftp}{net_ftp_intern_port}; + + $ftp->_EPSV && $ftp->message =~ m{\(([\x33-\x7e])\1\1(\d+)\1\)} + ? ${*$ftp}{net_ftp_pasv} = [ $ftp->peerhost, $2 ] + : undef; +} + + +sub unique_name { + my $ftp = shift; + ${*$ftp}{'net_ftp_unique'} || undef; +} + + +sub supported { + @_ == 2 or croak 'usage: $ftp->supported( CMD )'; + my $ftp = shift; + my $cmd = uc shift; + my $hash = ${*$ftp}{'net_ftp_supported'} ||= {}; + + return $hash->{$cmd} + if exists $hash->{$cmd}; + + return $hash->{$cmd} = 1 + if $ftp->feature($cmd); + + return $hash->{$cmd} = 0 + unless $ftp->_HELP($cmd); + + my $text = $ftp->message; + if ($text =~ /following.+commands/i) { + $text =~ s/^.*\n//; + while ($text =~ /(\*?)(\w+)(\*?)/sg) { + $hash->{"\U$2"} = !length("$1$3"); + } + } + else { + $hash->{$cmd} = $text !~ /unimplemented/i; + } + + $hash->{$cmd} ||= 0; +} + +## +## Deprecated methods +## + + +sub lsl { + carp "Use of Net::FTP::lsl deprecated, use 'dir'" + if $^W; + goto &dir; +} + + +sub authorise { + carp "Use of Net::FTP::authorise deprecated, use 'authorize'" + if $^W; + goto &authorize; +} + + +## +## Private methods +## + + +sub _extract_path { + my ($ftp, $path) = @_; + + # This tries to work both with and without the quote doubling + # convention (RFC 959 requires it, but the first 3 servers I checked + # didn't implement it). It will fail on a server which uses a quote in + # the message which isn't a part of or surrounding the path. + $ftp->ok + && $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/ + && ($path = $1) =~ s/\"\"/\"/g; + + $path; +} + +## +## Communication methods +## + + +sub _dataconn { + my $ftp = shift; + my $pkg = "Net::FTP::" . $ftp->type; + eval "require " . $pkg ## no critic (BuiltinFunctions::ProhibitStringyEval) + or croak("cannot load $pkg required for type ".$ftp->type); + $pkg =~ s/ /_/g; + delete ${*$ftp}{net_ftp_dataconn}; + + my $conn; + my $pasv = ${*$ftp}{net_ftp_pasv}; + if ($pasv) { + $conn = $pkg->new( + PeerAddr => $pasv->[0], + PeerPort => $pasv->[1], + LocalAddr => ${*$ftp}{net_ftp_localaddr}, + $family_key => ${*$ftp}{net_ftp_domain}, + Timeout => $ftp->timeout, + can_ssl() ? ( + SSL_startHandshake => 0, + $ftp->is_SSL ? ( + SSL_reuse_ctx => $ftp, + SSL_verifycn_name => ${*$ftp}{net_ftp_tlsargs}{SSL_verifycn_name}, + # This will cause the use of SNI if supported by IO::Socket::SSL. + $ftp->can_client_sni ? ( + SSL_hostname => ${*$ftp}{net_ftp_tlsargs}{SSL_hostname} + ):(), + ) :( %{${*$ftp}{net_ftp_tlsargs}} ), + ):(), + ) or return; + } elsif (my $listen = delete ${*$ftp}{net_ftp_listen}) { + $conn = $listen->accept($pkg) or return; + $conn->timeout($ftp->timeout); + close($listen); + } else { + croak("no listener in active mode"); + } + + if (( ${*$ftp}{net_ftp_tlsprot} || '') eq 'P') { + if ($conn->connect_SSL) { + # SSL handshake ok + } else { + carp("failed to ssl upgrade dataconn: $IO::Socket::SSL::SSL_ERROR"); + return; + } + } + + ${*$ftp}{net_ftp_dataconn} = $conn; + ${*$conn} = ""; + ${*$conn}{net_ftp_cmd} = $ftp; + ${*$conn}{net_ftp_blksize} = ${*$ftp}{net_ftp_blksize}; + return $conn; +} + + +sub _list_cmd { + my $ftp = shift; + my $cmd = uc shift; + + delete ${*$ftp}{'net_ftp_port'}; + delete ${*$ftp}{'net_ftp_pasv'}; + + my $data = $ftp->_data_cmd($cmd, @_); + + return + unless (defined $data); + + require Net::FTP::A; + bless $data, "Net::FTP::A"; # Force ASCII mode + + my $databuf = ''; + my $buf = ''; + my $blksize = ${*$ftp}{'net_ftp_blksize'}; + + while ($data->read($databuf, $blksize)) { + $buf .= $databuf; + } + + my $list = [split(/\n/, $buf)]; + + $data->close(); + + if (EBCDIC) { + for (@$list) { $_ = $ftp->toebcdic($_) } + } + + wantarray + ? @{$list} + : $list; +} + + +sub _data_cmd { + my $ftp = shift; + my $cmd = uc shift; + my $ok = 1; + my $where = delete ${*$ftp}{'net_ftp_rest'} || 0; + my $arg; + + for my $arg (@_) { + croak("Bad argument '$arg'\n") + if $arg =~ /[\r\n]/s; + } + + if ( ${*$ftp}{'net_ftp_passive'} + && !defined ${*$ftp}{'net_ftp_pasv'} + && !defined ${*$ftp}{'net_ftp_port'}) + { + return unless defined $ftp->pasv; + + if ($where and !$ftp->_REST($where)) { + my ($status, $message) = ($ftp->status, $ftp->message); + $ftp->abort; + $ftp->set_status($status, $message); + return; + } + + # first send command, then open data connection + # otherwise the peer might not do a full accept (with SSL + # handshake if PROT P) + $ftp->command($cmd, @_); + my $data = $ftp->_dataconn(); + if (CMD_INFO == $ftp->response()) { + $data->reading + if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; + return $data; + } + $data->_close if $data; + + return; + } + + $ok = $ftp->port + unless (defined ${*$ftp}{'net_ftp_port'} + || defined ${*$ftp}{'net_ftp_pasv'}); + + $ok = $ftp->_REST($where) + if $ok && $where; + + return + unless $ok; + + if ($cmd =~ /(STOR|APPE|STOU)/ and exists ${*$ftp}{net_ftp_allo} and + $ftp->supported("ALLO")) + { + $ftp->_ALLO(delete ${*$ftp}{net_ftp_allo}) + or return; + } + + $ftp->command($cmd, @_); + + return 1 + if (defined ${*$ftp}{'net_ftp_pasv'}); + + $ok = CMD_INFO == $ftp->response(); + + return $ok + unless exists ${*$ftp}{'net_ftp_intern_port'}; + + if ($ok) { + my $data = $ftp->_dataconn(); + + $data->reading + if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; + + return $data; + } + + + close(delete ${*$ftp}{'net_ftp_listen'}); + + return; +} + +## +## Over-ride methods (Net::Cmd) +## + + +sub debug_text { $_[2] =~ /^(pass|resp|acct)/i ? "$1 ....\n" : $_[2]; } + + +sub command { + my $ftp = shift; + + delete ${*$ftp}{'net_ftp_port'}; + $ftp->SUPER::command(@_); +} + + +sub response { + my $ftp = shift; + my $code = $ftp->SUPER::response() || 5; # assume 500 if undef + + delete ${*$ftp}{'net_ftp_pasv'} + if ($code != CMD_MORE && $code != CMD_INFO); + + $code; +} + + +sub parse_response { + return ($1, $2 eq "-") + if $_[1] =~ s/^(\d\d\d)([- ]?)//o; + + my $ftp = shift; + + # Darn MS FTP server is a load of CRAP !!!! + # Expect to see undef here. + return () + unless 0 + (${*$ftp}{'net_cmd_code'} || 0); + + (${*$ftp}{'net_cmd_code'}, 1); +} + +## +## Allow 2 servers to talk directly +## + + +sub pasv_xfer_unique { + my ($sftp, $sfile, $dftp, $dfile) = @_; + $sftp->pasv_xfer($sfile, $dftp, $dfile, 1); +} + + +sub pasv_xfer { + my ($sftp, $sfile, $dftp, $dfile, $unique) = @_; + + ($dfile = $sfile) =~ s#.*/## + unless (defined $dfile); + + my $port = $sftp->pasv + or return; + + $dftp->port($port) + or return; + + return + unless ($unique ? $dftp->stou($dfile) : $dftp->stor($dfile)); + + unless ($sftp->retr($sfile) && $sftp->response == CMD_INFO) { + $sftp->retr($sfile); + $dftp->abort; + $dftp->response(); + return; + } + + $dftp->pasv_wait($sftp); +} + + +sub pasv_wait { + @_ == 2 or croak 'usage: $ftp->pasv_wait(NON_PASV_FTP)'; + + my ($ftp, $non_pasv) = @_; + my ($file, $rin, $rout); + + vec($rin = '', fileno($ftp), 1) = 1; + select($rout = $rin, undef, undef, undef); + + my $dres = $ftp->response(); + my $sres = $non_pasv->response(); + + return + unless $dres == CMD_OK && $sres == CMD_OK; + + return + unless $ftp->ok() && $non_pasv->ok(); + + return $1 + if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/; + + return $1 + if $non_pasv->message =~ /unique file name:\s*(\S*)\s*\)/; + + return 1; +} + + +sub feature { + @_ == 2 or croak 'usage: $ftp->feature( NAME )'; + my ($ftp, $feat) = @_; + + my $feature = ${*$ftp}{net_ftp_feature} ||= do { + my @feat; + + # Example response + # 211-Features: + # MDTM + # REST STREAM + # SIZE + # 211 End + + @feat = map { /^\s+(.*\S)/ } $ftp->message + if $ftp->_FEAT; + + \@feat; + }; + + return grep { /^\Q$feat\E\b/i } @$feature; +} + + +sub cmd { shift->command(@_)->response() } + +######################################## +# +# RFC959 + RFC2428 + RFC4217 commands +# + + +sub _ABOR { shift->command("ABOR")->response() == CMD_OK } +sub _ALLO { shift->command("ALLO", @_)->response() == CMD_OK } +sub _CDUP { shift->command("CDUP")->response() == CMD_OK } +sub _NOOP { shift->command("NOOP")->response() == CMD_OK } +sub _PASV { shift->command("PASV")->response() == CMD_OK } +sub _QUIT { shift->command("QUIT")->response() == CMD_OK } +sub _DELE { shift->command("DELE", @_)->response() == CMD_OK } +sub _CWD { shift->command("CWD", @_)->response() == CMD_OK } +sub _PORT { shift->command("PORT", @_)->response() == CMD_OK } +sub _RMD { shift->command("RMD", @_)->response() == CMD_OK } +sub _MKD { shift->command("MKD", @_)->response() == CMD_OK } +sub _PWD { shift->command("PWD", @_)->response() == CMD_OK } +sub _TYPE { shift->command("TYPE", @_)->response() == CMD_OK } +sub _RNTO { shift->command("RNTO", @_)->response() == CMD_OK } +sub _RESP { shift->command("RESP", @_)->response() == CMD_OK } +sub _MDTM { shift->command("MDTM", @_)->response() == CMD_OK } +sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK } +sub _HELP { shift->command("HELP", @_)->response() == CMD_OK } +sub _STAT { shift->command("STAT", @_)->response() == CMD_OK } +sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK } +sub _PBSZ { shift->command("PBSZ", @_)->response() == CMD_OK } +sub _PROT { shift->command("PROT", @_)->response() == CMD_OK } +sub _CCC { shift->command("CCC", @_)->response() == CMD_OK } +sub _EPRT { shift->command("EPRT", @_)->response() == CMD_OK } +sub _EPSV { shift->command("EPSV", @_)->response() == CMD_OK } +sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO } +sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO } +sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO } +sub _RETR { shift->command("RETR", @_)->response() == CMD_INFO } +sub _STOR { shift->command("STOR", @_)->response() == CMD_INFO } +sub _STOU { shift->command("STOU", @_)->response() == CMD_INFO } +sub _RNFR { shift->command("RNFR", @_)->response() == CMD_MORE } +sub _REST { shift->command("REST", @_)->response() == CMD_MORE } +sub _PASS { shift->command("PASS", @_)->response() } +sub _ACCT { shift->command("ACCT", @_)->response() } +sub _AUTH { shift->command("AUTH", @_)->response() } + + +sub _USER { + my $ftp = shift; + my $ok = $ftp->command("USER", @_)->response(); + + # A certain brain dead firewall :-) + $ok = $ftp->command("user", @_)->response() + unless $ok == CMD_MORE or $ok == CMD_OK; + + $ok; +} + + +sub _SMNT { shift->unsupported(@_) } +sub _MODE { shift->unsupported(@_) } +sub _SYST { shift->unsupported(@_) } +sub _STRU { shift->unsupported(@_) } +sub _REIN { shift->unsupported(@_) } + +{ + # Session Cache with single entry + # used to make sure that we reuse same session for control and data channels + package Net::FTP::_SSL_SingleSessionCache; + sub new { my $x; return bless \$x,shift } + sub add_session { + my ($cache,$key,$session) = @_; + Net::SSLeay::SESSION_free($$cache) if $$cache; + $$cache = $session; + } + sub get_session { + my $cache = shift; + return $$cache + } + sub DESTROY { + my $cache = shift; + Net::SSLeay::SESSION_free($$cache) if $$cache; + } +} + +1; + +__END__ + +=head1 NAME + +Net::FTP - FTP Client class + +=head1 SYNOPSIS + + use Net::FTP; + + $ftp = Net::FTP->new("some.host.name", Debug => 0) + or die "Cannot connect to some.host.name: $@"; + + $ftp->login("anonymous",'-anonymous@') + or die "Cannot login ", $ftp->message; + + $ftp->cwd("/pub") + or die "Cannot change working directory ", $ftp->message; + + $ftp->get("that.file") + or die "get failed ", $ftp->message; + + $ftp->quit; + +=head1 DESCRIPTION + +C is a class implementing a simple FTP client in Perl as +described in RFC959. It provides wrappers for the commonly used subset of the +RFC959 commands. +If L or L is installed it also provides +support for IPv6 as defined in RFC2428. +And with L installed it provides support for implicit FTPS +and explicit FTPS as defined in RFC4217. + +The Net::FTP class is a subclass of Net::Cmd and (depending on avaibility) of +IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET. + +=head1 OVERVIEW + +FTP stands for File Transfer Protocol. It is a way of transferring +files between networked machines. The protocol defines a client +(whose commands are provided by this module) and a server (not +implemented in this module). Communication is always initiated by the +client, and the server responds with a message and a status code (and +sometimes with data). + +The FTP protocol allows files to be sent to or fetched from the +server. Each transfer involves a B (on the client) and a +B (on the server). In this module, the same file name +will be used for both local and remote if only one is specified. This +means that transferring remote file C will try to put +that file in C locally, unless you specify a local file +name. + +The protocol also defines several standard B which the +file can undergo during transfer. These are ASCII, EBCDIC, binary, +and byte. ASCII is the default type, and indicates that the sender of +files will translate the ends of lines to a standard representation +which the receiver will then translate back into their local +representation. EBCDIC indicates the file being transferred is in +EBCDIC format. Binary (also known as image) format sends the data as +a contiguous bit stream. Byte format transfers the data as bytes, the +values of which remain the same regardless of differences in byte size +between the two machines (in theory - in practice you should only use +this if you really know what you're doing). This class does not support +the EBCDIC or byte formats, and will default to binary instead if they +are attempted. + +=head1 CONSTRUCTOR + +=over 4 + +=item new ([ HOST ] [, OPTIONS ]) + +This is the constructor for a new Net::FTP object. C is the +name of the remote host to which an FTP connection is required. + +C is optional. If C is not given then it may instead be +passed as the C option described below. + +C are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B - FTP host to connect to. It may be a single scalar, as defined for +the C option in L, or a reference to +an array with hosts to try in turn. The L method will return the value +which was used to connect to the host. + +B - The name of a machine which acts as an FTP firewall. This can be +overridden by an environment variable C. If specified, and the +given host cannot be directly connected to, then the +connection is made to the firewall machine and the string C<@hostname> is +appended to the login identifier. This kind of setup is also referred to +as an ftp proxy. + +B - The type of firewall running on the machine indicated by +B. This can be overridden by an environment variable +C. For a list of permissible types, see the description of +ftp_firewall_type in L. + +B - This is the block size that Net::FTP will use when doing +transfers. (defaults to 10240) + +B - The port number to connect to on the remote machine for the +FTP connection + +B - If the connection should be done from start with SSL, contrary to later +upgrade with C. + +B - SSL arguments which will be applied when upgrading the control or +data connection to SSL. You can use SSL arguments as documented in +L, but it will usually use the right arguments already. + +B - Set a timeout value in seconds (defaults to 120) + +B - debug level (see the debug method in L) + +B - If set to a non-zero value then all data transfers will +be done using passive mode. If set to zero then data transfers will be +done using active mode. If the machine is connected to the Internet +directly, both passive and active mode should work equally well. +Behind most firewall and NAT configurations passive mode has a better +chance of working. However, in some rare firewall configurations, +active mode actually works when passive mode doesn't. Some really old +FTP servers might not implement passive transfers. If not specified, +then the transfer mode is set by the environment variable +C or if that one is not set by the settings done by the +F utility. If none of these apply then passive mode is +used. + +B - If given a reference to a file handle (e.g., C<\*STDERR>), +print hash marks (#) on that filehandle every 1024 bytes. This +simply invokes the C method for you, so that hash marks +are displayed for all transfers. You can, of course, call C +explicitly whenever you'd like. + +B - Local address to use for all socket connections. This +argument will be passed to the super class, i.e. L +or L. + +B - Domain to use, i.e. AF_INET or AF_INET6. This +argument will be passed to the IO::Socket super class. +This can be used to enforce IPv4 even with L +which would default to IPv6. +B is accepted as alternative name for B. + +If the constructor fails undef will be returned and an error message will +be in $@ + +=back + +=head1 METHODS + +Unless otherwise stated all methods return either a I or I +value, with I meaning that the operation was a success. When a method +states that it returns a value, failure will be returned as I or an +empty list. + +C inherits from C so methods defined in C may +be used to send commands to the remote FTP server in addition to the methods +documented here. + +=over 4 + +=item login ([LOGIN [,PASSWORD [, ACCOUNT] ] ]) + +Log into the remote FTP server with the given login information. If +no arguments are given then the C uses the C +package to lookup the login information for the connected host. +If no information is found then a login of I is used. +If no password is given and the login is I then I +will be used for password. + +If the connection is via a firewall then the C method will +be called with no arguments. + +=item starttls () + +Upgrade existing plain connection to SSL. +The SSL arguments have to be given in C already because they are needed for +data connections too. + +=item stoptls () + +Downgrade existing SSL connection back to plain. +This is needed to work with some FTP helpers at firewalls, which need to see the +PORT and PASV commands and responses to dynamically open the necessary ports. +In this case C is usually only done to protect the authorization. + +=item prot ( LEVEL ) + +Set what type of data channel protection the client and server will be using. +Only Cs "C" (clear) and "P" (private) are supported. + +=item host () + +Returns the value used by the constructor, and passed to the IO::Socket super +class to connect to the host. + +=item account( ACCT ) + +Set a string identifying the user's account. + +=item authorize ( [AUTH [, RESP]]) + +This is a protocol used by some firewall ftp proxies. It is used +to authorise the user to send data out. If both arguments are not specified +then C uses C to do a lookup. + +=item site (ARGS) + +Send a SITE command to the remote server and wait for a response. + +Returns most significant digit of the response code. + +=item ascii () + +Transfer file in ASCII. CRLF translation will be done if required + +=item binary () + +Transfer file in binary mode. No transformation will be done. + +B: If both server and client machines use the same line ending for +text files, then it will be faster to transfer all files in binary mode. + +=item type ( [ TYPE ] ) + +Set or get if files will be transferred in ASCII or binary mode. + +=item rename ( OLDNAME, NEWNAME ) + +Rename a file on the remote FTP server from C to C. This +is done by sending the RNFR and RNTO commands. + +=item delete ( FILENAME ) + +Send a request to the server to delete C. + +=item cwd ( [ DIR ] ) + +Attempt to change directory to the directory given in C<$dir>. If +C<$dir> is C<"..">, the FTP C command is used to attempt to +move up one directory. If no directory is given then an attempt is made +to change the directory to the root directory. + +=item cdup () + +Change directory to the parent of the current directory. + +=item passive ( [ PASSIVE ] ) + +Set or get if data connections will be initiated in passive mode. + +=item pwd () + +Returns the full pathname of the current directory. + +=item restart ( WHERE ) + +Set the byte offset at which to begin the next data transfer. Net::FTP simply +records this value and uses it when during the next data transfer. For this +reason this method will not return an error, but setting it may cause +a subsequent data transfer to fail. + +=item rmdir ( DIR [, RECURSE ]) + +Remove the directory with the name C. If C is I then +C will attempt to delete everything inside the directory. + +=item mkdir ( DIR [, RECURSE ]) + +Create a new directory with the name C. If C is I then +C will attempt to create all the directories in the given path. + +Returns the full pathname to the new directory. + +=item alloc ( SIZE [, RECORD_SIZE] ) + +The alloc command allows you to give the ftp server a hint about the size +of the file about to be transferred using the ALLO ftp command. Some storage +systems use this to make intelligent decisions about how to store the file. +The C argument represents the size of the file in bytes. The +C argument indicates a maximum record or page size for files +sent with a record or page structure. + +The size of the file will be determined, and sent to the server +automatically for normal files so that this method need only be called if +you are transferring data from a socket, named pipe, or other stream not +associated with a normal file. + +=item ls ( [ DIR ] ) + +Get a directory listing of C, or the current directory. + +In an array context, returns a list of lines returned from the server. In +a scalar context, returns a reference to a list. + +=item dir ( [ DIR ] ) + +Get a directory listing of C, or the current directory in long format. + +In an array context, returns a list of lines returned from the server. In +a scalar context, returns a reference to a list. + +=item get ( REMOTE_FILE [, LOCAL_FILE [, WHERE]] ) + +Get C from the server and store locally. C may be +a filename or a filehandle. If not specified, the file will be stored in +the current directory with the same leafname as the remote file. + +If C is given then the first C bytes of the file will +not be transferred, and the remaining bytes will be appended to +the local file if it already exists. + +Returns C, or the generated local file name if C +is not given. If an error was encountered undef is returned. + +=item put ( LOCAL_FILE [, REMOTE_FILE ] ) + +Put a file on the remote server. C may be a name or a filehandle. +If C is a filehandle then C must be specified. If +C is not specified then the file will be stored in the current +directory with the same leafname as C. + +Returns C, or the generated remote filename if C +is not given. + +B: If for some reason the transfer does not complete and an error is +returned then the contents that had been transferred will not be remove +automatically. + +=item put_unique ( LOCAL_FILE [, REMOTE_FILE ] ) + +Same as put but uses the C command. + +Returns the name of the file on the server. + +=item append ( LOCAL_FILE [, REMOTE_FILE ] ) + +Same as put but appends to the file on the remote server. + +Returns C, or the generated remote filename if C +is not given. + +=item unique_name () + +Returns the name of the last file stored on the server using the +C command. + +=item mdtm ( FILE ) + +Returns the I of the given file + +=item size ( FILE ) + +Returns the size in bytes for the given file as stored on the remote server. + +B: The size reported is the size of the stored file on the remote server. +If the file is subsequently transferred from the server in ASCII mode +and the remote server and local machine have different ideas about +"End Of Line" then the size of file on the local machine after transfer +may be different. + +=item supported ( CMD ) + +Returns TRUE if the remote server supports the given command. + +=item hash ( [FILEHANDLE_GLOB_REF],[ BYTES_PER_HASH_MARK] ) + +Called without parameters, or with the first argument false, hash marks +are suppressed. If the first argument is true but not a reference to a +file handle glob, then \*STDERR is used. The second argument is the number +of bytes per hash mark printed, and defaults to 1024. In all cases the +return value is a reference to an array of two: the filehandle glob reference +and the bytes per hash mark. + +=item feature ( NAME ) + +Determine if the server supports the specified feature. The return +value is a list of lines the server responded with to describe the +options that it supports for the given feature. If the feature is +unsupported then the empty list is returned. + + if ($ftp->feature( 'MDTM' )) { + # Do something + } + + if (grep { /\bTLS\b/ } $ftp->feature('AUTH')) { + # Server supports TLS + } + +=back + +The following methods can return different results depending on +how they are called. If the user explicitly calls either +of the C or C methods then these methods will +return a I or I value. If the user does not +call either of these methods then the result will be a +reference to a C based object. + +=over 4 + +=item nlst ( [ DIR ] ) + +Send an C command to the server, with an optional parameter. + +=item list ( [ DIR ] ) + +Same as C but using the C command + +=item retr ( FILE ) + +Begin the retrieval of a file called C from the remote server. + +=item stor ( FILE ) + +Tell the server that you wish to store a file. C is the +name of the new file that should be created. + +=item stou ( FILE ) + +Same as C but using the C command. The name of the unique +file which was created on the server will be available via the C +method after the data connection has been closed. + +=item appe ( FILE ) + +Tell the server that we want to append some data to the end of a file +called C. If this file does not exist then create it. + +=back + +If for some reason you want to have complete control over the data connection, +this includes generating it and calling the C method when required, +then the user can use these methods to do so. + +However calling these methods only affects the use of the methods above that +can return a data connection. They have no effect on methods C, C, +C and those that do not require data connections. + +=over 4 + +=item port ( [ PORT ] ) + +=item eprt ( [ PORT ] ) + +Send a C (IPv4) or C (IPv6) command to the server. If C is +specified then it is sent to the server. If not, then a listen socket is created +and the correct information sent to the server. + +=item pasv () + +=item epsv () + +Tell the server to go into passive mode (C for IPv4, C for IPv6). +Returns the text that represents the port on which the server is listening, this +text is in a suitable form to send to another ftp server using the C or +C method. + +=back + +The following methods can be used to transfer files between two remote +servers, providing that these two servers can connect directly to each other. + +=over 4 + +=item pasv_xfer ( SRC_FILE, DEST_SERVER [, DEST_FILE ] ) + +This method will do a file transfer between two remote ftp servers. If +C is omitted then the leaf name of C will be used. + +=item pasv_xfer_unique ( SRC_FILE, DEST_SERVER [, DEST_FILE ] ) + +Like C but the file is stored on the remote server using +the STOU command. + +=item pasv_wait ( NON_PASV_SERVER ) + +This method can be used to wait for a transfer to complete between a passive +server and a non-passive server. The method should be called on the passive +server with the C object for the non-passive server passed as an +argument. + +=item abort () + +Abort the current data transfer. + +=item quit () + +Send the QUIT command to the remote FTP server and close the socket connection. + +=back + +=head2 Methods for the adventurous + +=over 4 + +=item quot (CMD [,ARGS]) + +Send a command, that Net::FTP does not directly support, to the remote +server and wait for a response. + +Returns most significant digit of the response code. + +B This call should only be used on commands that do not require +data connections. Misuse of this method can hang the connection. + +=item can_inet6 () + +Returns whether we can use IPv6. + +=item can_ssl () + +Returns whether we can use SSL. + +=back + +=head1 THE dataconn CLASS + +Some of the methods defined in C return an object which will +be derived from the C class. See L for +more details. + +=head1 UNIMPLEMENTED + +The following RFC959 commands have not been implemented: + +=over 4 + +=item B + +Mount a different file system structure without changing login or +accounting information. + +=item B + +Ask the server for "helpful information" (that's what the RFC says) on +the commands it accepts. + +=item B + +Specifies transfer mode (stream, block or compressed) for file to be +transferred. + +=item B + +Request remote server system identification. + +=item B + +Request remote server status. + +=item B + +Specifies file structure for file to be transferred. + +=item B + +Reinitialize the connection, flushing all I/O and account information. + +=back + +=head1 REPORTING BUGS + +When reporting bugs/problems please include as much information as possible. +It may be difficult for me to reproduce the problem as almost every setup +is different. + +A small script which yields the problem will probably be of help. It would +also be useful if this script was run with the extra options C<< Debug => 1 >> +passed to the constructor, and the output sent with the bug report. If you +cannot include a small script then please include a Debug trace from a +run of your program which does yield the problem. + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 SEE ALSO + +L, +L, +L + +ftp(1), ftpd(8), RFC 959, RFC 2428, RFC 4217 +http://www.ietf.org/rfc/rfc959.txt +http://www.ietf.org/rfc/rfc2428.txt +http://www.ietf.org/rfc/rfc4217.txt + +=head1 USE EXAMPLES + +For an example of the use of Net::FTP see + +=over 4 + +=item http://www.csh.rit.edu/~adam/Progs/ + +C is a program that can retrieve, send, or list files via +the FTP protocol in a non-interactive manner. + +=back + +=head1 CREDITS + +Henry Gabryjelski - for the suggestion of creating directories +recursively. + +Nathan Torkington - for some input on the documentation. + +Roderick Schertler - for various inputs + +=head1 COPYRIGHT + +Copyright (C) 1995-2004 Graham Barr. All rights reserved. + +Copyright (C) 2013-2017 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/FTP/A.pm b/lib/Net/FTP/A.pm new file mode 100644 index 0000000..0ea1ba2 --- /dev/null +++ b/lib/Net/FTP/A.pm @@ -0,0 +1,115 @@ +## +## Package to read/write on ASCII data connections +## + +package Net::FTP::A; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Net::FTP::dataconn; + +our @ISA = qw(Net::FTP::dataconn); +our $VERSION = "3.11"; + +our $buf; + +sub read { + my $data = shift; + local *buf = \$_[0]; + shift; + my $size = shift || croak 'read($buf,$size,[$offset])'; + my $timeout = @_ ? shift: $data->timeout; + + if (length(${*$data}) < $size && !${*$data}{'net_ftp_eof'}) { + my $blksize = ${*$data}{'net_ftp_blksize'}; + $blksize = $size if $size > $blksize; + + my $l = 0; + my $n; + + READ: + { + my $readbuf = defined(${*$data}{'net_ftp_cr'}) ? "\015" : ''; + + $data->can_read($timeout) + or croak "Timeout"; + + if ($n = sysread($data, $readbuf, $blksize, length $readbuf)) { + ${*$data}{'net_ftp_bytesread'} += $n; + ${*$data}{'net_ftp_cr'} = + substr($readbuf, -1) eq "\015" + ? chop($readbuf) + : undef; + } + else { + return + unless defined $n; + + ${*$data}{'net_ftp_eof'} = 1; + } + + $readbuf =~ s/\015\012/\n/sgo; + ${*$data} .= $readbuf; + + unless (length(${*$data})) { + + redo READ + if ($n > 0); + + $size = length(${*$data}) + if ($n == 0); + } + } + } + + $buf = substr(${*$data}, 0, $size); + substr(${*$data}, 0, $size) = ''; + + length $buf; +} + + +sub write { + my $data = shift; + local *buf = \$_[0]; + shift; + my $size = shift || croak 'write($buf,$size,[$timeout])'; + my $timeout = @_ ? shift: $data->timeout; + + my $nr = (my $tmp = substr($buf, 0, $size)) =~ tr/\r\n/\015\012/; + $tmp =~ s/(?can_write($timeout) + or croak "Timeout"; + + $off += $wrote; + $wrote = syswrite($data, substr($tmp, $off), $len > $blksize ? $blksize : $len); + return + unless defined($wrote); + $len -= $wrote; + } + + $size; +} + +1; diff --git a/lib/Net/FTP/E.pm b/lib/Net/FTP/E.pm new file mode 100644 index 0000000..30b371a --- /dev/null +++ b/lib/Net/FTP/E.pm @@ -0,0 +1,13 @@ +package Net::FTP::E; + +use 5.008001; + +use strict; +use warnings; + +use Net::FTP::I; + +our @ISA = qw(Net::FTP::I); +our $VERSION = "3.11"; + +1; diff --git a/lib/Net/FTP/I.pm b/lib/Net/FTP/I.pm new file mode 100644 index 0000000..ec46ab0 --- /dev/null +++ b/lib/Net/FTP/I.pm @@ -0,0 +1,84 @@ +## +## Package to read/write on BINARY data connections +## + +package Net::FTP::I; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Net::FTP::dataconn; + +our @ISA = qw(Net::FTP::dataconn); +our $VERSION = "3.11"; + +our $buf; + +sub read { + my $data = shift; + local *buf = \$_[0]; + shift; + my $size = shift || croak 'read($buf,$size,[$timeout])'; + my $timeout = @_ ? shift: $data->timeout; + + my $n; + + if ($size > length ${*$data} and !${*$data}{'net_ftp_eof'}) { + $data->can_read($timeout) + or croak "Timeout"; + + my $blksize = ${*$data}{'net_ftp_blksize'}; + $blksize = $size if $size > $blksize; + + unless ($n = sysread($data, ${*$data}, $blksize, length ${*$data})) { + return unless defined $n; + ${*$data}{'net_ftp_eof'} = 1; + } + } + + $buf = substr(${*$data}, 0, $size); + + $n = length($buf); + + substr(${*$data}, 0, $n) = ''; + + ${*$data}{'net_ftp_bytesread'} += $n; + + $n; +} + + +sub write { + my $data = shift; + local *buf = \$_[0]; + shift; + my $size = shift || croak 'write($buf,$size,[$timeout])'; + my $timeout = @_ ? shift: $data->timeout; + + # If the remote server has closed the connection we will be signal'd + # when we write. This can happen if the disk on the remote server fills up + + local $SIG{PIPE} = 'IGNORE' + unless ($SIG{PIPE} || '') eq 'IGNORE' + or $^O eq 'MacOS'; + my $sent = $size; + my $off = 0; + + my $blksize = ${*$data}{'net_ftp_blksize'}; + while ($sent > 0) { + $data->can_write($timeout) + or croak "Timeout"; + + my $n = syswrite($data, $buf, $sent > $blksize ? $blksize : $sent, $off); + return unless defined($n); + $sent -= $n; + $off += $n; + } + + $size; +} + +1; diff --git a/lib/Net/FTP/L.pm b/lib/Net/FTP/L.pm new file mode 100644 index 0000000..d9a8857 --- /dev/null +++ b/lib/Net/FTP/L.pm @@ -0,0 +1,13 @@ +package Net::FTP::L; + +use 5.008001; + +use strict; +use warnings; + +use Net::FTP::I; + +our @ISA = qw(Net::FTP::I); +our $VERSION = "3.11"; + +1; diff --git a/lib/Net/FTP/dataconn.pm b/lib/Net/FTP/dataconn.pm new file mode 100644 index 0000000..337b0e9 --- /dev/null +++ b/lib/Net/FTP/dataconn.pm @@ -0,0 +1,182 @@ +## +## Generic data connection package +## + +package Net::FTP::dataconn; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Errno; +use Net::Cmd; + +our $VERSION = '3.11'; + +$Net::FTP::IOCLASS or die "please load Net::FTP before Net::FTP::dataconn"; +our @ISA = $Net::FTP::IOCLASS; + +sub reading { + my $data = shift; + ${*$data}{'net_ftp_bytesread'} = 0; +} + + +sub abort { + my $data = shift; + my $ftp = ${*$data}{'net_ftp_cmd'}; + + # no need to abort if we have finished the xfer + return $data->close + if ${*$data}{'net_ftp_eof'}; + + # for some reason if we continuously open RETR connections and not + # read a single byte, then abort them after a while the server will + # close our connection, this prevents the unexpected EOF on the + # command channel -- GMB + if (exists ${*$data}{'net_ftp_bytesread'} + && (${*$data}{'net_ftp_bytesread'} == 0)) + { + my $buf = ""; + my $timeout = $data->timeout; + $data->can_read($timeout) && sysread($data, $buf, 1); + } + + ${*$data}{'net_ftp_eof'} = 1; # fake + + $ftp->abort; # this will close me +} + + +sub _close { + my $data = shift; + my $ftp = ${*$data}{'net_ftp_cmd'}; + + $data->SUPER::close(); + + delete ${*$ftp}{'net_ftp_dataconn'} + if defined $ftp + && exists ${*$ftp}{'net_ftp_dataconn'} + && $data == ${*$ftp}{'net_ftp_dataconn'}; +} + + +sub close { + my $data = shift; + my $ftp = ${*$data}{'net_ftp_cmd'}; + + if (exists ${*$data}{'net_ftp_bytesread'} && !${*$data}{'net_ftp_eof'}) { + my $junk; + eval { local($SIG{__DIE__}); $data->read($junk, 1, 0) }; + return $data->abort unless ${*$data}{'net_ftp_eof'}; + } + + $data->_close; + + return unless defined $ftp; + + $ftp->response() == CMD_OK + && $ftp->message =~ /unique file name:\s*(\S*)\s*\)/ + && (${*$ftp}{'net_ftp_unique'} = $1); + + $ftp->status == CMD_OK; +} + + +sub _select { + my ($data, $timeout, $do_read) = @_; + my ($rin, $rout, $win, $wout, $tout, $nfound); + + vec($rin = '', fileno($data), 1) = 1; + + ($win, $rin) = ($rin, $win) unless $do_read; + + while (1) { + $nfound = select($rout = $rin, $wout = $win, undef, $tout = $timeout); + + last if $nfound >= 0; + + croak "select: $!" + unless $!{EINTR}; + } + + $nfound; +} + + +sub can_read { + _select(@_[0, 1], 1); +} + + +sub can_write { + _select(@_[0, 1], 0); +} + + +sub cmd { + my $ftp = shift; + + ${*$ftp}{'net_ftp_cmd'}; +} + + +sub bytes_read { + my $ftp = shift; + + ${*$ftp}{'net_ftp_bytesread'} || 0; +} + +1; + +__END__ + +=head1 NAME + +Net::FTP::dataconn - FTP Client data connection class + +=head1 DESCRIPTION + +Some of the methods defined in C return an object which will +be derived from this class. The dataconn class itself is derived from +the C class, so any normal IO operations can be performed. +However the following methods are defined in the dataconn class and IO should +be performed using these. + +=over 4 + +=item read ( BUFFER, SIZE [, TIMEOUT ] ) + +Read C bytes of data from the server and place it into C, also +performing any translation necessary. C is optional, if not +given, the timeout value from the command connection will be used. + +Returns the number of bytes read before any translation. + +=item write ( BUFFER, SIZE [, TIMEOUT ] ) + +Write C bytes of data from C to the server, also +performing any translation necessary. C is optional, if not +given, the timeout value from the command connection will be used. + +Returns the number of bytes written before any translation. + +=item bytes_read () + +Returns the number of bytes read so far. + +=item abort () + +Abort the current data transfer. + +=item close () + +Close the data connection and get a response from the FTP server. Returns +I if the connection was closed successfully and the first digit of +the response from the server was a '2'. + +=back + +=cut diff --git a/lib/Net/NNTP.pm b/lib/Net/NNTP.pm new file mode 100644 index 0000000..0c22930 --- /dev/null +++ b/lib/Net/NNTP.pm @@ -0,0 +1,1302 @@ +# Net::NNTP.pm +# +# Copyright (C) 1995-1997 Graham Barr. All rights reserved. +# Copyright (C) 2013-2016 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::NNTP; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use IO::Socket; +use Net::Cmd; +use Net::Config; +use Time::Local; + +our $VERSION = "3.11"; + +# Code for detecting if we can use SSL +my $ssl_class = eval { + require IO::Socket::SSL; + # first version with default CA on most platforms + no warnings 'numeric'; + IO::Socket::SSL->VERSION(2.007); +} && 'IO::Socket::SSL'; + +my $nossl_warn = !$ssl_class && + 'To use SSL please install IO::Socket::SSL with version>=2.007'; + +# Code for detecting if we can use IPv6 +my $family_key = 'Domain'; +my $inet6_class = eval { + require IO::Socket::IP; + no warnings 'numeric'; + IO::Socket::IP->VERSION(0.25) || die; + $family_key = 'Family'; +} && 'IO::Socket::IP' || eval { + require IO::Socket::INET6; + no warnings 'numeric'; + IO::Socket::INET6->VERSION(2.62); +} && 'IO::Socket::INET6'; + + +sub can_ssl { $ssl_class }; +sub can_inet6 { $inet6_class }; + +our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); + + +sub new { + my $self = shift; + my $type = ref($self) || $self; + my ($host, %arg); + if (@_ % 2) { + $host = shift; + %arg = @_; + } + else { + %arg = @_; + $host = delete $arg{Host}; + } + my $obj; + + $host ||= $ENV{NNTPSERVER} || $ENV{NEWSHOST}; + + my $hosts = defined $host ? [$host] : $NetConfig{nntp_hosts}; + + @{$hosts} = qw(news) + unless @{$hosts}; + + my %connect = ( Proto => 'tcp'); + + if ($arg{SSL}) { + # SSL from start + die $nossl_warn if ! $ssl_class; + $arg{Port} ||= 563; + $connect{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg); + } + + foreach my $o (qw(LocalAddr LocalPort Timeout)) { + $connect{$o} = $arg{$o} if exists $arg{$o}; + } + $connect{$family_key} = $arg{Domain} || $arg{Family}; + $connect{Timeout} = 120 unless defined $connect{Timeout}; + $connect{PeerPort} = $arg{Port} || 'nntp(119)'; + foreach my $h (@{$hosts}) { + $connect{PeerAddr} = $h; + $obj = $type->SUPER::new(%connect) or next; + ${*$obj}{'net_nntp_host'} = $h; + ${*$obj}{'net_nntp_arg'} = \%arg; + if ($arg{SSL}) { + Net::NNTP::_SSL->start_SSL($obj,%arg) or next; + } + last: + } + + return + unless defined $obj; + + $obj->autoflush(1); + $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); + + unless ($obj->response() == CMD_OK) { + $obj->close; + return; + } + + my $c = $obj->code; + my @m = $obj->message; + + unless (exists $arg{Reader} && $arg{Reader} == 0) { + + # if server is INN and we have transfer rights the we are currently + # talking to innd not nnrpd + if ($obj->reader) { + + # If reader succeeds the we need to consider this code to determine postok + $c = $obj->code; + } + else { + + # I want to ignore this failure, so restore the previous status. + $obj->set_status($c, \@m); + } + } + + ${*$obj}{'net_nntp_post'} = $c == 200 ? 1 : 0; + + $obj; +} + + +sub host { + my $me = shift; + ${*$me}{'net_nntp_host'}; +} + + +sub debug_text { + my $nntp = shift; + my $inout = shift; + my $text = shift; + + if ( (ref($nntp) and $nntp->code == 350 and $text =~ /^(\S+)/) + || ($text =~ /^(authinfo\s+pass)/io)) + { + $text = "$1 ....\n"; + } + + $text; +} + + +sub postok { + @_ == 1 or croak 'usage: $nntp->postok()'; + my $nntp = shift; + ${*$nntp}{'net_nntp_post'} || 0; +} + + +sub starttls { + my $self = shift; + $ssl_class or die $nossl_warn; + $self->_STARTTLS or return; + Net::NNTP::_SSL->start_SSL($self, + %{ ${*$self}{'net_nntp_arg'} }, # (ssl) args given in new + @_ # more (ssl) args + ) or return; + return 1; +} + + +sub article { + @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->article( [ MSGID ], [ FH ] )'; + my $nntp = shift; + my @fh; + + @fh = (pop) if @_ == 2 || (@_ && (ref($_[0]) || ref(\$_[0]) eq 'GLOB')); + + $nntp->_ARTICLE(@_) + ? $nntp->read_until_dot(@fh) + : undef; +} + + +sub articlefh { + @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->articlefh( [ MSGID ] )'; + my $nntp = shift; + + return unless $nntp->_ARTICLE(@_); + return $nntp->tied_fh; +} + + +sub authinfo { + @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )'; + my ($nntp, $user, $pass) = @_; + + $nntp->_AUTHINFO("USER", $user) == CMD_MORE + && $nntp->_AUTHINFO("PASS", $pass) == CMD_OK; +} + + +sub authinfo_simple { + @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )'; + my ($nntp, $user, $pass) = @_; + + $nntp->_AUTHINFO('SIMPLE') == CMD_MORE + && $nntp->command($user, $pass)->response == CMD_OK; +} + + +sub body { + @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->body( [ MSGID ], [ FH ] )'; + my $nntp = shift; + my @fh; + + @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB'); + + $nntp->_BODY(@_) + ? $nntp->read_until_dot(@fh) + : undef; +} + + +sub bodyfh { + @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->bodyfh( [ MSGID ] )'; + my $nntp = shift; + return unless $nntp->_BODY(@_); + return $nntp->tied_fh; +} + + +sub head { + @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->head( [ MSGID ], [ FH ] )'; + my $nntp = shift; + my @fh; + + @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB'); + + $nntp->_HEAD(@_) + ? $nntp->read_until_dot(@fh) + : undef; +} + + +sub headfh { + @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->headfh( [ MSGID ] )'; + my $nntp = shift; + return unless $nntp->_HEAD(@_); + return $nntp->tied_fh; +} + + +sub nntpstat { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->nntpstat( [ MSGID ] )'; + my $nntp = shift; + + $nntp->_STAT(@_) && $nntp->message =~ /(<[^>]+>)/o + ? $1 + : undef; +} + + +sub group { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->group( [ GROUP ] )'; + my $nntp = shift; + my $grp = ${*$nntp}{'net_nntp_group'}; + + return $grp + unless (@_ || wantarray); + + my $newgrp = shift; + + $newgrp = (defined($grp) and length($grp)) ? $grp : "" + unless defined($newgrp) and length($newgrp); + + return + unless $nntp->_GROUP($newgrp) and $nntp->message =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/; + + my ($count, $first, $last, $group) = ($1, $2, $3, $4); + + # group may be replied as '(current group)' + $group = ${*$nntp}{'net_nntp_group'} + if $group =~ /\(/; + + ${*$nntp}{'net_nntp_group'} = $group; + + wantarray + ? ($count, $first, $last, $group) + : $group; +} + + +sub help { + @_ == 1 or croak 'usage: $nntp->help()'; + my $nntp = shift; + + $nntp->_HELP + ? $nntp->read_until_dot + : undef; +} + + +sub ihave { + @_ >= 2 or croak 'usage: $nntp->ihave( MESSAGE-ID [, MESSAGE ])'; + my $nntp = shift; + my $mid = shift; + + $nntp->_IHAVE($mid) && $nntp->datasend(@_) + ? @_ == 0 || $nntp->dataend + : undef; +} + + +sub last { + @_ == 1 or croak 'usage: $nntp->last()'; + my $nntp = shift; + + $nntp->_LAST && $nntp->message =~ /(<[^>]+>)/o + ? $1 + : undef; +} + + +sub list { + @_ == 1 or croak 'usage: $nntp->list()'; + my $nntp = shift; + + $nntp->_LIST + ? $nntp->_grouplist + : undef; +} + + +sub newgroups { + @_ >= 2 or croak 'usage: $nntp->newgroups( SINCE [, DISTRIBUTIONS ])'; + my $nntp = shift; + my $time = _timestr(shift); + my $dist = shift || ""; + + $dist = join(",", @{$dist}) + if ref($dist); + + $nntp->_NEWGROUPS($time, $dist) + ? $nntp->_grouplist + : undef; +} + + +sub newnews { + @_ >= 2 && @_ <= 4 + or croak 'usage: $nntp->newnews( SINCE [, GROUPS [, DISTRIBUTIONS ]])'; + my $nntp = shift; + my $time = _timestr(shift); + my $grp = @_ ? shift: $nntp->group; + my $dist = shift || ""; + + $grp ||= "*"; + $grp = join(",", @{$grp}) + if ref($grp); + + $dist = join(",", @{$dist}) + if ref($dist); + + $nntp->_NEWNEWS($grp, $time, $dist) + ? $nntp->_articlelist + : undef; +} + + +sub next { + @_ == 1 or croak 'usage: $nntp->next()'; + my $nntp = shift; + + $nntp->_NEXT && $nntp->message =~ /(<[^>]+>)/o + ? $1 + : undef; +} + + +sub post { + @_ >= 1 or croak 'usage: $nntp->post( [ MESSAGE ] )'; + my $nntp = shift; + + $nntp->_POST() && $nntp->datasend(@_) + ? @_ == 0 || $nntp->dataend + : undef; +} + + +sub postfh { + my $nntp = shift; + return unless $nntp->_POST(); + return $nntp->tied_fh; +} + + +sub quit { + @_ == 1 or croak 'usage: $nntp->quit()'; + my $nntp = shift; + + $nntp->_QUIT; + $nntp->close; +} + + +sub slave { + @_ == 1 or croak 'usage: $nntp->slave()'; + my $nntp = shift; + + $nntp->_SLAVE; +} + +## +## The following methods are not implemented by all servers +## + + +sub active { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->active( [ PATTERN ] )'; + my $nntp = shift; + + $nntp->_LIST('ACTIVE', @_) + ? $nntp->_grouplist + : undef; +} + + +sub active_times { + @_ == 1 or croak 'usage: $nntp->active_times()'; + my $nntp = shift; + + $nntp->_LIST('ACTIVE.TIMES') + ? $nntp->_grouplist + : undef; +} + + +sub distributions { + @_ == 1 or croak 'usage: $nntp->distributions()'; + my $nntp = shift; + + $nntp->_LIST('DISTRIBUTIONS') + ? $nntp->_description + : undef; +} + + +sub distribution_patterns { + @_ == 1 or croak 'usage: $nntp->distributions()'; + my $nntp = shift; + + my $arr; + local $_; + + ## no critic (ControlStructures::ProhibitMutatingListFunctions) + $nntp->_LIST('DISTRIB.PATS') + && ($arr = $nntp->read_until_dot) + ? [grep { /^\d/ && (chomp, $_ = [split /:/]) } @$arr] + : undef; +} + + +sub newsgroups { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->newsgroups( [ PATTERN ] )'; + my $nntp = shift; + + $nntp->_LIST('NEWSGROUPS', @_) + ? $nntp->_description + : undef; +} + + +sub overview_fmt { + @_ == 1 or croak 'usage: $nntp->overview_fmt()'; + my $nntp = shift; + + $nntp->_LIST('OVERVIEW.FMT') + ? $nntp->_articlelist + : undef; +} + + +sub subscriptions { + @_ == 1 or croak 'usage: $nntp->subscriptions()'; + my $nntp = shift; + + $nntp->_LIST('SUBSCRIPTIONS') + ? $nntp->_articlelist + : undef; +} + + +sub listgroup { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->listgroup( [ GROUP ] )'; + my $nntp = shift; + + $nntp->_LISTGROUP(@_) + ? $nntp->_articlelist + : undef; +} + + +sub reader { + @_ == 1 or croak 'usage: $nntp->reader()'; + my $nntp = shift; + + $nntp->_MODE('READER'); +} + + +sub xgtitle { + @_ == 1 || @_ == 2 or croak 'usage: $nntp->xgtitle( [ PATTERN ] )'; + my $nntp = shift; + + $nntp->_XGTITLE(@_) + ? $nntp->_description + : undef; +} + + +sub xhdr { + @_ >= 2 && @_ <= 4 or croak 'usage: $nntp->xhdr( HEADER, [ MESSAGE-SPEC ] )'; + my $nntp = shift; + my $hdr = shift; + my $arg = _msg_arg(@_); + + $nntp->_XHDR($hdr, $arg) + ? $nntp->_description + : undef; +} + + +sub xover { + @_ == 2 || @_ == 3 or croak 'usage: $nntp->xover( MESSAGE-SPEC )'; + my $nntp = shift; + my $arg = _msg_arg(@_); + + $nntp->_XOVER($arg) + ? $nntp->_fieldlist + : undef; +} + + +sub xpat { + @_ == 4 || @_ == 5 or croak '$nntp->xpat( HEADER, PATTERN, MESSAGE-SPEC )'; + my $nntp = shift; + my $hdr = shift; + my $pat = shift; + my $arg = _msg_arg(@_); + + $pat = join(" ", @$pat) + if ref($pat); + + $nntp->_XPAT($hdr, $arg, $pat) + ? $nntp->_description + : undef; +} + + +sub xpath { + @_ == 2 or croak 'usage: $nntp->xpath( MESSAGE-ID )'; + my ($nntp, $mid) = @_; + + return + unless $nntp->_XPATH($mid); + + my $m; + ($m = $nntp->message) =~ s/^\d+\s+//o; + my @p = split /\s+/, $m; + + wantarray ? @p : $p[0]; +} + + +sub xrover { + @_ == 2 || @_ == 3 or croak 'usage: $nntp->xrover( MESSAGE-SPEC )'; + my $nntp = shift; + my $arg = _msg_arg(@_); + + $nntp->_XROVER($arg) + ? $nntp->_description + : undef; +} + + +sub date { + @_ == 1 or croak 'usage: $nntp->date()'; + my $nntp = shift; + + $nntp->_DATE + && $nntp->message =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ + ? timegm($6, $5, $4, $3, $2 - 1, $1 - 1900) + : undef; +} + + +## +## Private subroutines +## + + +sub _msg_arg { + my $spec = shift; + my $arg = ""; + + if (@_) { + carp "Depriciated passing of two message numbers, " . "pass a reference" + if $^W; + $spec = [$spec, $_[0]]; + } + + if (defined $spec) { + if (ref($spec)) { + $arg = $spec->[0]; + if (defined $spec->[1]) { + $arg .= "-" + if $spec->[1] != $spec->[0]; + $arg .= $spec->[1] + if $spec->[1] > $spec->[0]; + } + } + else { + $arg = $spec; + } + } + + $arg; +} + + +sub _timestr { + my $time = shift; + my @g = reverse((gmtime($time))[0 .. 5]); + $g[1] += 1; + $g[0] %= 100; + sprintf "%02d%02d%02d %02d%02d%02d GMT", @g; +} + + +sub _grouplist { + my $nntp = shift; + my $arr = $nntp->read_until_dot + or return; + + my $hash = {}; + + foreach my $ln (@$arr) { + my @a = split(/[\s\n]+/, $ln); + $hash->{$a[0]} = [@a[1, 2, 3]]; + } + + $hash; +} + + +sub _fieldlist { + my $nntp = shift; + my $arr = $nntp->read_until_dot + or return; + + my $hash = {}; + + foreach my $ln (@$arr) { + my @a = split(/[\t\n]/, $ln); + my $m = shift @a; + $hash->{$m} = [@a]; + } + + $hash; +} + + +sub _articlelist { + my $nntp = shift; + my $arr = $nntp->read_until_dot; + + chomp(@$arr) + if $arr; + + $arr; +} + + +sub _description { + my $nntp = shift; + my $arr = $nntp->read_until_dot + or return; + + my $hash = {}; + + foreach my $ln (@$arr) { + chomp($ln); + + $hash->{$1} = $ln + if $ln =~ s/^\s*(\S+)\s*//o; + } + + $hash; + +} + +## +## The commands +## + + +sub _ARTICLE { shift->command('ARTICLE', @_)->response == CMD_OK } +sub _AUTHINFO { shift->command('AUTHINFO', @_)->response } +sub _BODY { shift->command('BODY', @_)->response == CMD_OK } +sub _DATE { shift->command('DATE')->response == CMD_INFO } +sub _GROUP { shift->command('GROUP', @_)->response == CMD_OK } +sub _HEAD { shift->command('HEAD', @_)->response == CMD_OK } +sub _HELP { shift->command('HELP', @_)->response == CMD_INFO } +sub _IHAVE { shift->command('IHAVE', @_)->response == CMD_MORE } +sub _LAST { shift->command('LAST')->response == CMD_OK } +sub _LIST { shift->command('LIST', @_)->response == CMD_OK } +sub _LISTGROUP { shift->command('LISTGROUP', @_)->response == CMD_OK } +sub _NEWGROUPS { shift->command('NEWGROUPS', @_)->response == CMD_OK } +sub _NEWNEWS { shift->command('NEWNEWS', @_)->response == CMD_OK } +sub _NEXT { shift->command('NEXT')->response == CMD_OK } +sub _POST { shift->command('POST', @_)->response == CMD_MORE } +sub _QUIT { shift->command('QUIT', @_)->response == CMD_OK } +sub _SLAVE { shift->command('SLAVE', @_)->response == CMD_OK } +sub _STARTTLS { shift->command("STARTTLS")->response() == CMD_MORE } +sub _STAT { shift->command('STAT', @_)->response == CMD_OK } +sub _MODE { shift->command('MODE', @_)->response == CMD_OK } +sub _XGTITLE { shift->command('XGTITLE', @_)->response == CMD_OK } +sub _XHDR { shift->command('XHDR', @_)->response == CMD_OK } +sub _XPAT { shift->command('XPAT', @_)->response == CMD_OK } +sub _XPATH { shift->command('XPATH', @_)->response == CMD_OK } +sub _XOVER { shift->command('XOVER', @_)->response == CMD_OK } +sub _XROVER { shift->command('XROVER', @_)->response == CMD_OK } +sub _XTHREAD { shift->unsupported } +sub _XSEARCH { shift->unsupported } +sub _XINDEX { shift->unsupported } + +## +## IO/perl methods +## + + +sub DESTROY { + my $nntp = shift; + defined(fileno($nntp)) && $nntp->quit; +} + +{ + package Net::NNTP::_SSL; + our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::NNTP' ); + sub starttls { die "NNTP connection is already in SSL mode" } + sub start_SSL { + my ($class,$nntp,%arg) = @_; + delete @arg{ grep { !m{^SSL_} } keys %arg }; + ( $arg{SSL_verifycn_name} ||= $nntp->host ) + =~s{(?can_client_sni; + my $ok = $class->SUPER::start_SSL($nntp, + SSL_verifycn_scheme => 'nntp', + %arg + ); + $@ = $ssl_class->errstr if !$ok; + return $ok; + } +} + + + + +1; + +__END__ + +=head1 NAME + +Net::NNTP - NNTP Client class + +=head1 SYNOPSIS + + use Net::NNTP; + + $nntp = Net::NNTP->new("some.host.name"); + $nntp->quit; + + # start with SSL, e.g. nntps + $nntp = Net::NNTP->new("some.host.name", SSL => 1); + + # start with plain and upgrade to SSL + $nntp = Net::NNTP->new("some.host.name"); + $nntp->starttls; + + +=head1 DESCRIPTION + +C is a class implementing a simple NNTP client in Perl as described +in RFC977 and RFC4642. +With L installed it also provides support for implicit and +explicit TLS encryption, i.e. NNTPS or NNTP+STARTTLS. + +The Net::NNTP class is a subclass of Net::Cmd and (depending on avaibility) of +IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET. + +=head1 CONSTRUCTOR + +=over 4 + +=item new ( [ HOST ] [, OPTIONS ]) + +This is the constructor for a new Net::NNTP object. C is the +name of the remote host to which a NNTP connection is required. If not +given then it may be passed as the C option described below. If no host is passed +then two environment variables are checked, first C then +C, then C is checked, and if a host is not found +then C is used. + +C are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B - NNTP host to connect to. It may be a single scalar, as defined for +the C option in L, or a reference to +an array with hosts to try in turn. The L method will return the value +which was used to connect to the host. + +B - port to connect to. +Default - 119 for plain NNTP and 563 for immediate SSL (nntps). + +B - If the connection should be done from start with SSL, contrary to later +upgrade with C. +You can use SSL arguments as documented in L, but it will +usually use the right arguments already. + +B - Maximum time, in seconds, to wait for a response from the +NNTP server, a value of zero will cause all IO operations to block. +(default: 120) + +B - Enable the printing of debugging information to STDERR + +B - If the remote server is INN then initially the connection +will be to innd, by default C will issue a C command +so that the remote server becomes nnrpd. If the C option is given +with a value of zero, then this command will not be sent and the +connection will be left talking to innd. + +B and B - These parameters are passed directly +to IO::Socket to allow binding the socket to a specific local address and port. + +B - This parameter is passed directly to IO::Socket and makes it +possible to enforce IPv4 connections even if L is used as super +class. Alternatively B can be used. + +=back + +=head1 METHODS + +Unless otherwise stated all methods return either a I or I +value, with I meaning that the operation was a success. When a method +states that it returns a value, failure will be returned as I or an +empty list. + +C inherits from C so methods defined in C may +be used to send commands to the remote NNTP server in addition to the methods +documented here. + +=over 4 + +=item host () + +Returns the value used by the constructor, and passed to IO::Socket::INET, +to connect to the host. + +=item starttls () + +Upgrade existing plain connection to SSL. +Any arguments necessary for SSL must be given in C already. + +=item article ( [ MSGID|MSGNUM ], [FH] ) + +Retrieve the header, a blank line, then the body (text) of the +specified article. + +If C is specified then it is expected to be a valid filehandle +and the result will be printed to it, on success a true value will be +returned. If C is not specified then the return value, on success, +will be a reference to an array containing the article requested, each +entry in the array will contain one line of the article. + +If no arguments are passed then the current article in the currently +selected newsgroup is fetched. + +C is a numeric id of an article in the current newsgroup, and +will change the current article pointer. C is the message id of +an article as shown in that article's header. It is anticipated that the +client will obtain the C from a list provided by the C +command, from references contained within another article, or from the +message-id provided in the response to some other commands. + +If there is an error then C will be returned. + +=item body ( [ MSGID|MSGNUM ], [FH] ) + +Like C
but only fetches the body of the article. + +=item head ( [ MSGID|MSGNUM ], [FH] ) + +Like C
but only fetches the headers for the article. + +=item articlefh ( [ MSGID|MSGNUM ] ) + +=item bodyfh ( [ MSGID|MSGNUM ] ) + +=item headfh ( [ MSGID|MSGNUM ] ) + +These are similar to article(), body() and head(), but rather than +returning the requested data directly, they return a tied filehandle +from which to read the article. + +=item nntpstat ( [ MSGID|MSGNUM ] ) + +The C command is similar to the C
command except that no +text is returned. When selecting by message number within a group, +the C command serves to set the "current article pointer" without +sending text. + +Using the C command to +select by message-id is valid but of questionable value, since a +selection by message-id does B alter the "current article pointer". + +Returns the message-id of the "current article". + +=item group ( [ GROUP ] ) + +Set and/or get the current group. If C is not given then information +is returned on the current group. + +In a scalar context it returns the group name. + +In an array context the return value is a list containing, the number +of articles in the group, the number of the first article, the number +of the last article and the group name. + +=item help ( ) + +Request help text (a short summary of commands that are understood by this +implementation) from the server. Returns the text or undef upon failure. + +=item ihave ( MSGID [, MESSAGE ]) + +The C command informs the server that the client has an article +whose id is C. If the server desires a copy of that +article and C has been given then it will be sent. + +Returns I if the server desires the article and C was +successfully sent, if specified. + +If C is not specified then the message must be sent using the +C and C methods from L + +C can be either an array of lines or a reference to an array +and must be encoded by the caller to octets of whatever encoding is required, +e.g. by using the Encode module's C function. + +=item last () + +Set the "current article pointer" to the previous article in the current +newsgroup. + +Returns the message-id of the article. + +=item date () + +Returns the date on the remote server. This date will be in a UNIX time +format (seconds since 1970) + +=item postok () + +C will return I if the servers initial response indicated +that it will allow posting. + +=item authinfo ( USER, PASS ) + +Authenticates to the server (using the original AUTHINFO USER / AUTHINFO PASS +form, defined in RFC2980) using the supplied username and password. Please +note that the password is sent in clear text to the server. This command +should not be used with valuable passwords unless the connection to the server +is somehow protected. + +=item authinfo_simple ( USER, PASS ) + +Authenticates to the server (using the proposed NNTP V2 AUTHINFO SIMPLE form, +defined and deprecated in RFC2980) using the supplied username and password. +As with L the password is sent in clear text. + +=item list () + +Obtain information about all the active newsgroups. The results is a reference +to a hash where the key is a group name and each value is a reference to an +array. The elements in this array are:- the last article number in the group, +the first article number in the group and any information flags about the group. + +=item newgroups ( SINCE [, DISTRIBUTIONS ]) + +C is a time value and C is either a distribution +pattern or a reference to a list of distribution patterns. +The result is the same as C, but the +groups return will be limited to those created after C and, if +specified, in one of the distribution areas in C. + +=item newnews ( SINCE [, GROUPS [, DISTRIBUTIONS ]]) + +C is a time value. C is either a group pattern or a reference +to a list of group patterns. C is either a distribution +pattern or a reference to a list of distribution patterns. + +Returns a reference to a list which contains the message-ids of all news posted +after C, that are in a groups which matched C and a +distribution which matches C. + +=item next () + +Set the "current article pointer" to the next article in the current +newsgroup. + +Returns the message-id of the article. + +=item post ( [ MESSAGE ] ) + +Post a new article to the news server. If C is specified and posting +is allowed then the message will be sent. + +If C is not specified then the message must be sent using the +C and C methods from L + +C can be either an array of lines or a reference to an array +and must be encoded by the caller to octets of whatever encoding is required, +e.g. by using the Encode module's C function. + +The message, either sent via C or as the C +parameter, must be in the format as described by RFC822 and must +contain From:, Newsgroups: and Subject: headers. + +=item postfh () + +Post a new article to the news server using a tied filehandle. If +posting is allowed, this method will return a tied filehandle that you +can print() the contents of the article to be posted. You must +explicitly close() the filehandle when you are finished posting the +article, and the return value from the close() call will indicate +whether the message was successfully posted. + +=item slave () + +Tell the remote server that I am not a user client, but probably another +news server. + +=item quit () + +Quit the remote server and close the socket connection. + +=item can_inet6 () + +Returns whether we can use IPv6. + +=item can_ssl () + +Returns whether we can use SSL. + +=back + +=head2 Extension methods + +These methods use commands that are not part of the RFC977 documentation. Some +servers may not support all of them. + +=over 4 + +=item newsgroups ( [ PATTERN ] ) + +Returns a reference to a hash where the keys are all the group names which +match C, or all of the groups if no pattern is specified, and +each value contains the description text for the group. + +=item distributions () + +Returns a reference to a hash where the keys are all the possible +distribution names and the values are the distribution descriptions. + +=item distribution_patterns () + +Returns a reference to an array where each element, itself an array +reference, consists of the three fields of a line of the distrib.pats list +maintained by some NNTP servers, namely: a weight, a wildmat and a value +which the client may use to construct a Distribution header. + +=item subscriptions () + +Returns a reference to a list which contains a list of groups which +are recommended for a new user to subscribe to. + +=item overview_fmt () + +Returns a reference to an array which contain the names of the fields returned +by C. + +=item active_times () + +Returns a reference to a hash where the keys are the group names and each +value is a reference to an array containing the time the groups was created +and an identifier, possibly an Email address, of the creator. + +=item active ( [ PATTERN ] ) + +Similar to C but only active groups that match the pattern are returned. +C can be a group pattern. + +=item xgtitle ( PATTERN ) + +Returns a reference to a hash where the keys are all the group names which +match C and each value is the description text for the group. + +=item xhdr ( HEADER, MESSAGE-SPEC ) + +Obtain the header field C
for all the messages specified. + +The return value will be a reference +to a hash where the keys are the message numbers and each value contains +the text of the requested header for that message. + +=item xover ( MESSAGE-SPEC ) + +The return value will be a reference +to a hash where the keys are the message numbers and each value contains +a reference to an array which contains the overview fields for that +message. + +The names of the fields can be obtained by calling C. + +=item xpath ( MESSAGE-ID ) + +Returns the path name to the file on the server which contains the specified +message. + +=item xpat ( HEADER, PATTERN, MESSAGE-SPEC) + +The result is the same as C except the is will be restricted to +headers where the text of the header matches C + +=item xrover () + +The XROVER command returns reference information for the article(s) +specified. + +Returns a reference to a HASH where the keys are the message numbers and the +values are the References: lines from the articles + +=item listgroup ( [ GROUP ] ) + +Returns a reference to a list of all the active messages in C, or +the current group if C is not specified. + +=item reader () + +Tell the server that you are a reader and not another server. + +This is required by some servers. For example if you are connecting to +an INN server and you have transfer permission your connection will +be connected to the transfer daemon, not the NNTP daemon. Issuing +this command will cause the transfer daemon to hand over control +to the NNTP daemon. + +Some servers do not understand this command, but issuing it and ignoring +the response is harmless. + +=back + +=head1 UNSUPPORTED + +The following NNTP command are unsupported by the package, and there are +no plans to do so. + + AUTHINFO GENERIC + XTHREAD + XSEARCH + XINDEX + +=head1 DEFINITIONS + +=over 4 + +=item MESSAGE-SPEC + +C is either a single message-id, a single message number, or +a reference to a list of two message numbers. + +If C is a reference to a list of two message numbers and the +second number in a range is less than or equal to the first then the range +represents all messages in the group after the first message number. + +B For compatibility reasons only with earlier versions of Net::NNTP +a message spec can be passed as a list of two numbers, this is deprecated +and a reference to the list should now be passed + +=item PATTERN + +The C protocol uses the C format for patterns. +The WILDMAT format was first developed by Rich Salz based on +the format used in the UNIX "find" command to articulate +file names. It was developed to provide a uniform mechanism +for matching patterns in the same manner that the UNIX shell +matches filenames. + +Patterns are implicitly anchored at the +beginning and end of each string when testing for a match. + +There are five pattern matching operations other than a strict +one-to-one match between the pattern and the source to be +checked for a match. + +The first is an asterisk C<*> to match any sequence of zero or more +characters. + +The second is a question mark C to match any single character. The +third specifies a specific set of characters. + +The set is specified as a list of characters, or as a range of characters +where the beginning and end of the range are separated by a minus (or dash) +character, or as any combination of lists and ranges. The dash can +also be included in the set as a character it if is the beginning +or end of the set. This set is enclosed in square brackets. The +close square bracket C<]> may be used in a set if it is the first +character in the set. + +The fourth operation is the same as the +logical not of the third operation and is specified the same +way as the third with the addition of a caret character C<^> at +the beginning of the test string just inside the open square +bracket. + +The final operation uses the backslash character to +invalidate the special meaning of an open square bracket C<[>, +the asterisk, backslash or the question mark. Two backslashes in +sequence will result in the evaluation of the backslash as a +character with no special meaning. + +=over 4 + +=item Examples + +=item C<[^]-]> + +matches any single character other than a close square +bracket or a minus sign/dash. + +=item C<*bdc> + +matches any string that ends with the string "bdc" +including the string "bdc" (without quotes). + +=item C<[0-9a-zA-Z]> + +matches any single printable alphanumeric ASCII character. + +=item C + +matches any four character string which begins +with a and ends with d. + +=back + +=back + +=head1 SEE ALSO + +L, +L + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-1997 Graham Barr. All rights reserved. + +Copyright (C) 2013-2016 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/Netrc.pm b/lib/Net/Netrc.pm new file mode 100644 index 0000000..46fba27 --- /dev/null +++ b/lib/Net/Netrc.pm @@ -0,0 +1,347 @@ +# Net::Netrc.pm +# +# Copyright (C) 1995-1998 Graham Barr. All rights reserved. +# Copyright (C) 2013-2014 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::Netrc; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use FileHandle; + +our $VERSION = "3.11"; + +our $TESTING; + +my %netrc = (); + +sub _readrc { + my($class, $host) = @_; + my ($home, $file); + + if ($^O eq "MacOS") { + $home = $ENV{HOME} || `pwd`; + chomp($home); + $file = ($home =~ /:$/ ? $home . "netrc" : $home . ":netrc"); + } + else { + + # Some OS's don't have "getpwuid", so we default to $ENV{HOME} + $home = eval { (getpwuid($>))[7] } || $ENV{HOME}; + $home ||= $ENV{HOMEDRIVE} . ($ENV{HOMEPATH} || '') if defined $ENV{HOMEDRIVE}; + if (-e $home . "/.netrc") { + $file = $home . "/.netrc"; + } + elsif (-e $home . "/_netrc") { + $file = $home . "/_netrc"; + } + else { + return unless $TESTING; + } + } + + my ($login, $pass, $acct) = (undef, undef, undef); + my $fh; + local $_; + + $netrc{default} = undef; + + # OS/2 and Win32 do not handle stat in a way compatible with this check :-( + unless ($^O eq 'os2' + || $^O eq 'MSWin32' + || $^O eq 'MacOS' + || $^O =~ /^cygwin/) + { + my @stat = stat($file); + + if (@stat) { + if ($stat[2] & 077) { ## no critic (ValuesAndExpressions::ProhibitLeadingZeros) + carp "Bad permissions: $file"; + return; + } + if ($stat[4] != $<) { + carp "Not owner: $file"; + return; + } + } + } + + if ($fh = FileHandle->new($file, "r")) { + my ($mach, $macdef, $tok, @tok) = (0, 0); + + while (<$fh>) { + undef $macdef if /\A\n\Z/; + + if ($macdef) { + push(@$macdef, $_); + next; + } + + s/^\s*//; + chomp; + + while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) { + (my $tok = $+) =~ s/\\(.)/$1/g; + push(@tok, $tok); + } + + TOKEN: + while (@tok) { + if ($tok[0] eq "default") { + shift(@tok); + $mach = bless {}, $class; + $netrc{default} = [$mach]; + + next TOKEN; + } + + last TOKEN + unless @tok > 1; + + $tok = shift(@tok); + + if ($tok eq "machine") { + my $host = shift @tok; + $mach = bless {machine => $host}, $class; + + $netrc{$host} = [] + unless exists($netrc{$host}); + push(@{$netrc{$host}}, $mach); + } + elsif ($tok =~ /^(login|password|account)$/) { + next TOKEN unless $mach; + my $value = shift @tok; + + # Following line added by rmerrell to remove '/' escape char in .netrc + $value =~ s/\/\\/\\/g; + $mach->{$1} = $value; + } + elsif ($tok eq "macdef") { + next TOKEN unless $mach; + my $value = shift @tok; + $mach->{macdef} = {} + unless exists $mach->{macdef}; + $macdef = $mach->{machdef}{$value} = []; + } + } + } + $fh->close(); + } +} + + +sub lookup { + my ($class, $mach, $login) = @_; + + $class->_readrc() + unless exists $netrc{default}; + + $mach ||= 'default'; + undef $login + if $mach eq 'default'; + + if (exists $netrc{$mach}) { + if (defined $login) { + foreach my $m (@{$netrc{$mach}}) { + return $m + if (exists $m->{login} && $m->{login} eq $login); + } + return; + } + return $netrc{$mach}->[0]; + } + + return $netrc{default}->[0] + if defined $netrc{default}; + + return; +} + + +sub login { + my $me = shift; + + exists $me->{login} + ? $me->{login} + : undef; +} + + +sub account { + my $me = shift; + + exists $me->{account} + ? $me->{account} + : undef; +} + + +sub password { + my $me = shift; + + exists $me->{password} + ? $me->{password} + : undef; +} + + +sub lpa { + my $me = shift; + ($me->login, $me->password, $me->account); +} + +1; + +__END__ + +=head1 NAME + +Net::Netrc - OO interface to users netrc file + +=head1 SYNOPSIS + + use Net::Netrc; + + $mach = Net::Netrc->lookup('some.machine'); + $login = $mach->login; + ($login, $password, $account) = $mach->lpa; + +=head1 DESCRIPTION + +C is a class implementing a simple interface to the .netrc file +used as by the ftp program. + +C also implements security checks just like the ftp program, +these checks are, first that the .netrc file must be owned by the user and +second the ownership permissions should be such that only the owner has +read and write access. If these conditions are not met then a warning is +output and the .netrc file is not read. + +=head1 THE .netrc FILE + +The .netrc file contains login and initialization information used by the +auto-login process. It resides in the user's home directory. The following +tokens are recognized; they may be separated by spaces, tabs, or new-lines: + +=over 4 + +=item machine name + +Identify a remote machine name. The auto-login process searches +the .netrc file for a machine token that matches the remote machine +specified. Once a match is made, the subsequent .netrc tokens +are processed, stopping when the end of file is reached or an- +other machine or a default token is encountered. + +=item default + +This is the same as machine name except that default matches +any name. There can be only one default token, and it must be +after all machine tokens. This is normally used as: + + default login anonymous password user@site + +thereby giving the user automatic anonymous login to machines +not specified in .netrc. + +=item login name + +Identify a user on the remote machine. If this token is present, +the auto-login process will initiate a login using the +specified name. + +=item password string + +Supply a password. If this token is present, the auto-login +process will supply the specified string if the remote server +requires a password as part of the login process. + +=item account string + +Supply an additional account password. If this token is present, +the auto-login process will supply the specified string +if the remote server requires an additional account password. + +=item macdef name + +Define a macro. C only parses this field to be compatible +with I. + +=back + +=head1 CONSTRUCTOR + +The constructor for a C object is not called new as it does not +really create a new object. But instead is called C as this is +essentially what it does. + +=over 4 + +=item lookup ( MACHINE [, LOGIN ]) + +Lookup and return a reference to the entry for C. If C is given +then the entry returned will have the given login. If C is not given then +the first entry in the .netrc file for C will be returned. + +If a matching entry cannot be found, and a default entry exists, then a +reference to the default entry is returned. + +If there is no matching entry found and there is no default defined, or +no .netrc file is found, then C is returned. + +=back + +=head1 METHODS + +=over 4 + +=item login () + +Return the login id for the netrc entry + +=item password () + +Return the password for the netrc entry + +=item account () + +Return the account information for the netrc entry + +=item lpa () + +Return a list of login, password and account information for the netrc entry + +=back + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 SEE ALSO + +L, +L + +=head1 COPYRIGHT + +Copyright (C) 1995-1998 Graham Barr. All rights reserved. + +Copyright (C) 2013-2014 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/POP3.pm b/lib/Net/POP3.pm new file mode 100644 index 0000000..0811025 --- /dev/null +++ b/lib/Net/POP3.pm @@ -0,0 +1,863 @@ +# Net::POP3.pm +# +# Copyright (C) 1995-2004 Graham Barr. All rights reserved. +# Copyright (C) 2013-2016 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::POP3; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use IO::Socket; +use Net::Cmd; +use Net::Config; + +our $VERSION = "3.11"; + +# Code for detecting if we can use SSL +my $ssl_class = eval { + require IO::Socket::SSL; + # first version with default CA on most platforms + no warnings 'numeric'; + IO::Socket::SSL->VERSION(2.007); +} && 'IO::Socket::SSL'; + +my $nossl_warn = !$ssl_class && + 'To use SSL please install IO::Socket::SSL with version>=2.007'; + +# Code for detecting if we can use IPv6 +my $family_key = 'Domain'; +my $inet6_class = eval { + require IO::Socket::IP; + no warnings 'numeric'; + IO::Socket::IP->VERSION(0.25) || die; + $family_key = 'Family'; +} && 'IO::Socket::IP' || eval { + require IO::Socket::INET6; + no warnings 'numeric'; + IO::Socket::INET6->VERSION(2.62); +} && 'IO::Socket::INET6'; + + +sub can_ssl { $ssl_class }; +sub can_inet6 { $inet6_class }; + +our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); + +sub new { + my $self = shift; + my $type = ref($self) || $self; + my ($host, %arg); + if (@_ % 2) { + $host = shift; + %arg = @_; + } + else { + %arg = @_; + $host = delete $arg{Host}; + } + my $hosts = defined $host ? [$host] : $NetConfig{pop3_hosts}; + my $obj; + + if ($arg{SSL}) { + # SSL from start + die $nossl_warn if !$ssl_class; + $arg{Port} ||= 995; + } + + $arg{Timeout} = 120 if ! defined $arg{Timeout}; + + foreach my $h (@{$hosts}) { + $obj = $type->SUPER::new( + PeerAddr => ($host = $h), + PeerPort => $arg{Port} || 'pop3(110)', + Proto => 'tcp', + $family_key => $arg{Domain} || $arg{Family}, + LocalAddr => $arg{LocalAddr}, + LocalPort => exists($arg{ResvPort}) ? $arg{ResvPort} : $arg{LocalPort}, + Timeout => $arg{Timeout}, + ) + and last; + } + + return + unless defined $obj; + + ${*$obj}{'net_pop3_arg'} = \%arg; + ${*$obj}{'net_pop3_host'} = $host; + if ($arg{SSL}) { + Net::POP3::_SSL->start_SSL($obj,%arg) or return; + } + + $obj->autoflush(1); + $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); + + unless ($obj->response() == CMD_OK) { + $obj->close(); + return; + } + + ${*$obj}{'net_pop3_banner'} = $obj->message; + + $obj; +} + + +sub host { + my $me = shift; + ${*$me}{'net_pop3_host'}; +} + +## +## We don't want people sending me their passwords when they report problems +## now do we :-) +## + + +sub debug_text { $_[2] =~ /^(pass|rpop)/i ? "$1 ....\n" : $_[2]; } + + +sub login { + @_ >= 1 && @_ <= 3 or croak 'usage: $pop3->login( USER, PASS )'; + my ($me, $user, $pass) = @_; + + if (@_ <= 2) { + ($user, $pass) = $me->_lookup_credentials($user); + } + + $me->user($user) + and $me->pass($pass); +} + +sub starttls { + my $self = shift; + $ssl_class or die $nossl_warn; + $self->_STLS or return; + Net::POP3::_SSL->start_SSL($self, + %{ ${*$self}{'net_pop3_arg'} }, # (ssl) args given in new + @_ # more (ssl) args + ) or return; + return 1; +} + +sub apop { + @_ >= 1 && @_ <= 3 or croak 'usage: $pop3->apop( USER, PASS )'; + my ($me, $user, $pass) = @_; + my $banner; + my $md; + + if (eval { local $SIG{__DIE__}; require Digest::MD5 }) { + $md = Digest::MD5->new(); + } + elsif (eval { local $SIG{__DIE__}; require MD5 }) { + $md = MD5->new(); + } + else { + carp "You need to install Digest::MD5 or MD5 to use the APOP command"; + return; + } + + return + unless ($banner = (${*$me}{'net_pop3_banner'} =~ /(<.*>)/)[0]); + + if (@_ <= 2) { + ($user, $pass) = $me->_lookup_credentials($user); + } + + $md->add($banner, $pass); + + return + unless ($me->_APOP($user, $md->hexdigest)); + + $me->_get_mailbox_count(); +} + + +sub user { + @_ == 2 or croak 'usage: $pop3->user( USER )'; + $_[0]->_USER($_[1]) ? 1 : undef; +} + + +sub pass { + @_ == 2 or croak 'usage: $pop3->pass( PASS )'; + + my ($me, $pass) = @_; + + return + unless ($me->_PASS($pass)); + + $me->_get_mailbox_count(); +} + + +sub reset { + @_ == 1 or croak 'usage: $obj->reset()'; + + my $me = shift; + + return 0 + unless ($me->_RSET); + + if (defined ${*$me}{'net_pop3_mail'}) { + local $_; + foreach (@{${*$me}{'net_pop3_mail'}}) { + delete $_->{'net_pop3_deleted'}; + } + } +} + + +sub last { + @_ == 1 or croak 'usage: $obj->last()'; + + return + unless $_[0]->_LAST && $_[0]->message =~ /(\d+)/; + + return $1; +} + + +sub top { + @_ == 2 || @_ == 3 or croak 'usage: $pop3->top( MSGNUM [, NUMLINES ])'; + my $me = shift; + + return + unless $me->_TOP($_[0], $_[1] || 0); + + $me->read_until_dot; +} + + +sub popstat { + @_ == 1 or croak 'usage: $pop3->popstat()'; + my $me = shift; + + return () + unless $me->_STAT && $me->message =~ /(\d+)\D+(\d+)/; + + ($1 || 0, $2 || 0); +} + + +sub list { + @_ == 1 || @_ == 2 or croak 'usage: $pop3->list( [ MSGNUM ] )'; + my $me = shift; + + return + unless $me->_LIST(@_); + + if (@_) { + $me->message =~ /\d+\D+(\d+)/; + return $1 || undef; + } + + my $info = $me->read_until_dot + or return; + + my %hash = map { (/(\d+)\D+(\d+)/) } @$info; + + return \%hash; +} + + +sub get { + @_ == 2 or @_ == 3 or croak 'usage: $pop3->get( MSGNUM [, FH ])'; + my $me = shift; + + return + unless $me->_RETR(shift); + + $me->read_until_dot(@_); +} + + +sub getfh { + @_ == 2 or croak 'usage: $pop3->getfh( MSGNUM )'; + my $me = shift; + + return unless $me->_RETR(shift); + return $me->tied_fh; +} + + +sub delete { + @_ == 2 or croak 'usage: $pop3->delete( MSGNUM )'; + my $me = shift; + return 0 unless $me->_DELE(@_); + ${*$me}{'net_pop3_deleted'} = 1; +} + + +sub uidl { + @_ == 1 || @_ == 2 or croak 'usage: $pop3->uidl( [ MSGNUM ] )'; + my $me = shift; + my $uidl; + + $me->_UIDL(@_) + or return; + if (@_) { + $uidl = ($me->message =~ /\d+\s+([\041-\176]+)/)[0]; + } + else { + my $ref = $me->read_until_dot + or return; + $uidl = {}; + foreach my $ln (@$ref) { + my ($msg, $uid) = $ln =~ /^\s*(\d+)\s+([\041-\176]+)/; + $uidl->{$msg} = $uid; + } + } + return $uidl; +} + + +sub ping { + @_ == 2 or croak 'usage: $pop3->ping( USER )'; + my $me = shift; + + return () unless $me->_PING(@_) && $me->message =~ /(\d+)\D+(\d+)/; + + ($1 || 0, $2 || 0); +} + + +sub _lookup_credentials { + my ($me, $user) = @_; + + require Net::Netrc; + + $user ||= eval { local $SIG{__DIE__}; (getpwuid($>))[0] } + || $ENV{NAME} + || $ENV{USER} + || $ENV{LOGNAME}; + + my $m = Net::Netrc->lookup(${*$me}{'net_pop3_host'}, $user); + $m ||= Net::Netrc->lookup(${*$me}{'net_pop3_host'}); + + my $pass = $m + ? $m->password || "" + : ""; + + ($user, $pass); +} + + +sub _get_mailbox_count { + my ($me) = @_; + my $ret = ${*$me}{'net_pop3_count'} = + ($me->message =~ /(\d+)\s+message/io) ? $1 : ($me->popstat)[0]; + + $ret ? $ret : "0E0"; +} + + +sub _STAT { shift->command('STAT' )->response() == CMD_OK } +sub _LIST { shift->command('LIST', @_)->response() == CMD_OK } +sub _RETR { shift->command('RETR', $_[0])->response() == CMD_OK } +sub _DELE { shift->command('DELE', $_[0])->response() == CMD_OK } +sub _NOOP { shift->command('NOOP' )->response() == CMD_OK } +sub _RSET { shift->command('RSET' )->response() == CMD_OK } +sub _QUIT { shift->command('QUIT' )->response() == CMD_OK } +sub _TOP { shift->command( 'TOP', @_)->response() == CMD_OK } +sub _UIDL { shift->command('UIDL', @_)->response() == CMD_OK } +sub _USER { shift->command('USER', $_[0])->response() == CMD_OK } +sub _PASS { shift->command('PASS', $_[0])->response() == CMD_OK } +sub _APOP { shift->command('APOP', @_)->response() == CMD_OK } +sub _PING { shift->command('PING', $_[0])->response() == CMD_OK } +sub _RPOP { shift->command('RPOP', $_[0])->response() == CMD_OK } +sub _LAST { shift->command('LAST' )->response() == CMD_OK } +sub _CAPA { shift->command('CAPA' )->response() == CMD_OK } +sub _STLS { shift->command("STLS", )->response() == CMD_OK } + + +sub quit { + my $me = shift; + + $me->_QUIT; + $me->close; +} + + +sub DESTROY { + my $me = shift; + + if (defined fileno($me) and ${*$me}{'net_pop3_deleted'}) { + $me->reset; + $me->quit; + } +} + +## +## POP3 has weird responses, so we emulate them to look the same :-) +## + + +sub response { + my $cmd = shift; + my $str = $cmd->getline() or return; + my $code = "500"; + + $cmd->debug_print(0, $str) + if ($cmd->debug); + + if ($str =~ s/^\+OK\s*//io) { + $code = "200"; + } + elsif ($str =~ s/^\+\s*//io) { + $code = "300"; + } + else { + $str =~ s/^-ERR\s*//io; + } + + ${*$cmd}{'net_cmd_resp'} = [$str]; + ${*$cmd}{'net_cmd_code'} = $code; + + substr($code, 0, 1); +} + + +sub capa { + my $this = shift; + my ($capa, %capabilities); + + # Fake a capability here + $capabilities{APOP} = '' if ($this->banner() =~ /<.*>/); + + if ($this->_CAPA()) { + $capabilities{CAPA} = 1; + $capa = $this->read_until_dot(); + %capabilities = (%capabilities, map {/^\s*(\S+)\s*(.*)/} @$capa); + } + else { + + # Check AUTH for SASL capabilities + if ($this->command('AUTH')->response() == CMD_OK) { + my $mechanism = $this->read_until_dot(); + $capabilities{SASL} = join " ", map {m/([A-Z0-9_-]+)/} @{$mechanism}; + } + } + + return ${*$this}{'net_pop3e_capabilities'} = \%capabilities; +} + + +sub capabilities { + my $this = shift; + + ${*$this}{'net_pop3e_capabilities'} || $this->capa; +} + + +sub auth { + my ($self, $username, $password) = @_; + + eval { + require MIME::Base64; + require Authen::SASL; + } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0; + + my $capa = $self->capa; + my $mechanisms = $capa->{SASL} || 'CRAM-MD5'; + + my $sasl; + + if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) { + $sasl = $username; + my $user_mech = $sasl->mechanism || ''; + my @user_mech = split(/\s+/, $user_mech); + my %user_mech; + @user_mech{@user_mech} = (); + + my @server_mech = split(/\s+/, $mechanisms); + my @mech = @user_mech + ? grep { exists $user_mech{$_} } @server_mech + : @server_mech; + unless (@mech) { + $self->set_status( + 500, + [ 'Client SASL mechanisms (', + join(', ', @user_mech), + ') do not match the SASL mechnism the server announces (', + join(', ', @server_mech), ')', + ] + ); + return 0; + } + + $sasl->mechanism(join(" ", @mech)); + } + else { + die "auth(username, password)" if not length $username; + $sasl = Authen::SASL->new( + mechanism => $mechanisms, + callback => { + user => $username, + pass => $password, + authname => $username, + } + ); + } + + # We should probably allow the user to pass the host, but I don't + # currently know and SASL mechanisms that are used by smtp that need it + my ($hostname) = split /:/, ${*$self}{'net_pop3_host'}; + my $client = eval { $sasl->client_new('pop', $hostname, 0) }; + + unless ($client) { + my $mech = $sasl->mechanism; + $self->set_status( + 500, + [ " Authen::SASL failure: $@", + '(please check if your local Authen::SASL installation', + "supports mechanism '$mech'" + ] + ); + return 0; + } + + my ($token) = $client->client_start + or do { + my $mech = $client->mechanism; + $self->set_status( + 500, + [ ' Authen::SASL failure: $client->client_start ', + "mechanism '$mech' hostname #$hostname#", + $client->error + ] + ); + return 0; + }; + + # We don't support sasl mechanisms that encrypt the socket traffic. + # todo that we would really need to change the ISA hierarchy + # so we don't inherit from IO::Socket, but instead hold it in an attribute + + my @cmd = ("AUTH", $client->mechanism); + my $code; + + push @cmd, MIME::Base64::encode_base64($token, '') + if defined $token and length $token; + + while (($code = $self->command(@cmd)->response()) == CMD_MORE) { + + my ($token) = $client->client_step(MIME::Base64::decode_base64(($self->message)[0])) or do { + $self->set_status( + 500, + [ ' Authen::SASL failure: $client->client_step ', + "mechanism '", $client->mechanism, " hostname #$hostname#, ", + $client->error + ] + ); + return 0; + }; + + @cmd = (MIME::Base64::encode_base64(defined $token ? $token : '', '')); + } + + $code == CMD_OK; +} + + +sub banner { + my $this = shift; + + return ${*$this}{'net_pop3_banner'}; +} + +{ + package Net::POP3::_SSL; + our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::POP3' ); + sub starttls { die "POP3 connection is already in SSL mode" } + sub start_SSL { + my ($class,$pop3,%arg) = @_; + delete @arg{ grep { !m{^SSL_} } keys %arg }; + ( $arg{SSL_verifycn_name} ||= $pop3->host ) + =~s{(?can_client_sni; + $arg{SSL_verifycn_scheme} ||= 'pop3'; + my $ok = $class->SUPER::start_SSL($pop3,%arg); + $@ = $ssl_class->errstr if !$ok; + return $ok; + } +} + + + +1; + +__END__ + +=head1 NAME + +Net::POP3 - Post Office Protocol 3 Client class (RFC1939) + +=head1 SYNOPSIS + + use Net::POP3; + + # Constructors + $pop = Net::POP3->new('pop3host'); + $pop = Net::POP3->new('pop3host', Timeout => 60); + $pop = Net::POP3->new('pop3host', SSL => 1, Timeout => 60); + + if ($pop->login($username, $password) > 0) { + my $msgnums = $pop->list; # hashref of msgnum => size + foreach my $msgnum (keys %$msgnums) { + my $msg = $pop->get($msgnum); + print @$msg; + $pop->delete($msgnum); + } + } + + $pop->quit; + +=head1 DESCRIPTION + +This module implements a client interface to the POP3 protocol, enabling +a perl5 application to talk to POP3 servers. This documentation assumes +that you are familiar with the POP3 protocol described in RFC1939. +With L installed it also provides support for implicit and +explicit TLS encryption, i.e. POP3S or POP3+STARTTLS. + +A new Net::POP3 object must be created with the I method. Once +this has been done, all POP3 commands are accessed via method calls +on the object. + +The Net::POP3 class is a subclass of Net::Cmd and (depending on avaibility) of +IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET. + + +=head1 CONSTRUCTOR + +=over 4 + +=item new ( [ HOST ] [, OPTIONS ] ) + +This is the constructor for a new Net::POP3 object. C is the +name of the remote host to which an POP3 connection is required. + +C is optional. If C is not given then it may instead be +passed as the C option described below. If neither is given then +the C specified in C will be used. + +C are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B - POP3 host to connect to. It may be a single scalar, as defined for +the C option in L, or a reference to +an array with hosts to try in turn. The L method will return the value +which was used to connect to the host. + +B - port to connect to. +Default - 110 for plain POP3 and 995 for POP3s (direct SSL). + +B - If the connection should be done from start with SSL, contrary to later +upgrade with C. +You can use SSL arguments as documented in L, but it will +usually use the right arguments already. + +B and B - These parameters are passed directly +to IO::Socket to allow binding the socket to a specific local address and port. +For compatibility with older versions B can be used instead of +B. + +B - This parameter is passed directly to IO::Socket and makes it +possible to enforce IPv4 connections even if L is used as super +class. Alternatively B can be used. + +B - Maximum time, in seconds, to wait for a response from the +POP3 server (default: 120) + +B - Enable debugging information + +=back + +=head1 METHODS + +Unless otherwise stated all methods return either a I or I +value, with I meaning that the operation was a success. When a method +states that it returns a value, failure will be returned as I or an +empty list. + +C inherits from C so methods defined in C may +be used to send commands to the remote POP3 server in addition to the methods +documented here. + +=over 4 + +=item host () + +Returns the value used by the constructor, and passed to IO::Socket::INET, +to connect to the host. + +=item auth ( USERNAME, PASSWORD ) + +Attempt SASL authentication. + +=item user ( USER ) + +Send the USER command. + +=item pass ( PASS ) + +Send the PASS command. Returns the number of messages in the mailbox. + +=item login ( [ USER [, PASS ]] ) + +Send both the USER and PASS commands. If C is not given the +C uses C to lookup the password using the host +and username. If the username is not specified then the current user name +will be used. + +Returns the number of messages in the mailbox. However if there are no +messages on the server the string C<"0E0"> will be returned. This is +will give a true value in a boolean context, but zero in a numeric context. + +If there was an error authenticating the user then I will be returned. + +=item starttls ( SSLARGS ) + +Upgrade existing plain connection to SSL. +You can use SSL arguments as documented in L, but it will +usually use the right arguments already. + +=item apop ( [ USER [, PASS ]] ) + +Authenticate with the server identifying as C with password C. +Similar to L, but the password is not sent in clear text. + +To use this method you must have the Digest::MD5 or the MD5 module installed, +otherwise this method will return I. + +=item banner () + +Return the sever's connection banner + +=item capa () + +Return a reference to a hash of the capabilities of the server. APOP +is added as a pseudo capability. Note that I've been unable to +find a list of the standard capability values, and some appear to +be multi-word and some are not. We make an attempt at intelligently +parsing them, but it may not be correct. + +=item capabilities () + +Just like capa, but only uses a cache from the last time we asked +the server, so as to avoid asking more than once. + +=item top ( MSGNUM [, NUMLINES ] ) + +Get the header and the first C of the body for the message +C. Returns a reference to an array which contains the lines of text +read from the server. + +=item list ( [ MSGNUM ] ) + +If called with an argument the C returns the size of the message +in octets. + +If called without arguments a reference to a hash is returned. The +keys will be the C's of all undeleted messages and the values will +be their size in octets. + +=item get ( MSGNUM [, FH ] ) + +Get the message C from the remote mailbox. If C is not given +then get returns a reference to an array which contains the lines of +text read from the server. If C is given then the lines returned +from the server are printed to the filehandle C. + +=item getfh ( MSGNUM ) + +As per get(), but returns a tied filehandle. Reading from this +filehandle returns the requested message. The filehandle will return +EOF at the end of the message and should not be reused. + +=item last () + +Returns the highest C of all the messages accessed. + +=item popstat () + +Returns a list of two elements. These are the number of undeleted +elements and the size of the mbox in octets. + +=item ping ( USER ) + +Returns a list of two elements. These are the number of new messages +and the total number of messages for C. + +=item uidl ( [ MSGNUM ] ) + +Returns a unique identifier for C if given. If C is not +given C returns a reference to a hash where the keys are the +message numbers and the values are the unique identifiers. + +=item delete ( MSGNUM ) + +Mark message C to be deleted from the remote mailbox. All messages +that are marked to be deleted will be removed from the remote mailbox +when the server connection closed. + +=item reset () + +Reset the status of the remote POP3 server. This includes resetting the +status of all messages to not be deleted. + +=item quit () + +Quit and close the connection to the remote POP3 server. Any messages marked +as deleted will be deleted from the remote mailbox. + +=item can_inet6 () + +Returns whether we can use IPv6. + +=item can_ssl () + +Returns whether we can use SSL. + +=back + +=head1 NOTES + +If a C object goes out of scope before C method is called +then the C method will called before the connection is closed. This +means that any messages marked to be deleted will not be. + +=head1 SEE ALSO + +L, +L, +L + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-2004 Graham Barr. All rights reserved. + +Copyright (C) 2013-2016 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/SMTP.pm b/lib/Net/SMTP.pm new file mode 100644 index 0000000..5eaf422 --- /dev/null +++ b/lib/Net/SMTP.pm @@ -0,0 +1,1046 @@ +# Net::SMTP.pm +# +# Copyright (C) 1995-2004 Graham Barr. All rights reserved. +# Copyright (C) 2013-2016 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::SMTP; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use IO::Socket; +use Net::Cmd; +use Net::Config; +use Socket; + +our $VERSION = "3.11"; + +# Code for detecting if we can use SSL +my $ssl_class = eval { + require IO::Socket::SSL; + # first version with default CA on most platforms + no warnings 'numeric'; + IO::Socket::SSL->VERSION(2.007); +} && 'IO::Socket::SSL'; + +my $nossl_warn = !$ssl_class && + 'To use SSL please install IO::Socket::SSL with version>=2.007'; + +# Code for detecting if we can use IPv6 +my $family_key = 'Domain'; +my $inet6_class = eval { + require IO::Socket::IP; + no warnings 'numeric'; + IO::Socket::IP->VERSION(0.25) || die; + $family_key = 'Family'; +} && 'IO::Socket::IP' || eval { + require IO::Socket::INET6; + no warnings 'numeric'; + IO::Socket::INET6->VERSION(2.62); +} && 'IO::Socket::INET6'; + +sub can_ssl { $ssl_class }; +sub can_inet6 { $inet6_class }; + +our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); + +sub new { + my $self = shift; + my $type = ref($self) || $self; + my ($host, %arg); + if (@_ % 2) { + $host = shift; + %arg = @_; + } + else { + %arg = @_; + $host = delete $arg{Host}; + } + + if ($arg{SSL}) { + # SSL from start + die $nossl_warn if !$ssl_class; + $arg{Port} ||= 465; + } + + my $hosts = defined $host ? $host : $NetConfig{smtp_hosts}; + my $obj; + + $arg{Timeout} = 120 if ! defined $arg{Timeout}; + + foreach my $h (@{ref($hosts) ? $hosts : [$hosts]}) { + $obj = $type->SUPER::new( + PeerAddr => ($host = $h), + PeerPort => $arg{Port} || 'smtp(25)', + LocalAddr => $arg{LocalAddr}, + LocalPort => $arg{LocalPort}, + $family_key => $arg{Domain} || $arg{Family}, + Proto => 'tcp', + Timeout => $arg{Timeout} + ) + and last; + } + + return + unless defined $obj; + + ${*$obj}{'net_smtp_arg'} = \%arg; + ${*$obj}{'net_smtp_host'} = $host; + + if ($arg{SSL}) { + Net::SMTP::_SSL->start_SSL($obj,%arg) + or return; + } + + $obj->autoflush(1); + + $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); + + unless ($obj->response() == CMD_OK) { + my $err = ref($obj) . ": " . $obj->code . " " . $obj->message; + $obj->close(); + $@ = $err; + return; + } + + ${*$obj}{'net_smtp_exact_addr'} = $arg{ExactAddresses}; + + (${*$obj}{'net_smtp_banner'}) = $obj->message; + (${*$obj}{'net_smtp_domain'}) = $obj->message =~ /\A\s*(\S+)/; + + if (!exists $arg{SendHello} || $arg{SendHello}) { + unless ($obj->hello($arg{Hello} || "")) { + my $err = ref($obj) . ": " . $obj->code . " " . $obj->message; + $obj->close(); + $@ = $err; + return; + } + } + + $obj; +} + + +sub host { + my $me = shift; + ${*$me}{'net_smtp_host'}; +} + +## +## User interface methods +## + + +sub banner { + my $me = shift; + + return ${*$me}{'net_smtp_banner'} || undef; +} + + +sub domain { + my $me = shift; + + return ${*$me}{'net_smtp_domain'} || undef; +} + + +sub etrn { + my $self = shift; + defined($self->supports('ETRN', 500, ["Command unknown: 'ETRN'"])) + && $self->_ETRN(@_); +} + + +sub auth { + my ($self, $username, $password) = @_; + + eval { + require MIME::Base64; + require Authen::SASL; + } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0; + + my $mechanisms = $self->supports('AUTH', 500, ["Command unknown: 'AUTH'"]); + return unless defined $mechanisms; + + my $sasl; + + if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) { + $sasl = $username; + my $requested_mechanisms = $sasl->mechanism(); + if (! defined($requested_mechanisms) || $requested_mechanisms eq '') { + $sasl->mechanism($mechanisms); + } + } + else { + die "auth(username, password)" if not length $username; + $sasl = Authen::SASL->new( + mechanism => $mechanisms, + callback => { + user => $username, + pass => $password, + authname => $username, + }, + debug => $self->debug + ); + } + + my $client; + my $str; + do { + if ($client) { + # $client mechanism failed, so we need to exclude this mechanism from list + my $failed_mechanism = $client->mechanism; + return unless defined $failed_mechanism; + $self->debug_text("Auth mechanism failed: $failed_mechanism") + if $self->debug; + $mechanisms =~ s/\b\Q$failed_mechanism\E\b//; + return unless $mechanisms =~ /\S/; + $sasl->mechanism($mechanisms); + } + + # We should probably allow the user to pass the host, but I don't + # currently know and SASL mechanisms that are used by smtp that need it + + $client = $sasl->client_new('smtp', ${*$self}{'net_smtp_host'}, 0); + $str = $client->client_start; + } while (!defined $str); + + # We don't support sasl mechanisms that encrypt the socket traffic. + # todo that we would really need to change the ISA hierarchy + # so we don't inherit from IO::Socket, but instead hold it in an attribute + + my @cmd = ("AUTH", $client->mechanism); + my $code; + + push @cmd, MIME::Base64::encode_base64($str, '') + if defined $str and length $str; + + while (($code = $self->command(@cmd)->response()) == CMD_MORE) { + my $str2 = MIME::Base64::decode_base64(($self->message)[0]); + $self->debug_print(0, "(decoded) " . $str2 . "\n") if $self->debug; + + $str = $client->client_step($str2); + @cmd = ( + MIME::Base64::encode_base64($str, '') + ); + + $self->debug_print(1, "(decoded) " . $str . "\n") if $self->debug; + } + + $code == CMD_OK; +} + + +sub hello { + my $me = shift; + my $domain = shift || "localhost.localdomain"; + my $ok = $me->_EHLO($domain); + my @msg = $me->message; + + if ($ok) { + my $h = ${*$me}{'net_smtp_esmtp'} = {}; + foreach my $ln (@msg) { + $h->{uc $1} = $2 + if $ln =~ /([-\w]+)\b[= \t]*([^\n]*)/; + } + } + elsif ($me->status == CMD_ERROR) { + @msg = $me->message + if $ok = $me->_HELO($domain); + } + + return unless $ok; + ${*$me}{net_smtp_hello_domain} = $domain; + + $msg[0] =~ /\A\s*(\S+)/; + return ($1 || " "); +} + +sub starttls { + my $self = shift; + $ssl_class or die $nossl_warn; + $self->_STARTTLS or return; + Net::SMTP::_SSL->start_SSL($self, + %{ ${*$self}{'net_smtp_arg'} }, # (ssl) args given in new + @_ # more (ssl) args + ) or return; + + # another hello after starttls to read new ESMTP capabilities + return $self->hello(${*$self}{net_smtp_hello_domain}); +} + + +sub supports { + my $self = shift; + my $cmd = uc shift; + return ${*$self}{'net_smtp_esmtp'}->{$cmd} + if exists ${*$self}{'net_smtp_esmtp'}->{$cmd}; + $self->set_status(@_) + if @_; + return; +} + + +sub _addr { + my $self = shift; + my $addr = shift; + $addr = "" unless defined $addr; + + if (${*$self}{'net_smtp_exact_addr'}) { + return $1 if $addr =~ /^\s*(<.*>)\s*$/s; + } + else { + return $1 if $addr =~ /(<[^>]*>)/; + $addr =~ s/^\s+|\s+$//sg; + } + + "<$addr>"; +} + + +sub mail { + my $me = shift; + my $addr = _addr($me, shift); + my $opts = ""; + + if (@_) { + my %opt = @_; + my ($k, $v); + + if (exists ${*$me}{'net_smtp_esmtp'}) { + my $esmtp = ${*$me}{'net_smtp_esmtp'}; + + if (defined($v = delete $opt{Size})) { + if (exists $esmtp->{SIZE}) { + $opts .= sprintf " SIZE=%d", $v + 0; + } + else { + carp 'Net::SMTP::mail: SIZE option not supported by host'; + } + } + + if (defined($v = delete $opt{Return})) { + if (exists $esmtp->{DSN}) { + $opts .= " RET=" . ((uc($v) eq "FULL") ? "FULL" : "HDRS"); + } + else { + carp 'Net::SMTP::mail: DSN option not supported by host'; + } + } + + if (defined($v = delete $opt{Bits})) { + if ($v eq "8") { + if (exists $esmtp->{'8BITMIME'}) { + $opts .= " BODY=8BITMIME"; + } + else { + carp 'Net::SMTP::mail: 8BITMIME option not supported by host'; + } + } + elsif ($v eq "binary") { + if (exists $esmtp->{'BINARYMIME'} && exists $esmtp->{'CHUNKING'}) { + $opts .= " BODY=BINARYMIME"; + ${*$me}{'net_smtp_chunking'} = 1; + } + else { + carp 'Net::SMTP::mail: BINARYMIME option not supported by host'; + } + } + elsif (exists $esmtp->{'8BITMIME'} or exists $esmtp->{'BINARYMIME'}) { + $opts .= " BODY=7BIT"; + } + else { + carp 'Net::SMTP::mail: 8BITMIME and BINARYMIME options not supported by host'; + } + } + + if (defined($v = delete $opt{Transaction})) { + if (exists $esmtp->{CHECKPOINT}) { + $opts .= " TRANSID=" . _addr($me, $v); + } + else { + carp 'Net::SMTP::mail: CHECKPOINT option not supported by host'; + } + } + + if (defined($v = delete $opt{Envelope})) { + if (exists $esmtp->{DSN}) { + $v =~ s/([^\041-\176]|=|\+)/sprintf "+%02X", ord($1)/sge; + $opts .= " ENVID=$v"; + } + else { + carp 'Net::SMTP::mail: DSN option not supported by host'; + } + } + + if (defined($v = delete $opt{ENVID})) { + + # expected to be in a format as required by RFC 3461, xtext-encoded + if (exists $esmtp->{DSN}) { + $opts .= " ENVID=$v"; + } + else { + carp 'Net::SMTP::mail: DSN option not supported by host'; + } + } + + if (defined($v = delete $opt{AUTH})) { + + # expected to be in a format as required by RFC 2554, + # rfc2821-quoted and xtext-encoded, or <> + if (exists $esmtp->{AUTH}) { + $v = '<>' if !defined($v) || $v eq ''; + $opts .= " AUTH=$v"; + } + else { + carp 'Net::SMTP::mail: AUTH option not supported by host'; + } + } + + if (defined($v = delete $opt{XVERP})) { + if (exists $esmtp->{'XVERP'}) { + $opts .= " XVERP"; + } + else { + carp 'Net::SMTP::mail: XVERP option not supported by host'; + } + } + + carp 'Net::SMTP::recipient: unknown option(s) ' . join(" ", keys %opt) . ' - ignored' + if scalar keys %opt; + } + else { + carp 'Net::SMTP::mail: ESMTP not supported by host - options discarded :-('; + } + } + + $me->_MAIL("FROM:" . $addr . $opts); +} + + +sub send { my $me = shift; $me->_SEND("FROM:" . _addr($me, $_[0])) } +sub send_or_mail { my $me = shift; $me->_SOML("FROM:" . _addr($me, $_[0])) } +sub send_and_mail { my $me = shift; $me->_SAML("FROM:" . _addr($me, $_[0])) } + + +sub reset { + my $me = shift; + + $me->dataend() + if (exists ${*$me}{'net_smtp_lastch'}); + + $me->_RSET(); +} + + +sub recipient { + my $smtp = shift; + my $opts = ""; + my $skip_bad = 0; + + if (@_ && ref($_[-1])) { + my %opt = %{pop(@_)}; + my $v; + + $skip_bad = delete $opt{'SkipBad'}; + + if (exists ${*$smtp}{'net_smtp_esmtp'}) { + my $esmtp = ${*$smtp}{'net_smtp_esmtp'}; + + if (defined($v = delete $opt{Notify})) { + if (exists $esmtp->{DSN}) { + $opts .= " NOTIFY=" . join(",", map { uc $_ } @$v); + } + else { + carp 'Net::SMTP::recipient: DSN option not supported by host'; + } + } + + if (defined($v = delete $opt{ORcpt})) { + if (exists $esmtp->{DSN}) { + $opts .= " ORCPT=" . $v; + } + else { + carp 'Net::SMTP::recipient: DSN option not supported by host'; + } + } + + carp 'Net::SMTP::recipient: unknown option(s) ' . join(" ", keys %opt) . ' - ignored' + if scalar keys %opt; + } + elsif (%opt) { + carp 'Net::SMTP::recipient: ESMTP not supported by host - options discarded :-('; + } + } + + my @ok; + foreach my $addr (@_) { + if ($smtp->_RCPT("TO:" . _addr($smtp, $addr) . $opts)) { + push(@ok, $addr) if $skip_bad; + } + elsif (!$skip_bad) { + return 0; + } + } + + return $skip_bad ? @ok : 1; +} + +BEGIN { + *to = \&recipient; + *cc = \&recipient; + *bcc = \&recipient; +} + + +sub data { + my $me = shift; + + if (exists ${*$me}{'net_smtp_chunking'}) { + carp 'Net::SMTP::data: CHUNKING extension in use, must call bdat instead'; + } + else { + my $ok = $me->_DATA() && $me->datasend(@_); + + $ok && @_ + ? $me->dataend + : $ok; + } +} + + +sub bdat { + my $me = shift; + + if (exists ${*$me}{'net_smtp_chunking'}) { + my $data = shift; + + $me->_BDAT(length $data) + && $me->rawdatasend($data) + && $me->response() == CMD_OK; + } + else { + carp 'Net::SMTP::bdat: CHUNKING extension is not in use, call data instead'; + } +} + + +sub bdatlast { + my $me = shift; + + if (exists ${*$me}{'net_smtp_chunking'}) { + my $data = shift; + + $me->_BDAT(length $data, "LAST") + && $me->rawdatasend($data) + && $me->response() == CMD_OK; + } + else { + carp 'Net::SMTP::bdat: CHUNKING extension is not in use, call data instead'; + } +} + + +sub datafh { + my $me = shift; + return unless $me->_DATA(); + return $me->tied_fh; +} + + +sub expand { + my $me = shift; + + $me->_EXPN(@_) + ? ($me->message) + : (); +} + + +sub verify { shift->_VRFY(@_) } + + +sub help { + my $me = shift; + + $me->_HELP(@_) + ? scalar $me->message + : undef; +} + + +sub quit { + my $me = shift; + + $me->_QUIT; + $me->close; +} + + +sub DESTROY { + + # ignore +} + +## +## RFC821 commands +## + + +sub _EHLO { shift->command("EHLO", @_)->response() == CMD_OK } +sub _HELO { shift->command("HELO", @_)->response() == CMD_OK } +sub _MAIL { shift->command("MAIL", @_)->response() == CMD_OK } +sub _RCPT { shift->command("RCPT", @_)->response() == CMD_OK } +sub _SEND { shift->command("SEND", @_)->response() == CMD_OK } +sub _SAML { shift->command("SAML", @_)->response() == CMD_OK } +sub _SOML { shift->command("SOML", @_)->response() == CMD_OK } +sub _VRFY { shift->command("VRFY", @_)->response() == CMD_OK } +sub _EXPN { shift->command("EXPN", @_)->response() == CMD_OK } +sub _HELP { shift->command("HELP", @_)->response() == CMD_OK } +sub _RSET { shift->command("RSET")->response() == CMD_OK } +sub _NOOP { shift->command("NOOP")->response() == CMD_OK } +sub _QUIT { shift->command("QUIT")->response() == CMD_OK } +sub _DATA { shift->command("DATA")->response() == CMD_MORE } +sub _BDAT { shift->command("BDAT", @_) } +sub _TURN { shift->unsupported(@_); } +sub _ETRN { shift->command("ETRN", @_)->response() == CMD_OK } +sub _AUTH { shift->command("AUTH", @_)->response() == CMD_OK } +sub _STARTTLS { shift->command("STARTTLS")->response() == CMD_OK } + + +{ + package Net::SMTP::_SSL; + our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::SMTP' ); + sub starttls { die "SMTP connection is already in SSL mode" } + sub start_SSL { + my ($class,$smtp,%arg) = @_; + delete @arg{ grep { !m{^SSL_} } keys %arg }; + ( $arg{SSL_verifycn_name} ||= $smtp->host ) + =~s{(?can_client_sni; + $arg{SSL_verifycn_scheme} ||= 'smtp'; + my $ok = $class->SUPER::start_SSL($smtp,%arg); + $@ = $ssl_class->errstr if !$ok; + return $ok; + } +} + + + +1; + +__END__ + +=head1 NAME + +Net::SMTP - Simple Mail Transfer Protocol Client + +=head1 SYNOPSIS + + use Net::SMTP; + + # Constructors + $smtp = Net::SMTP->new('mailhost'); + $smtp = Net::SMTP->new('mailhost', Timeout => 60); + +=head1 DESCRIPTION + +This module implements a client interface to the SMTP and ESMTP +protocol, enabling a perl5 application to talk to SMTP servers. This +documentation assumes that you are familiar with the concepts of the +SMTP protocol described in RFC2821. +With L installed it also provides support for implicit and +explicit TLS encryption, i.e. SMTPS or SMTP+STARTTLS. + +The Net::SMTP class is a subclass of Net::Cmd and (depending on avaibility) of +IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET. + +=head1 EXAMPLES + +This example prints the mail domain name of the SMTP server known as mailhost: + + #!/usr/local/bin/perl -w + + use Net::SMTP; + + $smtp = Net::SMTP->new('mailhost'); + print $smtp->domain,"\n"; + $smtp->quit; + +This example sends a small message to the postmaster at the SMTP server +known as mailhost: + + #!/usr/local/bin/perl -w + + use Net::SMTP; + + my $smtp = Net::SMTP->new('mailhost'); + + $smtp->mail($ENV{USER}); + if ($smtp->to('postmaster')) { + $smtp->data(); + $smtp->datasend("To: postmaster\n"); + $smtp->datasend("\n"); + $smtp->datasend("A simple test message\n"); + $smtp->dataend(); + } else { + print "Error: ", $smtp->message(); + } + + $smtp->quit; + +=head1 CONSTRUCTOR + +=over 4 + +=item new ( [ HOST ] [, OPTIONS ] ) + +This is the constructor for a new Net::SMTP object. C is the +name of the remote host to which an SMTP connection is required. + +On failure C will be returned and C<$@> will contain the reason +for the failure. + +C is optional. If C is not given then it may instead be +passed as the C option described below. If neither is given then +the C specified in C will be used. + +C are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B - SMTP requires that you identify yourself. This option +specifies a string to pass as your mail domain. If not given localhost.localdomain +will be used. + +B - If false then the EHLO (or HELO) command that is normally sent +when constructing the object will not be sent. In that case the command will +have to be sent manually by calling C instead. + +B - SMTP host to connect to. It may be a single scalar (hostname[:port]), +as defined for the C option in L, or a reference to +an array with hosts to try in turn. The L method will return the value +which was used to connect to the host. +Format - C from L new method. + +B - port to connect to. +Default - 25 for plain SMTP and 465 for immediate SSL. + +B - If the connection should be done from start with SSL, contrary to later +upgrade with C. +You can use SSL arguments as documented in L, but it will +usually use the right arguments already. + +B and B - These parameters are passed directly +to IO::Socket to allow binding the socket to a specific local address and port. + +B - This parameter is passed directly to IO::Socket and makes it +possible to enforce IPv4 connections even if L is used as super +class. Alternatively B can be used. + +B - Maximum time, in seconds, to wait for a response from the +SMTP server (default: 120) + +B - If true the all ADDRESS arguments must be as +defined by C in RFC2822. If not given, or false, then +Net::SMTP will attempt to extract the address from the value passed. + +B - Enable debugging information + + +Example: + + + $smtp = Net::SMTP->new('mailhost', + Hello => 'my.mail.domain', + Timeout => 30, + Debug => 1, + ); + + # the same + $smtp = Net::SMTP->new( + Host => 'mailhost', + Hello => 'my.mail.domain', + Timeout => 30, + Debug => 1, + ); + + # the same with direct SSL + $smtp = Net::SMTP->new('mailhost', + Hello => 'my.mail.domain', + Timeout => 30, + Debug => 1, + SSL => 1, + ); + + # Connect to the default server from Net::config + $smtp = Net::SMTP->new( + Hello => 'my.mail.domain', + Timeout => 30, + ); + +=back + +=head1 METHODS + +Unless otherwise stated all methods return either a I or I +value, with I meaning that the operation was a success. When a method +states that it returns a value, failure will be returned as I or an +empty list. + +C inherits from C so methods defined in C may +be used to send commands to the remote SMTP server in addition to the methods +documented here. + +=over 4 + +=item banner () + +Returns the banner message which the server replied with when the +initial connection was made. + +=item domain () + +Returns the domain that the remote SMTP server identified itself as during +connection. + +=item hello ( DOMAIN ) + +Tell the remote server the mail domain which you are in using the EHLO +command (or HELO if EHLO fails). Since this method is invoked +automatically when the Net::SMTP object is constructed the user should +normally not have to call it manually. + +=item host () + +Returns the value used by the constructor, and passed to IO::Socket::INET, +to connect to the host. + +=item etrn ( DOMAIN ) + +Request a queue run for the DOMAIN given. + +=item starttls ( SSLARGS ) + +Upgrade existing plain connection to SSL. +You can use SSL arguments as documented in L, but it will +usually use the right arguments already. + +=item auth ( USERNAME, PASSWORD ) + +=item auth ( SASL ) + +Attempt SASL authentication. Requires Authen::SASL module. The first form +constructs a new Authen::SASL object using the given username and password; +the second form uses the given Authen::SASL object. + +=item mail ( ADDRESS [, OPTIONS] ) + +=item send ( ADDRESS ) + +=item send_or_mail ( ADDRESS ) + +=item send_and_mail ( ADDRESS ) + +Send the appropriate command to the server MAIL, SEND, SOML or SAML. C
+is the address of the sender. This initiates the sending of a message. The +method C should be called for each address that the message is to +be sent to. + +The C method can some additional ESMTP OPTIONS which is passed +in hash like fashion, using key and value pairs. Possible options are: + + Size => + Return => "FULL" | "HDRS" + Bits => "7" | "8" | "binary" + Transaction =>
+ Envelope => # xtext-encodes its argument + ENVID => # similar to Envelope, but expects argument encoded + XVERP => 1 + AUTH => # encoded address according to RFC 2554 + +The C and C parameters are used for DSN (Delivery +Status Notification). + +The submitter address in C option is expected to be in a format as +required by RFC 2554, in an RFC2821-quoted form and xtext-encoded, or <> . + +=item reset () + +Reset the status of the server. This may be called after a message has been +initiated, but before any data has been sent, to cancel the sending of the +message. + +=item recipient ( ADDRESS [, ADDRESS, [...]] [, OPTIONS ] ) + +Notify the server that the current message should be sent to all of the +addresses given. Each address is sent as a separate command to the server. +Should the sending of any address result in a failure then the process is +aborted and a I value is returned. It is up to the user to call +C if they so desire. + +The C method can also pass additional case-sensitive OPTIONS as an +anonymous hash using key and value pairs. Possible options are: + + Notify => ['NEVER'] or ['SUCCESS','FAILURE','DELAY'] (see below) + ORcpt => + SkipBad => 1 (to ignore bad addresses) + +If C is true the C will not return an error when a bad +address is encountered and it will return an array of addresses that did +succeed. + + $smtp->recipient($recipient1,$recipient2); # Good + $smtp->recipient($recipient1,$recipient2, { SkipBad => 1 }); # Good + $smtp->recipient($recipient1,$recipient2, { Notify => ['FAILURE','DELAY'], SkipBad => 1 }); # Good + @goodrecips=$smtp->recipient(@recipients, { Notify => ['FAILURE'], SkipBad => 1 }); # Good + $smtp->recipient("$recipient,$recipient2"); # BAD + +Notify is used to request Delivery Status Notifications (DSNs), but your +SMTP/ESMTP service may not respect this request depending upon its version and +your site's SMTP configuration. + +Leaving out the Notify option usually defaults an SMTP service to its default +behavior equivalent to ['FAILURE'] notifications only, but again this may be +dependent upon your site's SMTP configuration. + +The NEVER keyword must appear by itself if used within the Notify option and "requests +that a DSN not be returned to the sender under any conditions." + + {Notify => ['NEVER']} + + $smtp->recipient(@recipients, { Notify => ['NEVER'], SkipBad => 1 }); # Good + +You may use any combination of these three values 'SUCCESS','FAILURE','DELAY' in +the anonymous array reference as defined by RFC3461 (see http://www.ietf.org/rfc/rfc3461.txt +for more information. Note: quotations in this topic from same.). + +A Notify parameter of 'SUCCESS' or 'FAILURE' "requests that a DSN be issued on +successful delivery or delivery failure, respectively." + +A Notify parameter of 'DELAY' "indicates the sender's willingness to receive +delayed DSNs. Delayed DSNs may be issued if delivery of a message has been +delayed for an unusual amount of time (as determined by the Message Transfer +Agent (MTA) at which the message is delayed), but the final delivery status +(whether successful or failure) cannot be determined. The absence of the DELAY +keyword in a NOTIFY parameter requests that a "delayed" DSN NOT be issued under +any conditions." + + {Notify => ['SUCCESS','FAILURE','DELAY']} + + $smtp->recipient(@recipients, { Notify => ['FAILURE','DELAY'], SkipBad => 1 }); # Good + +ORcpt is also part of the SMTP DSN extension according to RFC3461. +It is used to pass along the original recipient that the mail was first +sent to. The machine that generates a DSN will use this address to inform +the sender, because he can't know if recipients get rewritten by mail servers. +It is expected to be in a format as required by RFC3461, xtext-encoded. + +=item to ( ADDRESS [, ADDRESS [...]] ) + +=item cc ( ADDRESS [, ADDRESS [...]] ) + +=item bcc ( ADDRESS [, ADDRESS [...]] ) + +Synonyms for C. + +=item data ( [ DATA ] ) + +Initiate the sending of the data from the current message. + +C may be a reference to a list or a list and must be encoded by the +caller to octets of whatever encoding is required, e.g. by using the Encode +module's C function. + +If specified the contents of C and a termination string C<".\r\n"> is +sent to the server. The result will be true if the data was accepted. + +If C is not specified then the result will indicate that the server +wishes the data to be sent. The data must then be sent using the C +and C methods described in L. + +=item bdat ( DATA ) + +=item bdatlast ( DATA ) + +Use the alternate DATA command "BDAT" of the data chunking service extension +defined in RFC1830 for efficiently sending large MIME messages. + +=item expand ( ADDRESS ) + +Request the server to expand the given address Returns an array +which contains the text read from the server. + +=item verify ( ADDRESS ) + +Verify that C
is a legitimate mailing address. + +Most sites usually disable this feature in their SMTP service configuration. +Use "Debug => 1" option under new() to see if disabled. + +=item help ( [ $subject ] ) + +Request help text from the server. Returns the text or undef upon failure + +=item quit () + +Send the QUIT command to the remote SMTP server and close the socket connection. + +=item can_inet6 () + +Returns whether we can use IPv6. + +=item can_ssl () + +Returns whether we can use SSL. + +=back + +=head1 ADDRESSES + +Net::SMTP attempts to DWIM with addresses that are passed. For +example an application might extract The From: line from an email +and pass that to mail(). While this may work, it is not recommended. +The application should really use a module like L +to extract the mail address and pass that. + +If C is passed to the constructor, then addresses +should be a valid rfc2821-quoted address, although Net::SMTP will +accept the address surrounded by angle brackets. + + funny user@domain WRONG + "funny user"@domain RIGHT, recommended + <"funny user"@domain> OK + +=head1 SEE ALSO + +L, +L + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-2004 Graham Barr. All rights reserved. + +Copyright (C) 2013-2016 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/Time.pm b/lib/Net/Time.pm new file mode 100644 index 0000000..d049408 --- /dev/null +++ b/lib/Net/Time.pm @@ -0,0 +1,164 @@ +# Net::Time.pm +# +# Copyright (C) 1995-2004 Graham Barr. All rights reserved. +# Copyright (C) 2014 Steve Hay. All rights reserved. +# This module is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU General +# Public License or the Artistic License, as specified in the F file. + +package Net::Time; + +use 5.008001; + +use strict; +use warnings; + +use Carp; +use Exporter; +use IO::Select; +use IO::Socket; +use Net::Config; + +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(inet_time inet_daytime); + +our $VERSION = "3.11"; + +our $TIMEOUT = 120; + +sub _socket { + my ($pname, $pnum, $host, $proto, $timeout) = @_; + + $proto ||= 'udp'; + + my $port = (getservbyname($pname, $proto))[2] || $pnum; + + my $hosts = defined $host ? [$host] : $NetConfig{$pname . '_hosts'}; + + my $me; + + foreach my $addr (@$hosts) { + $me = IO::Socket::INET->new( + PeerAddr => $addr, + PeerPort => $port, + Proto => $proto + ) + and last; + } + + return unless $me; + + $me->send("\n") + if $proto eq 'udp'; + + $timeout = $TIMEOUT + unless defined $timeout; + + IO::Select->new($me)->can_read($timeout) + ? $me + : undef; +} + + +sub inet_time { + my $s = _socket('time', 37, @_) || return; + my $buf = ''; + my $offset = 0 | 0; + + return + unless defined $s->recv($buf, length(pack("N", 0))); + + # unpack, we | 0 to ensure we have an unsigned + my $time = (unpack("N", $buf))[0] | 0; + + # the time protocol return time in seconds since 1900, convert + # it to a the required format + + if ($^O eq "MacOS") { + + # MacOS return seconds since 1904, 1900 was not a leap year. + $offset = (4 * 31536000) | 0; + } + else { + + # otherwise return seconds since 1972, there were 17 leap years between + # 1900 and 1972 + $offset = (70 * 31536000 + 17 * 86400) | 0; + } + + $time - $offset; +} + + +sub inet_daytime { + my $s = _socket('daytime', 13, @_) || return; + my $buf = ''; + + defined($s->recv($buf, 1024)) + ? $buf + : undef; +} + +1; + +__END__ + +=head1 NAME + +Net::Time - time and daytime network client interface + +=head1 SYNOPSIS + + use Net::Time qw(inet_time inet_daytime); + + print inet_time(); # use default host from Net::Config + print inet_time('localhost'); + print inet_time('localhost', 'tcp'); + + print inet_daytime(); # use default host from Net::Config + print inet_daytime('localhost'); + print inet_daytime('localhost', 'tcp'); + +=head1 DESCRIPTION + +C provides subroutines that obtain the time on a remote machine. + +=over 4 + +=item inet_time ( [HOST [, PROTOCOL [, TIMEOUT]]]) + +Obtain the time on C, or some default host if C is not given +or not defined, using the protocol as defined in RFC868. The optional +argument C should define the protocol to use, either C or +C. The result will be a time value in the same units as returned +by time() or I upon failure. + +=item inet_daytime ( [HOST [, PROTOCOL [, TIMEOUT]]]) + +Obtain the time on C, or some default host if C is not given +or not defined, using the protocol as defined in RFC867. The optional +argument C should define the protocol to use, either C or +C. The result will be an ASCII string or I upon failure. + +=back + +=head1 AUTHOR + +Graham Barr EFE. + +Steve Hay EFE is now maintaining libnet as of version +1.22_02. + +=head1 COPYRIGHT + +Copyright (C) 1995-2004 Graham Barr. All rights reserved. + +Copyright (C) 2014 Steve Hay. All rights reserved. + +=head1 LICENCE + +This module is free software; you can redistribute it and/or modify it under the +same terms as Perl itself, i.e. under the terms of either the GNU General Public +License or the Artistic License, as specified in the F file. + +=cut diff --git a/lib/Net/libnetFAQ.pod b/lib/Net/libnetFAQ.pod new file mode 100644 index 0000000..8cd15d3 --- /dev/null +++ b/lib/Net/libnetFAQ.pod @@ -0,0 +1,6 @@ +=encoding utf-8 + +The L. You can +access the original document on +L. diff --git a/t/changes.t b/t/changes.t new file mode 100644 index 0000000..8235c1b --- /dev/null +++ b/t/changes.t @@ -0,0 +1,48 @@ +#!perl +#=============================================================================== +# +# t/changes.t +# +# DESCRIPTION +# Test script to check CPAN::Changes conformance. +# +# COPYRIGHT +# Copyright (C) 2014 Steve Hay. All rights reserved. +# +# LICENCE +# This script is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU +# General Public License or the Artistic License, as specified in the LICENCE +# file. +# +#=============================================================================== + +use 5.008001; + +use strict; +use warnings; + +use Test::More; + +#=============================================================================== +# MAIN PROGRAM +#=============================================================================== + +MAIN: { + plan skip_all => 'Author testing only' unless $ENV{AUTHOR_TESTING}; + + my $ok = eval { + require Test::CPAN::Changes; + Test::CPAN::Changes->import(); + 1; + }; + + if (not $ok) { + plan skip_all => 'Test::CPAN::Changes required to test Changes'; + } + else { + changes_ok(); + } +} + +#=============================================================================== diff --git a/t/config.t b/t/config.t new file mode 100644 index 0000000..3c29a03 --- /dev/null +++ b/t/config.t @@ -0,0 +1,88 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + undef *{Socket::inet_aton}; + undef *{Socket::inet_ntoa}; + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } + $INC{'Socket.pm'} = 1; +} + +package Socket; + +sub import { + my $pkg = caller(); + no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict) + *{ $pkg . '::inet_aton' } = \&inet_aton; + *{ $pkg . '::inet_ntoa' } = \&inet_ntoa; +} + +my $fail = 0; +my %names; + +sub set_fail { + $fail = shift; +} + +sub inet_aton { + return if $fail; + my $num = unpack('N', pack('C*', split(/\./, $_[0]))); + $names{$num} = $_[0]; + return $num; +} + +sub inet_ntoa { + return if $fail; + return $names{$_[0]}; +} + +package main; + + +(my $libnet_t = __FILE__) =~ s/config.t/libnet_t.pl/; +require $libnet_t; + +print "1..10\n"; + +use Net::Config; +ok( exists $INC{'Net/Config.pm'}, 'Net::Config should have been used' ); +ok( keys %NetConfig, '%NetConfig should be imported' ); + +Socket::set_fail(1); +undef $NetConfig{'ftp_firewall'}; +is( Net::Config->requires_firewall(), 0, + 'requires_firewall() should return 0 without ftp_firewall defined' ); + +$NetConfig{'ftp_firewall'} = 1; +is( Net::Config->requires_firewall('a.host.not.there'), -1, + '... should return -1 without a valid hostname' ); + +Socket::set_fail(0); +delete $NetConfig{'local_netmask'}; +is( Net::Config->requires_firewall('127.0.0.1'), 0, + '... should return 0 without local_netmask defined' ); + +$NetConfig{'local_netmask'} = '127.0.0.1/24'; +is( Net::Config->requires_firewall('127.0.0.1'), 0, + '... should return false if host is within netmask' ); +is( Net::Config->requires_firewall('192.168.10.0'), 1, + '... should return true if host is outside netmask' ); + +# now try more netmasks +$NetConfig{'local_netmask'} = [ '127.0.0.1/24', '10.0.0.0/8' ]; +is( Net::Config->requires_firewall('10.10.255.254'), 0, + '... should find success with mutiple local netmasks' ); +is( Net::Config->requires_firewall('192.168.10.0'), 1, + '... should handle failure with multiple local netmasks' ); + +is( \&Net::Config::is_external, \&Net::Config::requires_firewall, + 'is_external() should be an alias for requires_firewall()' ); diff --git a/t/critic.t b/t/critic.t new file mode 100644 index 0000000..339dadb --- /dev/null +++ b/t/critic.t @@ -0,0 +1,48 @@ +#!perl +#=============================================================================== +# +# t/critic.t +# +# DESCRIPTION +# Test script to check Perl::Critic conformance. +# +# COPYRIGHT +# Copyright (C) 2014 Steve Hay. All rights reserved. +# +# LICENCE +# This script is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU +# General Public License or the Artistic License, as specified in the LICENCE +# file. +# +#=============================================================================== + +use 5.008001; + +use strict; +use warnings; + +use Test::More; + +#=============================================================================== +# MAIN PROGRAM +#=============================================================================== + +MAIN: { + plan skip_all => 'Author testing only' unless $ENV{AUTHOR_TESTING}; + + my $ok = eval { + require Test::Perl::Critic; + Test::Perl::Critic->import(-profile => ''); + 1; + }; + + if (not $ok) { + plan skip_all => 'Test::Perl::Critic required to test with Perl::Critic'; + } + else { + all_critic_ok('.'); + } +} + +#=============================================================================== diff --git a/t/datasend.t b/t/datasend.t new file mode 100644 index 0000000..0aea9d4 --- /dev/null +++ b/t/datasend.t @@ -0,0 +1,167 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +BEGIN { + package Foo; + + use IO::File; + use Net::Cmd; + our @ISA = qw(Net::Cmd IO::File); + + sub timeout { 0 } + + sub new { + my $fh = shift->new_tmpfile; + binmode($fh); + $fh; + } + + sub output { + my $self = shift; + seek($self,0,0); + local $/ = undef; + scalar(<$self>); + } + + sub response { + return Net::Cmd::CMD_OK; + } +} + +(my $libnet_t = __FILE__) =~ s/datasend.t/libnet_t.pl/; +require $libnet_t or die; + +print "1..54\n"; + +sub check { + my $expect = pop; + my $cmd = Foo->new; + ok($cmd->datasend, 'datasend') unless @_; + foreach my $line (@_) { + ok($cmd->datasend($line), 'datasend'); + } + ok($cmd->dataend, 'dataend'); + is( + unpack("H*",$cmd->output), + unpack("H*",$expect) + ); +} + +my $cmd; + +check( + # nothing + + ".\015\012" +); + +check( + "a", + + "a\015\012.\015\012", +); + +check( + "a\r", + + "a\015\015\012.\015\012", +); + +check( + "a\rb", + + "a\015b\015\012.\015\012", +); + +check( + "a\rb\n", + + "a\015b\015\012.\015\012", +); + +check( + "a\rb\n\n", + + "a\015b\015\012\015\012.\015\012", +); + +check( + "a\r", + "\nb", + + "a\015\012b\015\012.\015\012", +); + +check( + "a\r", + "\nb\n", + + "a\015\012b\015\012.\015\012", +); + +check( + "a\r", + "\nb\r\n", + + "a\015\012b\015\012.\015\012", +); + +check( + "a\r", + "\nb\r\n\n", + + "a\015\012b\015\012\015\012.\015\012", +); + +check( + "a\n.b\n", + + "a\015\012..b\015\012.\015\012", +); + +check( + ".a\n.b\n", + + "..a\015\012..b\015\012.\015\012", +); + +check( + ".a\n", + ".b\n", + + "..a\015\012..b\015\012.\015\012", +); + +check( + ".a", + ".b\n", + + "..a.b\015\012.\015\012", +); + +check( + "a\n.", + + "a\015\012..\015\012.\015\012", +); + +# Test that datasend() plays nicely with bytes in an upgraded string, +# even though the input should really be encode()d already. +check( + substr("\x{100}", 0, 0) . "\x{e9}", + + "\x{e9}\015\012.\015\012" +); diff --git a/t/external/ftp-ssl.t b/t/external/ftp-ssl.t new file mode 100644 index 0000000..28c038a --- /dev/null +++ b/t/external/ftp-ssl.t @@ -0,0 +1,173 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Net::FTP; +use Test::More; +use File::Temp; +use IO::Socket::INET; + +my $server = 'test.rebex.net'; +my $debug = 0; + +plan skip_all => "no SSL support" if ! Net::FTP->can_ssl; +require IO::Socket::SSL; + + +# first try to connect w/o ftp +# plain +diag( "connect inet to $server:21" ); +IO::Socket::INET->new( "$server:21" ) or do { + plan skip_all => "$server:21 not reachable"; +}; + +# ssl to the right host +diag( "connect inet to $server:990" ); +my $sock = IO::Socket::INET->new( "$server:990") or do { + plan skip_all => "$server:990 not reachable"; +}; + +# now we need CAs +my $cafh = File::Temp->new( UNLINK => 0, SUFFIX => '.crt' ); +my %sslargs = ( SSL_ca_file => $cafh->filename ); +print $cafh ; +close($cafh); + +diag( "upgrade to ssl $server:990" ); +IO::Socket::SSL->start_SSL($sock, + SSL_verify_mode => 1, + SSL_verifycn_name => $server, + SSL_verifycn_scheme => 'ftp', + %sslargs, +) or do { + plan skip_all => "$server:990 not upgradable to SSL: ". + $IO::Socket::SSL::SSL_ERROR; +}; + +plan tests => 9; + +# first direct SSL +diag( "connect ftp over ssl to $server" ); +my $ftp = Net::FTP->new($server, + SSL => 1, + %sslargs, + Debug => $debug, + Passive => 1, +); +ok($ftp,"ftp ssl connect $server"); +$ftp->login("anonymous",'net-sslglue-ftp@test.perl') + or die "login to $server failed"; +diag("logged in"); +# check that we can talk on connection +ok(~~$ftp->ls,"directory listing protected"); +$ftp->prot('C'); +ok(~~$ftp->ls,"directory listing clear"); + +# then TLS upgrade inside plain connection +$ftp = Net::FTP->new($server, + Passive => 1, + Debug => $debug, + %sslargs +); +ok($ftp,"ftp plain connect $server"); +my $ok = $ftp->starttls; +ok($ok,"ssl upgrade"); +$ftp->login("anonymous",'net-sslglue-ftp@test.perl') + or die "login to $server failed"; +diag("logged in"); +# check that we can talk on connection +ok(~~$ftp->ls,"directory listing protected"); +$ftp->prot('C'); +ok(~~$ftp->ls,"directory listing clear"); +$ok = $ftp->stoptls; +ok($ok,"ssl downgrade"); +ok(~~$ftp->ls,"directory listing after downgrade"); + + +__DATA__ +# Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 2 Primary Intermediate Server CA +# Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority +-----BEGIN CERTIFICATE----- +MIIGNDCCBBygAwIBAgIBGjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB +jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT +IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0 +YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh +G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8 +H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL +hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE +d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa +8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID +AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov +L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0 +YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3 +dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0 +c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu +BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0 +BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl +LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAnQfh7pB2MWcWRXCMy4SLS1doRKWJwfJ+ +yyiL9edwd9W29AshYKWhdHMkIoDW2LqNomJdCTVCKfs5Y0ULpLA4Gmj0lRPM4EOU +7Os5GuxXKdmZbfWEzY5zrsncavqenRZkkwjHHMKJVJ53gJD2uSl26xNnSFn4Ljox +uMnTiOVfTtIZPUOO15L/zzi24VuKUx3OrLR2L9j3QGPV7mnzRX2gYsFhw3XtsntN +rCEnME5ZRmqTF8rIOS0Bc2Vb6UGbERecyMhK76F2YC2uk/8M1TMTn08Tzt2G8fz4 +NVQVqFvnhX76Nwn/i7gxSZ4Nbt600hItuO3Iw/G2QqBMl3nf/sOjn6H0bSyEd6Si +BeEX/zHdmvO4esNSwhERt1Axin/M51qJzPeGmmGSTy+UtpjHeOBiS0N9PN7WmrQQ +oUCcSyrcuNDUnv3xhHgbDlePaVRCaHvqoO91DweijHOZq1X1BwnSrzgDapADDC+P +4uhDwjHpb62H5Y29TiyJS1HmnExUdsASgVOb7KD8LJzaGJVuHjgmQid4YAjff20y +6NjAbx/rJnWfk/x7G/41kNxTowemP4NVCitOYoIlzmYwXSzg+RkbdbmdmFamgyd6 +0Y+NWZP8P3PXLrQsldiL98l+x/ydrHIEH9LMF/TtNGCbnkqXBP7dcg5XVFEGcE3v +qhykguAzx/Q= +-----END CERTIFICATE----- +# Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority +# Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC +ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w +ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk +aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 +YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg +c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 +d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG +CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF +wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS +Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst +0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc +pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl +CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF +P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK +1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm +KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ +8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm +fyWl8kgAwKQB2j8= +-----END CERTIFICATE----- diff --git a/t/external/pop3-ssl.t b/t/external/pop3-ssl.t new file mode 100644 index 0000000..554a8db --- /dev/null +++ b/t/external/pop3-ssl.t @@ -0,0 +1,58 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Net::POP3; +use Test::More; + +my $host = 'pop.gmx.net'; +my $debug = 0; + +plan skip_all => "no SSL support" if ! Net::POP3->can_ssl; +{ +no warnings 'once'; +plan skip_all => "no verified SSL connection to $host:995 - $@" if ! eval { + IO::Socket::SSL->new(PeerAddr => "$host:995", Timeout => 10) + || die($IO::Socket::SSL::SSL_ERROR||$!); +}; +} + +plan tests => 2; + +SKIP: { + diag( "connect inet to $host:110" ); + skip "no inet connect to $host:110",1 + if ! IO::Socket::INET->new(PeerAddr => "$host:110", Timeout => 10); + my $pop3 = Net::POP3->new($host, Debug => $debug, Timeout => 10) + or skip "normal POP3 failed: $@",1; + skip "no STARTTLS support",1 if $pop3->message !~/STARTTLS/; + + if (!$pop3->starttls) { + fail("starttls failed: ".$pop3->code." $@") + } else { + # we now should have access to SSL stuff + my $cipher = eval { $pop3->get_cipher }; + if (!$cipher) { + fail("after starttls: not an SSL object"); + } elsif ( $pop3->quit ) { + pass("starttls + quit ok, cipher=$cipher"); + } else { + fail("quit after starttls failed: ".$pop3->code); + } + } +} + + +my $pop3 = Net::POP3->new($host, SSL => 1, Timeout => 10, Debug => $debug); +# we now should have access to SSL stuff +my $cipher = eval { $pop3->get_cipher }; +if (!$cipher) { + fail("after ssl connect: not an SSL object"); +} elsif ( $pop3->quit ) { + pass("ssl connect ok, cipher=$cipher"); +} else { + fail("quit after direct ssl failed: ".$pop3->code); +} diff --git a/t/external/smtp-ssl.t b/t/external/smtp-ssl.t new file mode 100644 index 0000000..ccacbae --- /dev/null +++ b/t/external/smtp-ssl.t @@ -0,0 +1,57 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Net::SMTP; +use Test::More; + +my $host = 'mail.gmx.net'; +my $debug = 0; + +plan skip_all => "no SSL support" if ! Net::SMTP->can_ssl; +{ +no warnings 'once'; +plan skip_all => "no verified SSL connection to $host:465 - $@" if ! eval { + IO::Socket::SSL->new("$host:465") + || die($IO::Socket::SSL::SSL_ERROR||$!); +}; +} + +plan tests => 2; + +SKIP: { + diag( "connect inet to $host:25" ); + skip "no inet connect to $host:25",1 if ! IO::Socket::INET->new("$host:25"); + my $smtp = Net::SMTP->new($host, Debug => $debug) + or skip "normal SMTP failed: $@",1; + skip "no STARTTLS support",1 if $smtp->message !~/STARTTLS/; + + if (!$smtp->starttls) { + fail("starttls failed: ".$smtp->code." $@") + } else { + # we now should have access to SSL stuff + my $cipher = eval { $smtp->get_cipher }; + if (!$cipher) { + fail("after starttls: not an SSL object"); + } elsif ( $smtp->quit ) { + pass("starttls + quit ok, cipher=$cipher"); + } else { + fail("quit after starttls failed: ".$smtp->code); + } + } +} + + +my $smtp = Net::SMTP->new($host, SSL => 1, Debug => $debug); +# we now should have access to SSL stuff +my $cipher = eval { $smtp->get_cipher }; +if (!$cipher) { + fail("after ssl connect: not an SSL object"); +} elsif ( $smtp->quit ) { + pass("ssl connect ok, cipher=$cipher"); +} else { + fail("quit after direct ssl failed: ".$smtp->code); +} diff --git a/t/ftp.t b/t/ftp.t new file mode 100644 index 0000000..16cb868 --- /dev/null +++ b/t/ftp.t @@ -0,0 +1,81 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # Skip: no Socket module\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # Skip: EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +use Net::Config; +use Net::FTP; + +unless(defined($NetConfig{ftp_testhost})) { + print "1..0 # Skip: no ftp_testhost defined in config\n"; + exit 0; +} + +unless($NetConfig{test_hosts}) { + print "1..0 # Skip: test_hosts not enabled in config\n"; + exit 0; +} + +my $t = 1; +print "1..7\n"; + +my $ftp = Net::FTP->new($NetConfig{ftp_testhost}) + or (print("not ok 1\n"), exit); + +printf "ok %d\n",$t++; + +$ftp->login('anonymous') or die($ftp->message . "\n"); +printf "ok %d\n",$t++; + +$ftp->pwd or do { + print STDERR $ftp->message,"\n"; + print "not "; +}; + +printf "ok %d\n",$t++; + +$ftp->cwd('/pub') or do { + print STDERR $ftp->message,"\n"; + print "not "; +}; + +my $data; +if ($data = $ftp->stor('libnet.tst')) { + my $text = "abc\ndef\nqwe\n"; + printf "ok %d\n",$t++; + $data->write($text,length $text); + $data->close; + $data = $ftp->retr('libnet.tst'); + my $buf; + $data->read($buf,length $text); + $data->close; + print "not " unless $text eq $buf; + printf "ok %d\n",$t++; + $ftp->delete('libnet.tst') or print "not "; + printf "ok %d\n",$t++; + +} +else { + print "# ",$ftp->message,"\n"; + printf "ok %d\n",$t++; + printf "ok %d\n",$t++; + printf "ok %d\n",$t++; +} + +$ftp->quit or do { + print STDERR $ftp->message,"\n"; + print "not "; +}; + +printf "ok %d\n",$t++; diff --git a/t/hostname.t b/t/hostname.t new file mode 100644 index 0000000..55031bf --- /dev/null +++ b/t/hostname.t @@ -0,0 +1,62 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +use Net::Domain qw(hostname domainname hostdomain hostfqdn); +use Net::Config; + +unless($NetConfig{test_hosts}) { + print "1..0\n"; + exit 0; +} + +print "1..5\n"; + +my $domain = domainname(); + +if(defined $domain && $domain ne "") { + print "ok 1 - defined, non-empty domainname\n"; +} +elsif (not defined $domain) { + print "ok 1 # SKIP domain not fully defined\n"; +} +else { + print "not ok 1\n"; +} + +# This checks thats hostanme does not overwrite $_ +my @domain = qw(foo.example.com bar.example.jp); +my @copy = @domain; + +my @dummy = grep { defined hostname() and hostname() eq $_ } @domain; + +($domain[0] && $domain[0] eq $copy[0]) + ? print "ok 2\n" + : print "not ok 2\n"; + +@dummy = grep { defined hostdomain() and hostdomain() eq $_ } @domain; + +($domain[0] && $domain[0] eq $copy[0]) + ? print "ok 3\n" + : print "not ok 3\n"; + +my $name = hostname(); +$domain = hostdomain(); +if(defined $domain && defined $name && $name ne "" && $domain ne "") { + hostfqdn() eq $name . "." . $domain ? print "ok 4\n" : print "not ok 4\n"; + domainname() eq $name . "." . $domain ? print "ok 5\n" : print "not ok 5\n";} else { + print "ok 4 # SKIP domain not fully defined\n"; + print "ok 5 # SKIP domain not fully defined\n"; +} diff --git a/t/libnet_t.pl b/t/libnet_t.pl new file mode 100644 index 0000000..cc512ca --- /dev/null +++ b/t/libnet_t.pl @@ -0,0 +1,41 @@ +use 5.008001; + +use strict; +use warnings; + +my $number = 0; +sub ok { + my ($condition, $name) = @_; + + my $message = $condition ? "ok " : "not ok "; + $message .= ++$number; + $message .= " # $name" if defined $name; + print $message, "\n"; + return $condition; +} + +sub is { + my ($got, $expected, $name) = @_; + + for ($got, $expected) { + $_ = 'undef' unless defined $_; + } + + unless (ok($got eq $expected, $name)) { + warn "Got: '$got'\nExpected: '$expected'\n" . join(' ', caller) . "\n"; + } +} + +sub skip { + my ($reason, $num) = @_; + $reason ||= ''; + $number ||= 1; + + for (1 .. $num) { + $number++; + print "ok $number # skip $reason\n"; + } +} + +1; + diff --git a/t/netrc.t b/t/netrc.t new file mode 100644 index 0000000..e270b36 --- /dev/null +++ b/t/netrc.t @@ -0,0 +1,155 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +use Cwd; +print "1..20\n"; + +# for testing _readrc +$ENV{HOME} = Cwd::cwd(); + +# avoid "used only once" warning +local (*CORE::GLOBAL::getpwuid, *CORE::GLOBAL::stat); + +*CORE::GLOBAL::getpwuid = sub ($) { + ((undef) x 7, Cwd::cwd()); +}; + +# for testing _readrc +my @stat; +*CORE::GLOBAL::stat = sub (*) { + return @stat; +}; + +# for testing _readrc +$INC{'FileHandle.pm'} = 1; + +(my $libnet_t = __FILE__) =~ s/\w+.t$/libnet_t.pl/; +require $libnet_t; + +# now that the tricks are out of the way... +eval { require Net::Netrc; }; +ok( !$@, 'should be able to require() Net::Netrc safely' ); +ok( exists $INC{'Net/Netrc.pm'}, 'should be able to use Net::Netrc' ); +$Net::Netrc::TESTING=$Net::Netrc::TESTING=1; + +SKIP: { + skip('incompatible stat() handling for OS', 4), next SKIP + if $^O =~ /os2|win32|macos|cygwin/i; + + my $warn; + local $SIG{__WARN__} = sub { + $warn = shift; + }; + + # add write access for group/other + $stat[2] = 077; ## no critic (ValuesAndExpressions::ProhibitLeadingZeros) + ok( !defined(Net::Netrc->_readrc()), + '_readrc() should not read world-writable file' ); + ok( scalar($warn =~ /^Bad permissions:/), + '... and should warn about it' ); + + # the owner field should still not match + $stat[2] = 0; + + if ($<) { + ok( !defined(Net::Netrc->_readrc()), + '_readrc() should not read file owned by someone else' ); + ok( scalar($warn =~ /^Not owner:/), + '... and should warn about it' ); + } else { + skip("testing as root",2); + } +} + +# this field must now match, to avoid the last-tested warning +$stat[4] = $<; + +# this curious mix of spaces and quotes tests a regex at line 79 (version 2.11) +FileHandle::set_lines(split(/\n/, <_readrc(), 1, '_readrc() should succeed now' ); + +# on 'foo', the login is 'nigol' +is( Net::Netrc->lookup('foo')->{login}, 'nigol', + 'lookup() should find value by host name' ); + +# on 'foo' with login 'l2', the password is 'p2' +is( Net::Netrc->lookup('foo', 'l2')->{password}, 'p2', + 'lookup() should find value by hostname and login name' ); + +# the default password is 'p3', as later declarations have priority +is( Net::Netrc->lookup()->{password}, 'p3', + 'lookup() should find default value' ); + +# lookup() ignores the login parameter when using default data +is( Net::Netrc->lookup('default', 'baz')->{password}, 'p3', + 'lookup() should ignore passed login when searching default' ); + +# lookup() goes to default data if hostname cannot be found in config data +is( Net::Netrc->lookup('abadname')->{login}, 'baz', + 'lookup() should use default for unknown machine name' ); + +# now test these accessors +my $instance = bless({}, 'Net::Netrc'); +for my $accessor (qw( login account password )) { + is( $instance->$accessor(), undef, + "$accessor() should return undef if $accessor is not set" ); + $instance->{$accessor} = $accessor; + is( $instance->$accessor(), $accessor, + "$accessor() should return value when $accessor is set" ); +} + +# and the three-for-one accessor +is( scalar( () = $instance->lpa()), 3, + 'lpa() should return login, password, account'); +is( join(' ', $instance->lpa), 'login password account', + 'lpa() should return appropriate values for l, p, and a' ); + +package FileHandle; + +sub new { + tie *FH, 'FileHandle', @_; + bless \*FH, $_[0]; +} + +sub TIEHANDLE { + my ($class, $file, $mode) = @_[0,2,3]; + bless({ file => $file, mode => $mode }, $class); +} + +my @lines; +sub set_lines { + @lines = @_; +} + +sub READLINE { + shift @lines; +} + +sub close { 1 } + diff --git a/t/nntp.t b/t/nntp.t new file mode 100644 index 0000000..559f398 --- /dev/null +++ b/t/nntp.t @@ -0,0 +1,60 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +use Net::Config; +use Net::NNTP; +use Net::Cmd qw(CMD_REJECT); + +unless(@{$NetConfig{nntp_hosts}} && $NetConfig{test_hosts}) { + print "1..0\n"; + exit; +} + +print "1..4\n"; + +my $i = 1; + +my $nntp = Net::NNTP->new(Debug => 0) + or (print("not ok 1\n"), exit); + +print "ok 1\n"; + +my @grp; +foreach my $grp (qw(test alt.test control news.announce.newusers)) { + @grp = $nntp->group($grp); + last if @grp; +} + +if($nntp->status == CMD_REJECT) { + # Command was rejected, probably because we need authinfo + map { print "ok ",$_,"\n" } 2,3,4; + exit; +} + +print "not " unless @grp; +print "ok 2\n"; + + +if(@grp && $grp[2] > $grp[1]) { + $nntp->head($grp[1]) or print "not "; +} +print "ok 3\n"; + +if(@grp) { + $nntp->quit or print "not "; +} +print "ok 4\n"; + diff --git a/t/nntp_ipv6.t b/t/nntp_ipv6.t new file mode 100644 index 0000000..768489a --- /dev/null +++ b/t/nntp_ipv6.t @@ -0,0 +1,67 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::NNTP; +use Test::More; + +my $debug = 0; # Net::NNTP->new( Debug => .. ) + +my $inet6class = Net::NNTP->can_inet6; +plan skip_all => "no IPv6 support found in Net::NNTP" if ! $inet6class; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = $inet6class->new( + LocalAddr => '::1', + Listen => 10 +); +plan skip_all => "cannot create listener on ::1: $!" if ! $srv; +my $host = $srv->sockhost; +my $port = $srv->sockport; +note("server on $host port $port"); + +plan tests => 1; + +defined( my $pid = fork()) or die "fork failed: $!"; +exit(nntp_server()) if ! $pid; + +my $cl = Net::NNTP->new(Host => $host, Port => $port,, Debug => $debug); +note("created Net::NNTP object"); +if (!$cl) { + fail("IPv6 NNTP connect failed"); +} else { + $cl->quit; + pass("IPv6 success"); +} +wait; + +sub nntp_server { + my $ssl = shift; + my $cl = $srv->accept or die "accept failed: $!"; + print $cl "200 nntp.example.com\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "205 bye\r\n"; + last; + } elsif ( $cmd eq 'MODE' ) { + print $cl "201 Posting denied\r\n"; + } else { + diag("received unknown command: $cmd"); + print "500 unknown cmd\r\n"; + } + } + note("NNTP dialog done"); + return 0; +} diff --git a/t/nntp_ssl.t b/t/nntp_ssl.t new file mode 100644 index 0000000..e6a4fe5 --- /dev/null +++ b/t/nntp_ssl.t @@ -0,0 +1,129 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::NNTP; +use Test::More; + +my $debug = 0; # Net::NNTP Debug => .. + +my $parent = 0; + +plan skip_all => "no SSL support found in Net::NNTP" if ! Net::NNTP->can_ssl; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + Listen => 10 +); +plan skip_all => "cannot create listener on localhost: $!" if ! $srv; +my $host = $srv->sockhost; +my $port = $srv->sockport; + +plan tests => 2; + +require IO::Socket::SSL::Utils; +my ($ca,$key) = IO::Socket::SSL::Utils::CERT_create( CA => 1 ); +my ($fh,$cafile) = tempfile(); +print $fh IO::Socket::SSL::Utils::PEM_cert2string($ca); +close($fh); + +$parent = $$; +END { unlink($cafile) if $$ == $parent } + +my ($cert) = IO::Socket::SSL::Utils::CERT_create( + subject => { CN => 'nntp.example.com' }, + issuer_cert => $ca, issuer_key => $key, + key => $key +); + +test(1); # direct ssl +test(0); # starttls + + +sub test { + my $ssl = shift; + defined( my $pid = fork()) or die "fork failed: $!"; + exit(nntp_server($ssl)) if ! $pid; + nntp_client($ssl); + wait; +} + + +sub nntp_client { + my $ssl = shift; + my %sslopt = ( + SSL_verifycn_name => 'nntp.example.com', + SSL_ca_file => $cafile + ); + $sslopt{SSL} = 1 if $ssl; + my $cl = Net::NNTP->new( + Host => $host, + Port => $port, + Debug => $debug, + %sslopt, + ); + note("created Net::NNTP object"); + if (!$cl) { + fail( ($ssl ? "SSL ":"" )."NNTP connect failed"); + } elsif ($ssl) { + $cl->quit; + pass("SSL NNTP connect success"); + } elsif ( ! $cl->starttls ) { + no warnings 'once'; + fail("starttls failed: $IO::Socket::SSL::SSL_ERROR"); + } else { + $cl->quit; + pass("starttls success"); + } +} + +sub nntp_server { + my $ssl = shift; + my $cl = $srv->accept or die "accept failed: $!"; + my %sslargs = ( + SSL_server => 1, + SSL_cert => $cert, + SSL_key => $key, + ); + if ( $ssl ) { + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + } + + print $cl "200 nntp.example.com\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "205 bye\r\n"; + last; + } elsif ( $cmd eq 'MODE' ) { + print $cl "201 Posting denied\r\n"; + } elsif ( ! $ssl and $cmd eq 'STARTTLS' ) { + print $cl "382 Continue with TLS negotiation\r\n"; + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + $ssl = 1; + } else { + diag("received unknown command: $cmd"); + print "500 unknown cmd\r\n"; + } + } + + note("NNTP dialog done"); +} diff --git a/t/pod.t b/t/pod.t new file mode 100644 index 0000000..e31ecf3 --- /dev/null +++ b/t/pod.t @@ -0,0 +1,51 @@ +#!perl +#=============================================================================== +# +# t/pod.t +# +# DESCRIPTION +# Test script to check POD. +# +# COPYRIGHT +# Copyright (C) 2014 Steve Hay. All rights reserved. +# +# LICENCE +# This script is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU +# General Public License or the Artistic License, as specified in the LICENCE +# file. +# +#=============================================================================== + +use 5.008001; + +use strict; +use warnings; + +use Test::More; + +#=============================================================================== +# MAIN PROGRAM +#=============================================================================== + +MAIN: { + plan skip_all => 'Author testing only' unless $ENV{AUTHOR_TESTING}; + + my $ok = eval { + require Test::Pod; + Test::Pod->import(); + 1; + }; + + if (not $ok) { + plan skip_all => 'Test::Pod required to test POD'; + } + elsif ($Test::Pod::VERSION < 1.00) { + plan skip_all => 'Test::Pod 1.00 or higher required to test POD'; + } + else { + all_pod_files_ok(); + } +} + +#=============================================================================== diff --git a/t/pod_coverage.t b/t/pod_coverage.t new file mode 100644 index 0000000..a116277 --- /dev/null +++ b/t/pod_coverage.t @@ -0,0 +1,79 @@ +#!perl +#=============================================================================== +# +# t/pod_coverage.t +# +# DESCRIPTION +# Test script to check POD coverage. +# +# COPYRIGHT +# Copyright (C) 2014, 2015 Steve Hay. All rights reserved. +# +# LICENCE +# This script is free software; you can redistribute it and/or modify it under +# the same terms as Perl itself, i.e. under the terms of either the GNU +# General Public License or the Artistic License, as specified in the LICENCE +# file. +# +#=============================================================================== + +use 5.008001; + +use strict; +use warnings; + +use Test::More; + +#=============================================================================== +# MAIN PROGRAM +#=============================================================================== + +MAIN: { + plan skip_all => 'Author testing only' unless $ENV{AUTHOR_TESTING}; + + my $ok = eval { + require Test::Pod::Coverage; + Test::Pod::Coverage->import(); + 1; + }; + + if (not $ok) { + plan skip_all => 'Test::Pod::Coverage required to test POD coverage'; + } + elsif ($Test::Pod::Coverage::VERSION < 0.08) { + plan skip_all => 'Test::Pod::Coverage 0.08 or higher required to test POD coverage'; + } + else { + plan tests => 12; + my $params = { coverage_class => qw(Pod::Coverage::CountParents) }; + pod_coverage_ok('Net::Cmd', { + %$params, + also_private => [qw(toascii toebcdic set_status)] + }); + pod_coverage_ok('Net::Config', { + %$params, + also_private => [qw(is_external)] + }); + pod_coverage_ok('Net::Domain', $params); + pod_coverage_ok('Net::FTP', { + %$params, + also_private => [qw(authorise lsl ebcdic byte cmd)] + }); + pod_coverage_ok('Net::Netrc', $params); + pod_coverage_ok('Net::NNTP', $params); + pod_coverage_ok('Net::POP3', $params); + pod_coverage_ok('Net::SMTP', { + %$params, + also_private => [qw(datafh supports)] + }); + pod_coverage_ok('Net::Time', $params); + pod_coverage_ok('Net::FTP::A', $params); + pod_coverage_ok('Net::FTP::dataconn', { + %$params, + also_private => [qw(can_read can_write cmd reading)] + }); + pod_coverage_ok('Net::FTP::I', $params); + } +} + +#=============================================================================== diff --git a/t/pop3_ipv6.t b/t/pop3_ipv6.t new file mode 100644 index 0000000..db31128 --- /dev/null +++ b/t/pop3_ipv6.t @@ -0,0 +1,67 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::POP3; +use Test::More; + +my $debug = 0; # Net::POP3->new( Debug => .. ) + +my $inet6class = Net::POP3->can_inet6; +plan skip_all => "no IPv6 support found in Net::POP3" if ! $inet6class; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = $inet6class->new( + LocalAddr => '::1', + Listen => 10 +); +plan skip_all => "cannot create listener on ::1: $!" if ! $srv; +my $saddr = "[".$srv->sockhost."]".':'.$srv->sockport; +note("server on $saddr"); + +plan tests => 1; + +defined( my $pid = fork()) or die "fork failed: $!"; +exit(pop3_server()) if ! $pid; + +my $cl = Net::POP3->new($saddr, Debug => $debug); +note("created Net::POP3 object"); +if (!$cl) { + fail("IPv6 POP3 connect failed"); +} else { + $cl->quit; + pass("IPv6 success"); +} +wait; + +sub pop3_server { + my $cl = $srv->accept or die "accept failed: $!"; + print $cl "+OK localhost ready\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "+OK bye\r\n"; + last; + } elsif ( $cmd eq 'CAPA' ) { + print $cl "+OK\r\n". + ".\r\n"; + } else { + diag("received unknown command: $cmd"); + print "-ERR unknown cmd\r\n"; + } + } + + note("POP3 dialog done"); + return 0; +} diff --git a/t/pop3_ssl.t b/t/pop3_ssl.t new file mode 100644 index 0000000..356de40 --- /dev/null +++ b/t/pop3_ssl.t @@ -0,0 +1,125 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::POP3; +use Test::More; + +my $debug = 0; # Net::POP3 Debug => .. + +my $parent = 0; + +plan skip_all => "no SSL support found in Net::POP3" if ! Net::POP3->can_ssl; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + Listen => 10 +); +plan skip_all => "cannot create listener on localhost: $!" if ! $srv; +my $saddr = $srv->sockhost.':'.$srv->sockport; + +plan tests => 2; + +require IO::Socket::SSL::Utils; +my ($ca,$key) = IO::Socket::SSL::Utils::CERT_create( CA => 1 ); +my ($fh,$cafile) = tempfile(); +print $fh IO::Socket::SSL::Utils::PEM_cert2string($ca); +close($fh); + +$parent = $$; +END { unlink($cafile) if $$ == $parent } + +my ($cert) = IO::Socket::SSL::Utils::CERT_create( + subject => { CN => 'pop3.example.com' }, + issuer_cert => $ca, issuer_key => $key, + key => $key +); + +test(1); # direct ssl +test(0); # starttls + + +sub test { + my $ssl = shift; + defined( my $pid = fork()) or die "fork failed: $!"; + exit(pop3_server($ssl)) if ! $pid; + pop3_client($ssl); + wait; +} + + +sub pop3_client { + my $ssl = shift; + my %sslopt = ( + SSL_verifycn_name => 'pop3.example.com', + SSL_ca_file => $cafile + ); + $sslopt{SSL} = 1 if $ssl; + my $cl = Net::POP3->new($saddr, %sslopt, Debug => $debug); + note("created Net::POP3 object"); + if (!$cl) { + fail( ($ssl ? "SSL ":"" )."POP3 connect failed"); + } elsif ($ssl) { + $cl->quit; + pass("SSL POP3 connect success"); + } elsif ( ! $cl->starttls ) { + no warnings 'once'; + fail("starttls failed: $IO::Socket::SSL::SSL_ERROR"); + } else { + $cl->quit; + pass("starttls success"); + } +} + +sub pop3_server { + my $ssl = shift; + my $cl = $srv->accept or die "accept failed: $!"; + my %sslargs = ( + SSL_server => 1, + SSL_cert => $cert, + SSL_key => $key, + ); + if ( $ssl ) { + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + } + + print $cl "+OK localhost ready\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "+OK bye\r\n"; + last; + } elsif ( $cmd eq 'CAPA' ) { + print $cl "+OK\r\n". + ( $ssl ? "" : "STLS\r\n" ). + ".\r\n"; + } elsif ( ! $ssl and $cmd eq 'STLS' ) { + print $cl "+OK starting ssl\r\n"; + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + $ssl = 1; + } else { + diag("received unknown command: $cmd"); + print "-ERR unknown cmd\r\n"; + } + } + + note("POP3 dialog done"); +} diff --git a/t/require.t b/t/require.t new file mode 100644 index 0000000..70ec1f6 --- /dev/null +++ b/t/require.t @@ -0,0 +1,29 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +print "1..9\n"; +my $i = 1; +eval { require Net::Config; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::Domain; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::Cmd; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::Netrc; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::FTP; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::SMTP; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::NNTP; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::POP3; } || print "not "; print "ok ",$i++,"\n"; +eval { require Net::Time; } || print "not "; print "ok ",$i++,"\n"; + + diff --git a/t/smtp.t b/t/smtp.t new file mode 100644 index 0000000..9d6f65a --- /dev/null +++ b/t/smtp.t @@ -0,0 +1,39 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } +} + +use Net::Config; +use Net::SMTP; + +unless(@{$NetConfig{smtp_hosts}} && $NetConfig{test_hosts}) { + print "1..0\n"; + exit 0; +} + +print "1..3\n"; + +my $i = 1; + +my $smtp = Net::SMTP->new(Debug => 0) + or (print("not ok 1\n"), exit); + +print "ok 1\n"; + +$smtp->domain or print "not "; +print "ok 2\n"; + +$smtp->quit or print "not "; +print "ok 3\n"; + diff --git a/t/smtp_ipv6.t b/t/smtp_ipv6.t new file mode 100644 index 0000000..f430721 --- /dev/null +++ b/t/smtp_ipv6.t @@ -0,0 +1,69 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::SMTP; +use Test::More; + +my $debug = 0; # Net::SMTP->new( Debug => .. ) + +my $inet6class = Net::SMTP->can_inet6; +plan skip_all => "no IPv6 support found in Net::SMTP" if ! $inet6class; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = $inet6class->new( + LocalAddr => '::1', + Listen => 10 +); +plan skip_all => "cannot create listener on ::1: $!" if ! $srv; +my $saddr = "[".$srv->sockhost."]".':'.$srv->sockport; +note("server on $saddr"); + +plan tests => 1; + +defined( my $pid = fork()) or die "fork failed: $!"; +exit(smtp_server()) if ! $pid; + +my $cl = Net::SMTP->new($saddr, Debug => $debug); +note("created Net::SMTP object"); +if (!$cl) { + fail("IPv6 SMTP connect failed"); +} else { + $cl->quit; + pass("IPv6 success"); +} +wait; + +sub smtp_server { + my $cl = $srv->accept or die "accept failed: $!"; + print $cl "220 welcome\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "250 bye\r\n"; + last; + } elsif ( $cmd eq 'HELO' ) { + print $cl "250 localhost\r\n"; + } elsif ( $cmd eq 'EHLO' ) { + print $cl "250-localhost\r\n". + "250 HELP\r\n"; + } else { + diag("received unknown command: $cmd"); + print "500 unknown cmd\r\n"; + } + } + + note("SMTP dialog done"); + return 0; +} diff --git a/t/smtp_ssl.t b/t/smtp_ssl.t new file mode 100644 index 0000000..7290176 --- /dev/null +++ b/t/smtp_ssl.t @@ -0,0 +1,127 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +use Config; +use File::Temp 'tempfile'; +use Net::SMTP; +use Test::More; + +my $debug = 0; # Net::SMTP Debug => .. + +my $parent = 0; + +plan skip_all => "no SSL support found in Net::SMTP" if ! Net::SMTP->can_ssl; + +plan skip_all => "fork not supported on this platform" + unless $Config::Config{d_fork} || $Config::Config{d_pseudofork} || + (($^O eq 'MSWin32' || $^O eq 'NetWare') and + $Config::Config{useithreads} and + $Config::Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/); + +my $srv = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + Listen => 10 +); +plan skip_all => "cannot create listener on localhost: $!" if ! $srv; +my $saddr = $srv->sockhost.':'.$srv->sockport; + +plan tests => 2; + +require IO::Socket::SSL::Utils; +my ($ca,$key) = IO::Socket::SSL::Utils::CERT_create( CA => 1 ); +my ($fh,$cafile) = tempfile(); +print $fh IO::Socket::SSL::Utils::PEM_cert2string($ca); +close($fh); + +$parent = $$; +END { unlink($cafile) if $$ == $parent } + +my ($cert) = IO::Socket::SSL::Utils::CERT_create( + subject => { CN => 'smtp.example.com' }, + issuer_cert => $ca, issuer_key => $key, + key => $key +); + +test(1); # direct ssl +test(0); # starttls + + +sub test { + my $ssl = shift; + defined( my $pid = fork()) or die "fork failed: $!"; + exit(smtp_server($ssl)) if ! $pid; + smtp_client($ssl); + wait; +} + + +sub smtp_client { + my $ssl = shift; + my %sslopt = ( + SSL_verifycn_name => 'smtp.example.com', + SSL_ca_file => $cafile + ); + $sslopt{SSL} = 1 if $ssl; + my $cl = Net::SMTP->new($saddr, %sslopt, Debug => $debug); + note("created Net::SMTP object"); + if (!$cl) { + fail( ($ssl ? "SSL ":"" )."SMTP connect failed"); + } elsif ($ssl) { + $cl->quit; + pass("SSL SMTP connect success"); + } elsif ( ! $cl->starttls ) { + no warnings 'once'; + fail("starttls failed: $IO::Socket::SSL::SSL_ERROR"); + } else { + $cl->quit; + pass("starttls success"); + } +} + +sub smtp_server { + my $ssl = shift; + my $cl = $srv->accept or die "accept failed: $!"; + my %sslargs = ( + SSL_server => 1, + SSL_cert => $cert, + SSL_key => $key, + ); + if ( $ssl ) { + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + } + + print $cl "220 welcome\r\n"; + while (<$cl>) { + my ($cmd,$arg) = m{^(\S+)(?: +(.*))?\r\n} or die $_; + $cmd = uc($cmd); + if ($cmd eq 'QUIT' ) { + print $cl "250 bye\r\n"; + last; + } elsif ( $cmd eq 'HELO' ) { + print $cl "250 localhost\r\n"; + } elsif ( $cmd eq 'EHLO' ) { + print $cl "250-localhost\r\n". + ( $ssl ? "" : "250-STARTTLS\r\n" ). + "250 HELP\r\n"; + } elsif ( ! $ssl and $cmd eq 'STARTTLS' ) { + print $cl "250 starting ssl\r\n"; + if ( ! IO::Socket::SSL->start_SSL($cl, %sslargs)) { + diag("initial ssl handshake with client failed"); + return; + } + $ssl = 1; + } else { + diag("received unknown command: $cmd"); + print "500 unknown cmd\r\n"; + } + } + + note("SMTP dialog done"); +} diff --git a/t/time.t b/t/time.t new file mode 100644 index 0000000..6dcba3a --- /dev/null +++ b/t/time.t @@ -0,0 +1,134 @@ +#!perl + +use 5.008001; + +use strict; +use warnings; + +BEGIN { + if (!eval { require Socket }) { + print "1..0 # no Socket\n"; exit 0; + } + if (ord('A') == 193 && !eval { require Convert::EBCDIC }) { + print "1..0 # EBCDIC but no Convert::EBCDIC\n"; exit 0; + } + $INC{'IO/Socket.pm'} = 1; + $INC{'IO/Select.pm'} = 1; + $INC{'IO/Socket/INET.pm'} = 1; +} + +(my $libnet_t = __FILE__) =~ s/time.t/libnet_t.pl/; +require $libnet_t; + +print "1..12\n"; +# cannot use(), otherwise it will use IO::Socket and IO::Select +eval{ require Net::Time; }; +ok( !$@, 'should be able to require() Net::Time safely' ); +ok( exists $INC{'Net/Time.pm'}, 'should be able to use Net::Time' ); + +# force the socket to fail +make_fail('IO::Socket::INET', 'new'); +my $badsock = Net::Time::_socket('foo', 1, 'bar', 'baz'); +is( $badsock, undef, '_socket() should fail if Socket creation fails' ); + +# if socket is created with protocol UDP (default), it will send a newline +my $sock = Net::Time::_socket('foo', 2, 'bar'); +ok( $sock->isa('IO::Socket::INET'), 'should be an IO::Socket::INET object' ); +is( $sock->{sent}, "\n", 'should send \n with UDP protocol set' ); +is( $sock->{timeout}, 120, 'timeout should default to 120' ); + +# now try it with a custom timeout and a different protocol +$sock = Net::Time::_socket('foo', 3, 'bar', 'tcp', 11); +ok( $sock->isa('IO::Socket::INET'), 'should be an IO::Socket::INET object' ); +is( $sock->{sent}, undef, '_socket() should send nothing unless UDP protocol' ); +is( $sock->{PeerAddr}, 'bar', '_socket() should set PeerAddr in socket' ); +is( $sock->{timeout}, 11, '_socket() should respect custom timeout value' ); + +# inet_daytime +# check for correct args (daytime, 13) +IO::Socket::INET::set_message('z'); +is( Net::Time::inet_daytime('bob'), 'z', 'inet_daytime() should receive data' ); + +# magic numbers defined in Net::Time +my $offset = $^O eq 'MacOS' ? + (4 * 31536000) : (70 * 31536000 + 17 * 86400); + +# check for correct args (time, 13) +# pretend it is only six seconds since the offset, create a fake message +# inet_time +IO::Socket::INET::set_message(pack("N", $offset + 6)); +is( Net::Time::inet_time('foo'), 6, + 'inet_time() should calculate time since offset for time()' ); + + +my %fail; + +sub make_fail { + my ($pack, $func, $num) = @_; + $num = 1 unless defined $num; + + $fail{$pack}{$func} = $num; +} + +package IO::Socket::INET; + +$fail{'IO::Socket::INET'} = { + new => 0, + 'send' => 0, +}; + +sub new { + my $class = shift; + return if $fail{$class}{new} and $fail{$class}{new}--; + bless( { @_ }, $class ); +} + +sub send { + my $self = shift; + my $class = ref($self); + return if $fail{$class}{'send'} and $fail{$class}{'send'}--; + $self->{sent} .= shift; +} + +my $msg; +sub set_message { + if (ref($_[0])) { + $_[0]->{msg} = $_[1]; + } else { + $msg = shift; + } +} + +sub do_recv { + my ($len, $msg) = @_[1,2]; + $_[0] .= substr($msg, 0, $len); +} + +sub recv { + my ($self, $buf, $length, $flags) = @_; + my $message = exists $self->{msg} ? + $self->{msg} : $msg; + + if (defined($message)) { + do_recv($_[1], $length, $message); + } + 1; +} + +package IO::Select; + +sub new { + my $class = shift; + return if defined $fail{$class}{new} and $fail{$class}{new}--; + bless({sock => shift}, $class); +} + +sub can_read { + my ($self, $timeout) = @_; + my $class = ref($self); + return if defined $fail{$class}{can_read} and $fail{class}{can_read}--; + $self->{sock}{timeout} = $timeout; + 1; +} + +1;