#!/usr/bin/perl
# ---------------------------------------------------------------------------
# Copyright (C) 2000-2010 TJ Saunders <tj@castaglia.org>
#
# 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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
#
# Based on MacGuyver's genuser.pl script, this script generates password
# files suitable for use with proftpd's AuthUserFile directive, in passwd(5)
# format, or AuthGroupFile, in group(5) format.  The idea is somewhat similar
# to Apache's htpasswd program.
#
#  $Id: ftpasswd,v 1.10.4.2 2010/05/27 17:50:50 castaglia Exp $
#
# ---------------------------------------------------------------------------

use strict;

use File::Basename qw(basename);
use Getopt::Long;

# turn off auto abbreviation
$Getopt::Long::auto_abbrev = 0;

my $program = basename($0);
my $default_passwd_file = "./ftpd.passwd";
my $default_group_file = "./ftpd.group";
my $shell_file = "/etc/shells";
#my $default_cracklib_dict = "/usr/lib/cracklib_dict";
my $default_cracklib_dict = "/var/cache/cracklib";
my $cracklib_dict;
my $output_file;
my $version = "1.1.3";

my @data;

my %opts = ();
GetOptions(\%opts,
  'change-password',
  'delete-group',
  'delete-user',
  'des',
  'enable-group-passwd',
  'file=s',
  'F|force',
  'gecos=s',
  'gid=n',
  'group',
  'hash',
  'h|help',
  'home=s',
  'md5',
  'm|member=s@',
  'name=s',
  'not-previous-password',
  'not-system-password',
  'passwd',
  'shell=s',
  'stdin',
  'uid=n',
  'use-cracklib:s',
  'version',
);

usage() if (defined($opts{'h'}));

version() if (defined($opts{'version'}));

# check if "use-cracklib" was given as an option, and whether a path
# to other dictionary files was given.
if (defined($opts{'use-cracklib'})) {

  # make sure that Crypt::Cracklib is installed before trying to use
  # it later
  eval { require Crypt::Cracklib };
  die "$program: --use-cracklib requires Crypt::Cracklib to be installed\n" if $@;

  if ($opts{'use-cracklib'} ne "") {
    $cracklib_dict = $opts{'use-cracklib'};

  } else {
    $cracklib_dict = $default_cracklib_dict;
  }
}

# make sure that both passwd and group modes haven't been simultaneously
# requested

