#! /usr/bin/perl

# simpleftp - Rudimentary FTP and HTTP(S) client.
#
# Author: David Lawrence <tale@isc.org>.
#         Rewritten by Julien Élie in 2007 to use Net::FTP.
#
# Various bug fixes, code and documentation improvements by Julien Élie
# in 2007, 2009, 2011, 2021, 2022, 2024, 2025.
#
# Fetch files to the local machine based on URLs on the command line.
# INN's configure searches for wget (as well as ncftpget or ncftp for the sole
# scope of FTP) but they're all system add-ons, so this is provided.
#
# This script is nowhere near as flexible as libwww (Perl LWP module).  If you
# really need that kind of power, get libwww and use it.  This script is just
# sufficient for what INN needed.  As Perl 5 is already required by other parts
# of INN, this was the easiest way to go for a backup plan.

use strict;
use warnings;

use Net::FTP;
use Sys::Hostname;

# HTTP::Tiny is only installed in Perl 5.14.0 and above as a Perl core module.
# Besides, IO::Socket::SSL and Net::SSLeay are also required for HTTPS support.
my $http_available = 0;
eval { require HTTP::Tiny; $http_available = 1 };

$0 =~ s{.*/}{};

my $usage = "Usage: $0 url [...]\n";

# The following variables could be flags passed to simpleftp, but well, it is
# not intended to replace a full-featured program like wget!
my $debug = 0;       # 1 to enable verbose output for Net::FTP.
my $passive = 1;     # 0 for active FTP mode, 1 for passive FTP mode.
my $timeout = 30;    # Timeout in seconds for HTTP::Tiny and Net::FTP.

@ARGV
  or die $usage;

my @unique_hash_keys_unused = qw(success);    # perltidy -wuk
my ($lasthost, $ftp);
my $ftp_session = 0;

# This will keep track of how many _failed_.
my $exit = @ARGV;

for (@ARGV) {
    my ($protocol, $host, $path) = m{^([^:]+)://([^/]+)(/.+)};
    my $url = $_;
    my $user = 'anonymous';
    my $pass = (getpwuid($<))[0] . '@' . hostname;
    my $port;

    if ($protocol eq "ftp") {
        $port = 21;
    } elsif ($protocol eq "http") {
        if (!$http_available) {
            warn "$0: unsupported protocol: $protocol "
              . "(missing HTTP::Tiny Perl package)\n";
            next;
        }
        $port = 80;
    } elsif ($protocol eq "https") {
        if (!$http_available) {
            warn "$0: unsupported protocol: $protocol "
              . "(missing HTTP::Tiny Perl package)\n";
            next;
        }
        if (!HTTP::Tiny::can_ssl()) {
            warn "$0: unsupported protocol: $protocol "
              . "(missing IO::Socket::SSL and/or Net::SSLeay Perl packages\n";
            next;
        }
        $port = 443;
    } else {
        warn "$0: unsupported protocol: $protocol\n";
        next;
    }

    if (not defined $host or not defined $path) {
        warn "$0: bad URL: $url\n";
        next;
    }

    if ($host =~ /(.*):(.*)\@(.*)/) {
        $user = $1;
        $pass = $2;
        $host = $3;
    }

    # Do not search a port for IPv6 addresses.
    if ($host !~ /^[\da-fA-F\:]+$/ && $host =~ /(.*):(.*)/) {
        $host = $1;
        $port = $2;
    }

    if (defined $lasthost && $host ne $lasthost) {
        $ftp->quit if $ftp_session;
        $ftp_session = 0;
        $lasthost = undef;
    }

    my $localfile = $path;
    $path =~ s{[^/]+$}{};
    $localfile =~ s{.*/}{};

    if ($protocol eq "ftp") {
        if (not defined $lasthost) {
            $ftp = Net::FTP->new(
                Host    => $host,
                Port    => $port,
                Timeout => $timeout,
                Passive => $passive,
                Debug   => $debug,
            ) or next;

            $ftp_session = 1;

            $ftp->login($user, $pass)
              or next;
        }

        $ftp->binary
          or next;

        $ftp->cwd($path)
          or next;

        $ftp->get($localfile)
          or next;
    } elsif ($protocol =~ /^https?$/) {
        my $http = HTTP::Tiny->new(
            timeout => $timeout,
        ) or next;
        my $response = $http->mirror($url, $localfile);
        next if not $response->{success};
    }

    $exit--;
    $lasthost = $host;
}

$ftp->quit
  if $ftp_session && defined $lasthost;

exit $exit;