if ((exists($opts{'passwd'}) && exists($opts{'group'})) ||
    (exists($opts{'passwd'}) && exists($opts{'hash'})) ||
    (exists($opts{'group'}) && exists($opts{'hash'}))) {
  die "$program: please use *one*: --passwd, --group, or --hash\n";

} elsif (defined($opts{'passwd'})) {

  # determine to which file to write the passwd entry
  if (defined($opts{'file'})) {
    $output_file = $opts{'file'}; 
    print STDOUT "$program: using alternate file: $output_file\n"

  } else {
    $output_file = $default_passwd_file;
  } 

  # make sure that the required arguments are present
  die "$program: --passwd: missing required argument: --name\n"
    unless (defined($opts{'name'}));

  # check for and handle the --delete-user option.
  if (defined($opts{'delete-user'})) {

    # make sure we can write/update the file first
    unless (chmod 0644, $output_file) {
      die "$program: unable to set permissions on $output_file to 0644: $!\n";
    }

    open_output_file();

    my ($pass, $uid, $gid, $gecos, $home, $shell) = find_passwd_entry(name =>
      $opts{'name'});

    handle_passwd_entry(name => $opts{'name'}, uid => $uid, gid => $gid,
      gecos => $gecos, home => $home, shell => $shell,
      delete_user => $opts{'delete-user'});

    close_output_file();

    # done
    exit 0;
  }

  # now check for the --change-password option.  If present, lookup
  # the given name in the password file, and reuse all the information
  # except for the password
  if (defined($opts{'change-password'})) {
    open_output_file();

    my ($pass, $uid, $gid, $gecos, $home, $shell) = find_passwd_entry(name =>
      $opts{'name'});

    handle_passwd_entry(name => $opts{'name'}, uid => $uid, gid => $gid,
      gecos => $gecos, home => $home, shell => $shell);

    close_output_file();

    # done
    exit 0;
  }

  # check for the --not-system-password option.  If present, make sure that
  # a) the script is running with root privs, and b) perl on the system is
  # such that getpwnam() will return the system password
  if (defined($opts{'not-system-password'})) {
    die "$program: must be user root for system password check\n"
      unless ($> == 0);
  }

  die "$program: --passwd: missing required argument: --home\n"
    unless (defined($opts{'home'}));

  die "$program: --passwd: missing required argument: --shell\n"
    unless (defined($opts{'shell'}));

  die "$program: --passwd: missing required argument: --uid\n"
    unless (defined($opts{'uid'}));

  # As per Flying Hamster's suggestion, have $opts{'gid'} default to --uid
  # if none are specified on the command-line via --gid
  unless (defined($opts{'gid'})) {
    $opts{'gid'} = $opts{'uid'};
    warn "$program: --passwd: missing --gid argument: default gid set to uid\n";
  }

  open_output_file();

  handle_passwd_entry(name => $opts{'name'}, uid => $opts{'uid'},
    gid => $opts{'gid'}, gecos => $opts{'gecos'}, home => $opts{'home'},
    shell => $opts{'shell'}, delete_user => $opts{'delete-user'});

  close_output_file();

  # NOTE: if this process is not running as root, then the file generated
  # is not owned by root.  Issue a warning reminding the user to make the
  # generated file mode 0400, owned by root, before using it.

} elsif (defined($opts{'group'})) {

  # determine to which file to write the group entry
  if (defined($opts{'file'})) {
    $output_file = $opts{'file'};
    print STDOUT "$program: using alternate file: $output_file\n";

  } else {
    $output_file = $default_group_file;
  }

  # check for and handle the --delete-group option.
  if (defined($opts{'delete-group'})) {

    # make sure we can write/update the file first
    unless (chmod 0644, $output_file) {
      die "$program: unable to set permissions on $output_file to 0644: $!\n";
    }

    open_output_file();

    handle_group_entry(name => $opts{'name'},
      delete_group => $opts{'delete-group'});

    close_output_file();

    # done
    exit 0;
  }

  # make sure the required options are present
  die "$program: --group: missing required argument: --gid\n"
    unless (defined($opts{'gid'}));

  die "$program: --group: missing required argument: --name\n"
    unless (defined($opts{'name'}));

  open_output_file();

  handle_group_entry(gid => $opts{'gid'}, members => $opts{'m'},
    name => $opts{'name'}, delete_user => $opts{'delete-user'},
    delete_group => $opts{'delete-group'});

  close_output_file();

} elsif (defined($opts{'hash'})) {
  print STDOUT "$program: ", get_passwd(), "\n";

} else {
  die "$program: missing required --passwd or --group\n$program: use $program --help for details on usage\n\n";
}

# done
exit 0;

# ----------------------------------------------------------------------------
sub check_shell {
  my %args = @_;

  my $shell = $args{'shell'};
  my $result = 0;

  # check the given shell against the list in /etc/shells.  If not present
  # there, issue a message recognizing this, and suggesting that
  # RequireValidShell be set to off, and that any necessary PAM modules be
  # adjusted.

  unless (open(SHELLS, "< $shell_file")) {
    warn "$program: unable to open $shell_file: $!\n";
    warn "$program: skipping check of $shell_file\n";
    return;
  }

  while(my $line = <SHELLS>) {
    chomp($line);

    if ($line eq $shell) {
      $result = 1;
      last;
    } 
  }

  close(SHELLS);

  unless ($result) {
    print STDOUT "\n$program: $shell is not among the valid system shells.  Use of\n";
    print STDOUT "$program: \"RequireValidShell off\" may be required, and the PAM\n";
    print STDOUT "$program: module configuration may need to be adjusted.\n\n";
  }

  return $result;
}

# ----------------------------------------------------------------------------
sub close_output_file {
  my %args = @_;

  open(OUTPUT, "> $output_file") or
    die "$program: unable to open $output_file: $!\n";

  # flush the data to the file
  foreach my $line (@data) {
    print OUTPUT "$line\n";
  }

  # set the permissions appropriately, ie 0444, before closing the file
  chmod 0444, $output_file;

  close(OUTPUT) or die "$program: unable to close $output_file: $!\n";
}

# ----------------------------------------------------------------------------
sub find_passwd_entry {
  my %args = @_;

  my $name = $args{'name'};
  my ($pass, $uid, $gid, $gecos, $home, $shell);
  my $found = 0;

  # given a name, find the corresponding entry in the passwd file
  foreach my $line (@data) {
    next unless $line =~ /^$name:/;

    my @fields = split(':', $line);

    $pass = $fields[1];
    $uid = $fields[2];
    $gid = $fields[3];
    $gecos = $fields[4];
    $home = $fields[5];
    $shell = $fields[6];

    $found = 1;

    last;
  }

  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  unless ($found
  